Copy ModelState Errors to TempData & Display them

2019-04-01 06:31发布

问题:

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?

回答1:

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


回答2:

I started going down this road, and then read your answer. I combined them into the following files:

TempDataDictionaryExtensions.cs

I created extension methods to do the dirty work on the TempData, because I felt it didn't belong in the Action Filter itself.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace Project.Web.UI.Domain
{
    public static class TempDataDictionaryExtensions
    {
        private const string _ModelStateErrorsKey = "ModelStateErrors";

        public static IEnumerable<string> GetModelErrors(this TempDataDictionary instance)
        {
            return TempDataDictionaryExtensions.GetErrorsFromTempData(instance);
        }

        public static void AddModelError(this TempDataDictionary instance, string error)
        {
            TempDataDictionaryExtensions.AddModelErrors(instance, new List<string>() { error });
        }

        public static void AddModelErrors(this TempDataDictionary instance, IEnumerable<string> errors)
        {
            TempDataDictionaryExtensions.AddErrorsToTempData(instance, errors);
        }

        private static List<string> GetErrorsFromTempData(TempDataDictionary instance)
        {
            object tempObject = instance.FirstOrDefault(x => x.Key == TempDataDictionaryExtensions._ModelStateErrorsKey);
            if (tempObject == null)
            {
                return new List<String>();
            }
            List<string> tempErrors = instance.FirstOrDefault(x => x.Key == TempDataDictionaryExtensions._ModelStateErrorsKey).Value as List<string>;
            if (tempErrors == null)
            {
                return new List<String>();
            }
            return tempErrors;
        }

        private static void AddErrorsToTempData(TempDataDictionary instance, IEnumerable<string> errors)
        {
            List<string> tempErrors;

            object tempObject = instance.FirstOrDefault(x => x.Key == TempDataDictionaryExtensions._ModelStateErrorsKey);
            if (tempObject == null)
            {
                tempErrors = new List<String>();
            }
            else
            {
                tempErrors = instance.FirstOrDefault(x => x.Key == TempDataDictionaryExtensions._ModelStateErrorsKey).Value as List<string>;
                if (tempErrors == null)
                {
                    tempErrors = new List<String>();
                }
            }

            tempErrors.AddRange(errors);

            instance[TempDataDictionaryExtensions._ModelStateErrorsKey] = tempErrors;
        }
    }
}

TempDataModelStateAttribute.cs

My original, copied the errors out of TempData back into ModelState prior to the ActionResult executing via OnResultExecuting. This is a combination of copying them into TempData and back out.

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace Project.Web.UI.Domain
{
    public class TempDataModelStateAttribute : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            IEnumerable<string> modelErrors = ((Controller)filterContext.Controller).TempData.GetModelErrors();
            if (modelErrors != null
                && modelErrors.Count() > 0)
            {
                modelErrors.ToList()
                           .ForEach(x => ((Controller)filterContext.Controller).ModelState.AddModelError("GenericError", x));
            }
            base.OnResultExecuting(filterContext);
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (!filterContext.Controller.ViewData.ModelState.IsValid)
            {
                if (filterContext.Result is RedirectResult
                    || filterContext.Result is RedirectToRouteResult)
                {
                    List<string> errors = new List<string>();
                    foreach (var obj in filterContext.Controller.ViewData.ModelState.Values)
                    {
                        foreach (var error in obj.Errors)
                        {
                            errors.Add(error.ErrorMessage);
                        }
                    }
                    ((Controller)filterContext.Controller).TempData.AddModelErrors(errors); 
                }
            }

            base.OnActionExecuted(filterContext);
        }
    }
}


回答3:

You should seriously consider this concept: http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg