ASP.NET Web API and Handling ModelState Validation

How to handle ModelState Validation errors in ASP.NET Web API with an Action Filter and HttpError object
11 September 2012
4 minutes read

Related Posts

@amirrajan, who thinks that Web API sucks :), wanted to see how model validation works in ASP.NET Web API. Instead of responding him directly, I decided to blog about it. In his post, he indicated that we shouldn’t use ASP.NET Web API because it has usability issues but I am sure that he hasn’t taken the time to really understand the whole framework. It is really obvious that the blog post is written by someone who doesn’t know much about the framework. This is not a bad thing. You don’t have to know about every single technology but if *you* express that the technology bad, you better know about it. Anyway, this is not the topic.

ASP.NET Web API supports validation attributes from .NET Data Annotations out of the box and same ModelState concept which is present all across the ASP.NET is applicable here as well. You can inspect the ModelState property to see the errors inside your controller action but I bet that you want to just terminate the request and return back a error response if the ModelState is invalid. To do that, you just need to create and register an action filer as below:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class InvalidModelStateFilterAttribute : ActionFilterAttribute {

    public override void OnActionExecuting(HttpActionContext actionContext) {

        if (!actionContext.ModelState.IsValid) {

            actionContext.Response = actionContext.Request.CreateErrorResponse(
                HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

This is one time code and you don’t need to write this over and over again. CreateErrorResponse extension method for HttpRequestMessage uses HttpError class under the covers to create a consistent looking error response. The key point here is that the content negotiation will be handled here as well. the conneg will be handled according to the content negotiator service that you registered. If you don’t register one, the default one (DefaultContentNegotiator) will kick in which will be fine for 90% of the time.

I created a tiny controller which simply exposes two GET and one POST endpoint:

public class CarsController : ApiController {

    private readonly CarsContext _carsCtx = new CarsContext();

    public IEnumerable<Car> GetCars() {

        return _carsCtx.All;
    }

    public Car GetCar(int id) {

        Tuple<bool, Car> carResult = _carsCtx.GetSingle(id);
        if (!carResult.Item1) {

            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        return carResult.Item2;
    }

    public HttpResponseMessage PostCar(Car car) {

        var addedCar = _carsCtx.Add(car);
        var response = Request.CreateResponse(HttpStatusCode.Created, car);
        response.Headers.Location = new Uri(
            Url.Link("DefaultApiRoute", new { id = addedCar.Id }));

        return response;
    }
}

Here is what our Car object looks like with the validation attributes:

public class Car {

    public int Id { get; set; }

    [Required]
    public string Make { get; set; }

    [Required]
    public string Model { get; set; }
    public int Year { get; set; }

    [Range(0, 200000)]
    public float Price { get; set; }
}

And I registered the filter globally inside the Application_Start method:

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    config.Routes.MapHttpRoute(
        "DefaultApiRoute",
        "api/{controller}/{id}",
        new { id = RouteParameter.Optional }
    );

    config.Filters.Add(new InvalidModelStateFilterAttribute());
}

Let’s retrieve the list of cars first:

image

Let’s add a new car:

image

When we make a new request, it is now on the list:

image

Now, let’s try to add a new car again but this time, our object won’t fit the requirements and we should see the error response:

image

Here is the better look of the error message that we got:

image

Here is the plain text version of this message:

{
    "Message": "The request is invalid.",
    "ModelState": { 
        "car": [
            "Required property 'Make' not found in JSON. Path '', line 1, position 57."
        ],
        "car.Make" : [
            "The Make field is required."
        ], 
        "car.Price": [
            "The field Price must be between 0 and 200000."
        ]
    }
}

This doesn’t have to be in this format. You can play with the ModelState and iterate over the error messages to simply create your own error message format.

To be honest, this is no magic and this, by itself, doesn’t make ASP.NET Web API super awesome but please don’t make accusations on a technology without playing with it first at least enough amount of time to grasp the big picture. Otherwise, IMO, it would be a huge disrespect for the people who made the technology happen.