How do I maintain ModelState errors when using RedirectToAction?
Asked Answered
M

5

26

I have some code that saves a ticket in our system. If there is an error it does a RedirectToAction(). The problem is that I don't seem to have my errors in the new action. How can I fix this?

 ModelState.AddModelError("_FORM", "Unable to save ticket");
 ModelState.AddModelError("_FORM", "Phone number was invalid.");
 ModelState.AddModelError("_FORM", "Lane number is required.");
 return RedirectToAction("CreateStep", "Ticket");

I know some have suggested using TempData, but how would I get each error out of the ModelState?

Thanks.

Meek answered 18/3, 2009 at 15:24 Comment(0)
R
45

The PRG pattern is ok, but I did this:

Base controller:

protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
    if (TempData["ModelState"] != null && !ModelState.Equals(TempData["ModelState"]))
        ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);

    base.OnActionExecuted(filterContext);
}

Action (I'm using xVal):

try
{
    user.Login();
    AuthenticationManager.SignIn(user);
}
catch (RulesException rex)
{
    // on bad login
    rex.AddModelStateErrors(ModelState, "user");
    TempData["ModelState"] = ModelState;
    return Redirect(Request.UrlReferrer.ToString());
}

The action throws an exception, adds the ModelState to TempData and redirects back to the referrer. Since the action is caught, OnActionExecuted is still executed, but the first time around the ModelState is the same as TempData["ModelState"], so you don't want to merge with yourself. When the redirect action is executed, OnActionExecuted fires again. This time, if there's anything in TempData["ModelState"], it merges with this action's ModelState.

You could expand it to multiple models by using TempData["ModelState.user"] = ModelState and then merging every TempData object that starts with ModelState..

Ravi answered 11/4, 2009 at 22:18 Comment(2)
I like this... Seems a handy way to make this work. Thank you!Quondam
@Bill. I love it. I have extended your code for my usage. Instead of your RulesException.AddModelStateErrors(), I did it the other way, I extended ModelState to have two extension methods; UpdateModel and RestoreModel. +1 for you.Nakesha
B
30

I know this thread is old, but this blog about ASP.NET Best Practices has some excellent suggestions.
#13 on the page deals with using 2 Action filters to save and restore ModelState between redirects.

This is the pattern that my work uses, and I love it.

Here's the simplified example:

[ImportModelStateFromTempData]
public ActionResult Dashboard()
{
    return View();
}

[AcceptVerbs(HttpVerbs.Post), ExportModelStateToTempData]
public ActionResult Dashboard(string data)
{
    if (ValidateData(data))
    {
        try
        {
            _service.Submit(data);
        }
        catch (Exception e)
        {
            ModelState.AddModelError(ModelStateException, e);
        }
    }

    return RedirectToAction("Dashboard");
}
Burrton answered 18/11, 2011 at 21:12 Comment(4)
The age of the question or answers really doesn't matter - even here in 2014 they can help :-) And thanks for the link, really good stuff!Vertu
The [ImportModelStateFromTempData] and [ExportModelStateToTempData] attributes are also explained here: How to save the ModelState into session following the good practiceFascinating
Your link (which is ubiquitous with every discussion of PRG in MVC I saw) is weirdly dead. It looks like Rashid's entire blog was removed from the ASP.NET team blog.Culet
Found (presumably) the (same) link asp.net MVC Best Practices (Part 1) - see #13. Use PRG Pattern for Data ModificationAmidst
R
4

this blog post describes how you could implement the PRG-Pattern in MVC http://blog.simonlovely.com/archive/2008/11/26/post-redirect-get-pattern-in-mvc.aspx

hth

Reata answered 24/3, 2009 at 12:2 Comment(1)
This link is brokenGurango
L
3

Use the TempData[] Collection

The tempdata is stored from one request to the next, then its gone.

Less answered 18/3, 2009 at 20:55 Comment(0)
N
0

What I did to maintain my ModelState no matter where I go with redirects is the following:

  1. In your model, add:

    public ModelStateDictionary modelstate { get; set; }
    
  2. In your model's constructor, add:

    this.modelstate = new System.Web.Mvc.ModelStateDictionary();
    
  3. Sample Post with my model called Models.ContactInformation:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult contact(Models.ContactInformation con)
    {
      if (string.IsNullOrEmpty(con.SelectedAgencySelectorType))
      {
        ModelState.AddModelError("", "You did not select an agency type.");
      }
    
      con.modelstate = ModelState;
      TempData["contact"] = con;
      if (!ModelState.IsValid) return RedirectToAction("contactinformation", "reports");
    
        //do stuff
    
        return RedirectToAction("contactinformation", "reports");
    }
    
  4. So now your tempdata has your model and modelstate as is.

  5. The following is my view that is agnostic to the state of anything, unless it has something. Here's the code:

    [HttpGet]
    public ActionResult contactinformation()
    {
        //try cast to model
        var m = new Models.ContactInformation();
        if (TempData["contact"] is Models.ContactInformation) m = (Models.ContactInformation)TempData["contact"];
    
        //restore modelstate if needed
        if (!m.modelstate.IsValid)
        {
            foreach (ModelState item in m.modelstate.Values)
            {
                foreach (ModelError err in item.Errors)
                {
                    ModelState.AddModelError("", err.ErrorMessage.ToString());
                }
            }
        }
    
        return View(m);
    }
    
Neuritis answered 15/11, 2016 at 22:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.