Search code examples
asp.net-mvcasp.net-mvc-3modelstatetempdata

Copy ModelState Errors to TempData & Display them In the view


Most of my action methods return PartialViews on success and RedirectToAction results on failure. For that, I would like to copy the model state errors into TempData so I could display them to the user. I've read several questions here on SO and some external links but none of them worked for me... I'm decorating the ActionMethod with ModelStateToTempData attribute from MvcContrib, then displaying it as follows in the view: (this is just a prototype)

        @if (TempData.Count > 0)
        {
            foreach (var obj in TempData)
            {
                var errors = ((ModelStateDictionary)obj.Value).Values;
                foreach (var error in errors)
                {
                <div style="position:absolute; background:Black; color:White; top:250px; left:550px;">
                    <span style="margin-bottom:5px; display:block; height:25px;">@error.Value</span>
                </div>
                }
            }
        }

Rather than displaying the error itself, I keep getting System.Web.Mvc.ValueProviderResult. I know this is all wrong, and eventually I would want to filter the model state errors into a dictionary inside the TempData but for now I just want to have the error string displayed in the view.

P.S: I've tried to do it manually without the MvcContrib attribute, and I got the same result. But I do prefer to use my own code so I could have more control over the whole issue.

Any suggestions?


Solution

  • Ok After trying a million things, I found the answer myself... :)

    if (TempData["ModelErrors"] == null)
        TempData.Add("ModelErrors", new List<string>());
    foreach (var obj in ModelState.Values)
    {
        foreach (var error in obj.Errors)
        {
            if(!string.IsNullOrEmpty(error.ErrorMessage))
                ((List<string>)TempData["ModelErrors"]).Add(error.ErrorMessage);
        }
    }
    return RedirectToAction("Index", "Home");
    

    And in the view:

        <div id="validationMessages">
            @{
                var errors = (List<string>)TempData["ModelErrors"];
            }
            @if (errors != null && errors.Count() > 0)
            {
                <div style="position:absolute; background:Black; color:White; top:250px; left:550px;">
                    @foreach (var error in errors)
                    { 
                       <span style="margin-bottom:5px; display:block; height:25px;">@error</span> 
                    }
                </div>
            }
        </div>
    

    UPDATE:

    Here it is inside an ActionFilter:

    public class CopyModelStateErrorsToTempData : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            //Only export when ModelState is not valid
            if (!filterContext.Controller.ViewData.ModelState.IsValid)
            {
                //Export if we are redirecting
                if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
                {
                    if (filterContext.Controller.TempData["ModelErrors"] == null)
                        filterContext.Controller.TempData.Add("ModelErrors", new List<string>());
                    foreach (var obj in filterContext.Controller.ViewData.ModelState.Values)
                    {
                        foreach (var error in obj.Errors)
                        {
                            if (!string.IsNullOrEmpty(error.ErrorMessage))
                                ((List<string>)filterContext.Controller.TempData["ModelErrors"]).Add(error.ErrorMessage);
                        }
                    }
                }
            }
    
            base.OnActionExecuted(filterContext);
        }
    }