Return all ModelState errors with field keys as json data in mvc
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.
my recent comment probably had some code in it the blog system didn't like...
Ah, I see. Well thanks for the feedback anyway. I'll update the post now.
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.
@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
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.
Hi @shalim,
I think @Hal is right in that you are probably missing the using
statement.
@Hal,
Thanks for helping @shalim out. I will update the post to change the console.Log()
code
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,
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 + ': ' + message);
$('.' + fiedKey + '-error-message).html(message);
If this is not helpful, let me know and I'll look a bit deeper.
Thanks again.
John
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.
The response doesn't need to be JSON.parse again as it is already in JSON.