Return all ModelState errors with field keys as json data in mvc

by John Nye

04 Dec
2013

I was recently validating a jquery form submission and wanted to return all the model state errors as json back to the page. Retrieving all model state errors isn't as simple as you might think since on each field can contain multiple errors. Here is how I did it.

Define a class to hold each model error

public class Error
{
    public Error(string key, string message)
    {
        Key = key;
        Message = message;
    }

    public string Key{ get; set; }
    public string Message { get; set; }
}

Create an extension method to retrieve the errors

Because we are using Linq to retrieve the errors, we can either create the extension method using Method syntax or Query syntax. Both solutions are provided below.

Option 1: Uses Method syntax. It's a bit longer but arguably more readable.

public static class ModelStateExtensions
{
    public static IEnumerable<Error> AllErrors(this ModelStateDictionary modelState)
    {
        var result = new List<Error>();
        var erroneousFields = modelState.Where(ms => ms.Value.Errors.Any())
                                        .Select(x => new { x.Key, x.Value.Errors });

        foreach (var erroneousField in erroneousFields)
        {
            var fieldKey = erroneousField.Key;
            var fieldErrors = erroneousField.Errors
                               .Select(error => new Error(fieldKey, error.ErrorMessage));
            result.AddRange(fieldErrors);
        }

        return result;
    }
}

Option 2: Uses Query syntax. This solution is more compact but possible less readable.

public static IEnumerable<Error> AllErrors(this ModelStateDictionary modelState)
{
    var result = from ms in modelState
                 where ms.Value.Errors.Any()
                 let fieldKey = ms.Key
                 let errors = ms.Value.Errors
                 from error in errors
                 select new Error(fieldKey, error.ErrorMessage);

    return result;
}

Using the extension method in a controller

Using the extension method is simple

/**** IMPORTANT: Don't forget to add a using statement to use your extension method *****/

[HttpPost]
public ActionResult PostAction(MyViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        // Perform logic
        return new Content("success");
    }

    //set error status
    Response.StatusCode = 400;
    // Important for live environment.
    Response.TrySkipIisCustomErrors = true;

    var modelErrors = ModelState.AllErrors(); // <<<<<<<<< SEE HERE
    return Json(modelErrors);
}

Notice the Response.TrySkipIisCustomErrors = true; line.

This caught me out when I released this code to a live environment, as by default setting status code overwrites customErrors. See this stack overflow question for more information

Reading the errors

All that is left to do now is set up our javascript method to read our json result.

$.post(postUrl, postData)
...
.fail(function (error) {
    var response = JSON.parse(error.responseText);
    for (var i = 0; i < response.length; i++) {
        var error = response[i];
        var fieldKey = error.Key;
        var message = error.Message;
        // apply custom logic with field keys and messages
        console.log(fieldKey + ': ' + message);
    }
}

I hope you find this helpful, please tweet this article using the links above or comment using the form below.

Comments 11

Roman says: 3531 days ago

my recent comment probably had some code in it the blog system didn't like...

John says: 3531 days ago

Ah, I see. Well thanks for the feedback anyway. I'll update the post now.

shalim says: 3410 days ago

var modelErrors = ModelState.AllErrors(); // <<<<<<<<< SEE HERE

I got error on above line. I think, I need to put a parameter inside AllErrors(). How can i fix it.

Hal says: 3408 days ago

@shalim you probably just need to ass a "using" statement so that your extension is seen in the controller. using MyExtensions; or something like that

Hal says: 3408 days ago

the only change I had to make was in the javascript console.Log to console.log... but good tip. I like this solution MUCH better than the junk I was doing.

John says: 3405 days ago

Hi @shalim,

I think @Hal is right in that you are probably missing the using statement.

John says: 3405 days ago

@Hal,

Thanks for helping @shalim out. I will update the post to change the console.Log() code

Rob says: 3291 days ago

Hi John,

How do I link the captured errors to display in fields, is there a generic way to display the errors in the @Html.ValidationMessageFor fields for the corresponding @Html.EditorFor etc...

Thanks,

John says: 3290 days ago

Hi Rob,

Thanks for getting in touch.

I don't know that there is a generic way but if you can make a correlation between the field name and validation message html (by adding a custom class that contains the field key) you should be able to grab the element using a selector that you can then add the message to it.

// REMOVED FROM ABOVE: console.log(fieldKey + &#39;: &#39; + message);
$(&#39;.&#39; + fiedKey + &#39;-error-message).html(message);

If this is not helpful, let me know and I'll look a bit deeper.

Thanks again.
John

Ninu Prem says: 2611 days ago

Last couple of hours i am searching how to get a key i have added using AddModelError to find which key throws the error. Based on that i need to show error icon on that tab. I got lot of solution to read the error message using the key. But i want to loop in that and need to find the key which i gave and display the errormessage with that icon in that tab using jquery.

Using your code i can implement that. Thanks a lot for sharing your code.

Carol says: 2605 days ago

The response doesn't need to be JSON.parse again as it is already in JSON.

Leave a message...

18 Apr
2024