Handle ModelState Validation in ASP.NET Web API
I was wondering how I can achieve model validation with ASP.NET Web API. I have my model like so:
public class Enquiry
{
[Key]
public int EnquiryId { get; set; }
[Required]
public DateTime EnquiryDate { get; set; }
[Required]
public string CustomerAccountNumber { get; set; }
[Required]
public string ContactName { get; set; }
}
I then have a Post action in my API Controller:
public void Post(Enquiry enquiry)
{
enquiry.EnquiryDate = DateTime.Now;
context.DaybookEnquiries.Add(enquiry);
context.SaveChanges();
}
How do I add if(ModelState.IsValid)
and then handle the error message to pass down to the user?
For separation of concern, I would suggest you use action filter for model validation, so you don't need to care much how to do validation in your api controller:
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace System.Web.Http.Filters
{
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}
Maybe not what you were looking for, but perhaps nice for someone to know:
If you are using .net Web Api 2 you could just do the following:
if (!ModelState.IsValid)
return BadRequest();
Depending on the model errors, you get this result:
{
Message: "The request is invalid."
ModelState: {
model.PropertyA: [
"The PropertyA field is required."
],
model.PropertyB: [
"The PropertyB field is required."
]
}
}
Like this, for example:
public HttpResponseMessage Post(Person person)
{
if (ModelState.IsValid)
{
PersonDB.Add(person);
return Request.CreateResponse(HttpStatusCode.Created, person);
}
else
{
// the code below should probably be refactored into a GetModelErrors
// method on your BaseApiController or something like that
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
}
}
This will return a response like this (assuming JSON, but same basic principle for XML):
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)
["A value is required.","The field First is required.","Some custom errorm essage."]
You can of course construct your error object/list any way you like, for example adding field names, field id's etc.
Even if it's a "one way" Ajax call like a POST of a new entity, you should still return something to the caller - something that indicates whether or not the request was successful. Imagine a site where your user will add some info about themselves via an AJAX POST request. What if the information they have tried to entered isn't valid - how will they know if their Save action was successful or not?
The best way to do this is using Good Old HTTP Status Codes like 200 OK
and so on. That way your JavaScript can properly handle failures using the correct callbacks (error, success etc).
Here's a nice tutorial on a more advanced version of this method, using an ActionFilter and jQuery: http://asp.net/web-api/videos/getting-started/custom-validation
Or, if you are looking for simple collection of errors for your apps.. here is my implementation of this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
var errors = new List<string>();
foreach (var state in modelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
var response = new { errors = errors };
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
}
}
Error Message Response will look like:
{ "errors": [ "Please enter a valid phone number (7+ more digits)", "Please enter a valid e-mail address" ] }