How to handle model state errors in ajax-invoked controller action that returns a PartialView How to handle model state errors in ajax-invoked controller action that returns a PartialView ajax ajax

How to handle model state errors in ajax-invoked controller action that returns a PartialView


Solution

I had to write two separate parts that automagically work exactly as intended.

So it should return a partial view when controller action process succeeds and it should throw an error with some failure details when things are not ok so things on the client side would distinguish success as well as failure instead of always handling success.

There are two major parts that are used to achieve this:

  • A custom exception class that is thrown when something goes wrong so we can distinguish between general exceptions that can happen any time for whatever reason and errors related to our processing (most notably invalid model state)
  • Exception action filter that catches our custom exception and prepares result based on that exception; As you'll see from the code, our custom exception will hold information about model state errors so this filter will be able to return custom HTTP status code as well some textual information

On to details then...

External link: All this information (detailed explanation as well as all the code) is also available on my blog. Latest code updates will always be published there.

Custom exception class

This class provides two things

  1. make it simple to distinguish model state errors from regular exceptions
  2. provide some basic functionality I can use later

This class is later used in my custom error filter.

public class ModelStateException : Exception{    public Dictionary<string, string> Errors { get; private set; }    public ModelStateDictionary ModelState { get; private set; }    public override string Message    {        get        {            if (this.Errors.Count > 0)            {                return this.Errors.First().Value;            }            return null;        }    }    private ModelStateException()    {        this.Errors = new Dictionary<string, string>();    }    public ModelStateException(ModelStateDictionary modelState) : this()    {        this.ModelState = modelState;        if (!modelState.IsValid)        {            foreach (KeyValuePair<string, ModelState> state in modelState)            {                if (state.Value.Errors.Count > 0)                {                    this.Errors.Add(state.Key, state.Value.Errors[0].ErrorMessage);                }            }        }    }}

Error filter attribute

This attribute helps returning errors to the client in terms of HTTP error codes when there are any model state errors.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]public class HandleModelStateExceptionAttribute : FilterAttribute, IExceptionFilter{    public void OnException(ExceptionContext filterContext)    {        if (filterContext == null)        {            throw new ArgumentNullException("filterContext");        }        if (filterContext.Exception != null && typeof(ModelStateException).IsInstanceOfType(filterContext.Exception) && !filterContext.ExceptionHandled)        {            filterContext.ExceptionHandled = true;            filterContext.HttpContext.Response.Clear();            filterContext.HttpContext.Response.ContentEncoding = Encoding.UTF8;            filterContext.HttpContext.Response.HeaderEncoding = Encoding.UTF8;            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;            filterContext.HttpContext.Response.StatusCode = 400;            filterContext.HttpContext.Response.StatusDescription = (filterContext.Exception as ModelStateException).Message;        }    }}

After that, I simply decorated my controller action with my attribute and voila. I did get errors on the client with code 400 and correct information that I set in my filter. This information is then displayed to the user (when it's related to model state errors it displays information which form fields user should amend to make the form valid).

[HandleModelStateException]public ActionResult AddComment(MyModel data){    // check if state is valid    if (!this.ModelState.IsValid)    {        throw new ModelStateException(this.ModelState);    }    // get data from store    return PartialView("Comment", /* store data */ );}

This makes my code reusable with any model state errors and those will get sent to the client as they should.

One single issue (is now resolved)

But there's still one issue related to this code. When my error action filter sets StatusDescription and that string contains some special characters like Č, I get rubbish on the client. Unless I use IE (I'm using version 8). FF and CH display rubbish. That's why I set encodings but it doesn't work. If anyone has a workaround for this particularity I'd be more than glad to listen in.
If I return error message in content itself, everything's fine. Encoding is correct and I can display whatever I want.


Try this.

public ActionResult DoAjaxAction(Entity entity){   if(ModelState.IsValid)   {     return PartialView("Valid_View", entity);   }   else   {     return PartialView("Invalid_View", entity);   } }