Jef Claes

On software and life

17 Jun 2012

Persisting model state when using PRG

I’ve been working on an ASP.NET MVC application in which we frequently apply the Post/Redirect/Get pattern. One of the direct consequences of applying this pattern is that you often want to persist the model state across redirects, so that you don’t lose validation errors, or the values of input fields.

To persist the model state across redirects, we can put TempData to work. The sole purpose of TempData is exactly this; persisting state until the next request.

public ActionResult Index()
{
    ViewData.Model = ...

    if (TempData.ContainsKey("ModelState"))
        ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);

    return View();
}

[HttpPost]        
public ActionResult Update(AddModel inputModel)
{
    if (ModelState.IsValid)
        ...

    TempData["ModelState"] = ModelState;

    return RedirectToAction("Index");
}

So this works, but I found it to be a bit too cumbersome. And so did Davy Brion, he introduced a clean abstraction into the project, smoothing out some of the friction: making use of action filter attributes, we were able to eliminate duplication across controllers, leaving behind an AOP-ish taste.

The SetTempDataModelStateAttribute stores the model state in the TempData dictionary.

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

While the RestoreModelStateFromTempDataAttribute restores it by pulling it out of TempData again, when it exists.

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"]);
        }
    }
}

So when we apply these attributes to the example at the beginning of this post, we end up with something like this.

[RestoreModelStateFromTempData]
public ActionResult Index()
{
    ViewData.Model = ...    

    return View();
}

[HttpPost]   
[SetTempDataModelState]     
public ActionResult Update(AddModel inputModel)
{
    if (ModelState.IsValid)
        ...
    
    return RedirectToAction("Index");
}

Very clean. I’m interested to hear how you handle these concerns when using the PRG pattern.