How do you get a list of all ModelState error messages? I found this code to get all the keys: ( Returning a list of keys with ModelState errors)
var errorKeys = (from item in ModelState
where item.Value.Errors.Any()
select item.Key).ToList();
But how would I get the error messages as a IList or IQueryable?
I could go:
foreach (var key in errorKeys)
{
string msg = ModelState[error].Errors[0].ErrorMessage;
errorList.Add(msg);
}
But thats doing it manually - surely there is a way to do it using LINQ? The .ErrorMessage property is so far down the chain that I don't know how to write the LINQ...
You can put anything you want to inside the select
clause:
var errorList = (from item in ModelState
where item.Value.Errors.Any()
select item.Value.Errors[0].ErrorMessage).ToList();
EDIT: You can extract multiple errors into separate list items by adding a from
clause, like this:
var errorList = (from item in ModelState.Values
from error in item.Errors
select error.ErrorMessage).ToList();
Or:
var errorList = ModelState.Values.SelectMany(m => m.Errors)
.Select(e => e.ErrorMessage)
.ToList();
2nd EDIT: You're looking for a Dictionary<string, string[]>
:
var errorList = ModelState.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
Here is the full implementation with all the pieces put together:
First create an extension method:
public static class ModelStateHelper
{
public static IEnumerable Errors(this ModelStateDictionary modelState)
{
if (!modelState.IsValid)
{
return modelState.ToDictionary(kvp => kvp.Key,
kvp => kvp.Value.Errors
.Select(e => e.ErrorMessage).ToArray())
.Where(m => m.Value.Any());
}
return null;
}
}
Then call that extension method and return the errors from the controller action (if any) as json:
if (!ModelState.IsValid)
{
return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
}
And then finally, show those errors on the clientside (in jquery.validation style, but can be easily changed to any other style)
function DisplayErrors(errors) {
for (var i = 0; i < errors.length; i++) {
$("<label for='" + errors[i].Key + "' class='error'></label>")
.html(errors[i].Value[0]).appendTo($("input#" + errors[i].Key).parent());
}
}
.Where(m => m.Value.Count() > 0)
could also be written as .Where(m => m.Value.Any())
.
I like to use Hashtable
here, so that I get JSON object with properties as keys and errors as value in form of string array.
var errors = new Hashtable();
foreach (var pair in ModelState)
{
if (pair.Value.Errors.Count > 0)
{
errors[pair.Key] = pair.Value.Errors.Select(error => error.ErrorMessage).ToList();
}
}
return Json(new { success = false, errors });
This way you get following response:
{
"success":false,
"errors":{
"Phone":[
"The Phone field is required."
]
}
}
There are lots of different ways to do this that all work. Here is now I do it...
if (ModelState.IsValid)
{
return Json("Success");
}
else
{
return Json(ModelState.Values.SelectMany(x => x.Errors));
}
BadRequest(ModelState)
and it will serialiaze it into JSON for you.
The easiest way to do this is to just return a BadRequest
with the ModelState itself:
For example on a PUT
:
[HttpPut]
public async Task<IHttpActionResult> UpdateAsync(Update update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// perform the update
return StatusCode(HttpStatusCode.NoContent);
}
If we use data annotations on e.g. a mobile number, like this, in the Update
class:
public class Update {
[StringLength(22, MinimumLength = 8)]
[RegularExpression(@"^\d{8}$|^00\d{6,20}$|^\+\d{6,20}$")]
public string MobileNumber { get; set; }
}
This will return the following on an invalid request:
{
"Message": "The request is invalid.",
"ModelState": {
"update.MobileNumber": [
"The field MobileNumber must match the regular expression '^\\d{8}$|^00\\d{6,20}$|^\\+\\d{6,20}$'.",
"The field MobileNumber must be a string with a minimum length of 8 and a maximum length of 22."
]
}
}
Simple way achieve this by using built-in functionality
[HttpPost]
public IActionResult Post([FromBody]CreateDoctorInput createDoctorInput) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
//do something
}
@JK it helped me a lot but why not:
public class ErrorDetail {
public string fieldName = "";
public string[] messageList = null;
}
if (!modelState.IsValid)
{
var errorListAux = (from m in modelState
where m.Value.Errors.Count() > 0
select
new ErrorDetail
{
fieldName = m.Key,
errorList = (from msg in m.Value.Errors
select msg.ErrorMessage).ToArray()
})
.AsEnumerable()
.ToDictionary(v => v.fieldName, v => v);
return errorListAux;
}
Take a look at System.Web.Http.Results.OkNegotiatedContentResult.
It converts whatever you throw into it to JSON.
So I did this
var errorList = ModelState.ToDictionary(kvp => kvp.Key.Replace("model.", ""), kvp => kvp.Value.Errors[0].ErrorMessage);
return Ok(errorList);
This resulted in:
{
"Email":"The Email field is not a valid e-mail address."
}
I am yet to check what happens when there is more than one error for each field but the point is the OkNegoriatedContentResult is brilliant!
Got the linq/lambda idea from @SLaks
ToDictionary is an Enumerable extension found in System.Linq packaged in the System.Web.Extensions dll http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx. Here's what the complete class looks like for me.
using System.Collections;
using System.Web.Mvc;
using System.Linq;
namespace MyNamespace
{
public static class ModelStateExtensions
{
public static IEnumerable Errors(this ModelStateDictionary modelState)
{
if (!modelState.IsValid)
{
return modelState.ToDictionary(kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()).Where(m => m.Value.Count() > 0);
}
return null;
}
}
}
Why not return the original ModelState
object to the client, and then use jQuery to read the values. To me it looks much simpler, and uses the common data structure (.net's ModelState
)
to return the ModelState
as Json, simply pass it to Json class constructor (works with ANY object)
C#:
return Json(ModelState);
js:
var message = "";
if (e.response.length > 0) {
$.each(e.response, function(i, fieldItem) {
$.each(fieldItem.Value.Errors, function(j, errItem) {
message += errItem.ErrorMessage;
});
message += "\n";
});
alert(message);
}
Variation with return type instead of returning IEnumerable
public static class ModelStateHelper
{
public static IEnumerable<KeyValuePair<string, string[]>> Errors(this ModelStateDictionary modelState)
{
if (!modelState.IsValid)
{
return modelState
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray())
.Where(m => m.Value.Any());
}
return null;
}
}
I ran into the same hurdle, wanting to control my 400 Bad Request output format but not wanting to get my hands dirty serializing the guts of ModelState
. I would up using the sealed (but public, thankfully) SerializableError class.
var errorDetails = new SerializableError(ModelState);
var errorResponse = new YourCustomResponseType
{
ModelValidationErrors = errorDetails,
LogMessages = new []
{
new LogMessage("Error", "Invalid model - see modelValidationErrors for detail")
}
};
return BadRequest(errorResponse);
Where YourCustomResponseType
might look like this:
public class YourCustomResponseType
{
public LogMessage[] LogMessages { get; set; }
public Dictionary<string, object> ModelValidationErrors { get; set; }
}
SerializableError
is a Dictionary<string, object>
so this works out nicely. Your response might look like this:
{
"logMessages": [
{
"category": "Error",
"message": "Invalid model - see modelValidationErrors for detail"
}
],
"modelValidationErrors": {
"aSettingsType.someEnumField": [
"The input was not valid."
]
}
}
I made and extension that returns string with seperator " " (you can use your own):
public static string GetFullErrorMessage(this ModelStateDictionary modelState) {
var messages = new List<string>();
foreach (var entry in modelState) {
foreach (var error in entry.Value.Errors)
messages.Add(error.ErrorMessage);
}
return String.Join(" ", messages);
}
You can use middleware for this:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var result = new ValidationFailedResult(context.ModelState);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
return result;
};
});
and ValidationFailedResult:
public class ValidationFailedResult : ObjectResult
{
public ValidationFailedResult(ModelStateDictionary modelState)
: base(new ErrorResponse(modelState.Keys
.SelectMany(key => modelState[key].Errors.Select(x => new ApplicationError(StatusCodes.Status422UnprocessableEntity, key, x.ErrorMessage)))
.ToList()))
{
StatusCode = StatusCodes.Status422UnprocessableEntity; //change the http status code to 422.
}
}
and ApplicationError:
public class ApplicationError
{
public ApplicationError(int code, string title, string detail)
{
Code=code;
Title=title;
Detail=detail;
}
public int Code { get; set; }
public string Title { get; set; }
public string Detail { get; set; }
public override string ToString()
{
return $"Status:{Code} Title:{Title} Detail:{Detail}";
}
}
The result looks like this:
{
"errors": [
{
"code": 422,
"title": "$",
"detail": "'\"' is invalid after a value. Expected either ',', '}', or ']'. Path: $ | LineNumber: 7 | BytePositionInLine: 1."
},
{
"code": 422,
"title": "input",
"detail": "The input field is required."
}
]
}
List<ErrorList> Errors = new List<ErrorList>();
//test errors.
var modelStateErrors = this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors);
foreach (var x in modelStateErrors)
{
var errorInfo = new ErrorList()
{
ErrorMessage = x.ErrorMessage
};
Errors.Add(errorInfo);
}
if you use jsonresult then return
return Json(Errors);
or you can simply return the modelStateErrors, I havent tried it. What I did is assign the Errors collection to my ViewModel and then loop it..In this case I can return my Errors via json. I have a class/model, I wanted to get the source/key but I'm still trying to figure it out.
public class ErrorList
{
public string ErrorMessage;
}
Success story sharing
.Where(kvp => kvp.Value.Errors.Count > 0)
Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
you should usevar errorList = modelState.Where(elem => elem.Value.Errors.Any()) .ToDictionary( kvp => kvp.Key, kvp => kvp.Value.Errors.Select(e => string.IsNullOrEmpty(e.ErrorMessage) ? e.Exception.Message : e.ErrorMessage).ToArray());
Otherwise you won't have the ExceptionMessages