Blog Archives

Validating your model with Web API

One of the great things about ASP.NET 4.5’s Web API is that it’s built on the same (or similar) principles as MVC. This means that you get a lot of cool things out of your API controllers from MVC – like Action Filters.

While building my first Web API controller, I wanted to ensure that a creation or an update of an item was only done if that item was valid. I also wanted to pass any validation errors back to the client. This looks quite difficult at first because the Put and Post functions on an ApiController can’t return a result. Action Filters to the rescue!

With a simple action filter attribute, you can ensure that your models are validated and the errors are returned in a simple format to the client by decorating the appropriate methods.

Note: Code also available on Gist – https://gist.github.com/1920999

public class ValidateFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var modelState = actionExecutedContext.ActionContext.ModelState;
        if (!modelState.IsValid)
        {
            var errors = modelState
                .Where(s => s.Value.Errors.Count > 0)
                .Select(s => new KeyValuePair<string, string>(s.Key, s.Value.Errors.First().ErrorMessage))
                .ToArray();

            actionExecutedContext.Result = new HttpResponseMessage<KeyValuePair<string, string>[]>(
                errors,
                HttpStatusCode.BadRequest
            );
        }
    base.OnActionExecuted(actionExecutedContext);
}

Now, for the controller implementation…

public class TodoApiController : ApiController
{
    private BetterMobileSpaContext db = new BetterMobileSpaContext();

    // GET /api/todoapi
    public IEnumerable<TodoItem> Get()
    {
        return db.TodoItems.ToList();
    }

    // GET /api/todoapi/5
    public TodoItem Get(int id)
    {
        return db.TodoItems.Find(id);
    }

    // POST /api/todoapi
    [ValidateFilter]
    public void Post(TodoItem value)
    {
        if (!ModelState.IsValid) return;

        db.TodoItems.Add(value);
        db.SaveChanges();
    }

    // PUT /api/todoapi/5
    [ValidateFilter]
    public void Put(int id, TodoItem value)
    {
        if (!ModelState.IsValid) return;

        db.Entry(value).State = EntityState.Modified;
        db.SaveChanges();
    }

    // DELETE /api/todoapi/5
    public void Delete(int id)
    {
        TodoItem todoitem = db.TodoItems.Find(id);
        db.TodoItems.Remove(todoitem);
        db.SaveChanges();
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

Now a Create (PUT) and an Update (POST) request will validate the model before invoking the appropriate action. Calling Create from using a JSON request with an invalid model would look something like this:

Request

Method: PUT

Body: { Title: ”, IsDone: false }

Response

Status Code: 400 (Bad Request)

Body: [{ “key”: “Title”, “value”: “The Title field is required” }]

Using a valid model will simply result in a 200 (OK) response with no body.

NOTE: while writing this post I stumbled on these two blog posts that did the same thing and adapted my code:

EDIT 2012-02-27: Updated code to replace ValidationError type with KeyValuePair and changed filter to be after action execution so controller can perform any extra validation.