ASP.NET MVC - How to Preserve ModelState Errors Across RedirectToAction?

I had to solve this problem today myself, and came across this question.

Some of the answers are useful (using TempData), but don't really answer the question at hand.

The best advice I found was on this blog post:

http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html

Basically, use TempData to save and restore the ModelState object. However, it's a lot cleaner if you abstract this away into attributes.

E.g.

public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);         
        filterContext.Controller.TempData["ModelState"] = 
           filterContext.Controller.ViewData.ModelState;
    }
}

public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        if (filterContext.Controller.TempData.ContainsKey("ModelState"))
        {
            filterContext.Controller.ViewData.ModelState.Merge(
                (ModelStateDictionary)filterContext.Controller.TempData["ModelState"]);
        }
    }
}

Then as per your example, you could save / restore the ModelState like so:

[HttpGet]
[RestoreModelStateFromTempData]
public ActionResult Create(string uniqueUri)
{
    // get some stuff based on uniqueuri, set in ViewData.  
    return View();
}

[HttpPost]
[SetTempDataModelState]
public ActionResult Create(Review review)
{
    // validate review
    if (validatedOk)
    {
        return RedirectToAction("Details", new { postId = review.PostId});
    }  
    else
    {
        ModelState.AddModelError("ReviewErrors", "some error occured");
        return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
    }   
}

If you also want to pass the model along in TempData (as bigb suggested) then you can still do that too.


You need to have the same instance of Review on your HttpGet action. To do that you should save an object Review review in temp variable on your HttpPost action and then restore it on HttpGet action.

[HttpGet]
public ActionResult Create(string uniqueUri)
{
   //Restore
   Review review = TempData["Review"] as Review;            

   // get some stuff based on uniqueuri, set in ViewData.  
   return View(review);
}
[HttpPost]
public ActionResult Create(Review review)
{
   //Save your object
   TempData["Review"] = review;

   // validate review
   if (validatedOk)
   {
      return RedirectToAction("Details", new { postId = review.PostId});
   }  
   else
   {
      ModelState.AddModelError("ReviewErrors", "some error occured");
      return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
   }   
}

If you want this to work even if the browser is refreshed after the first execution of the HttpGet action, you could do this:

  Review review = TempData["Review"] as Review;  
  TempData["Review"] = review;

Otherwise on refresh button object review will be empty because there wouldn't be any data in TempData["Review"].