ASP.NET Web API - Screencast series Part 5: Custom Validation

We're continuing a six part series on ASP.NET Web API that accompanies the getting started screencast series. This is an introductory screencast series that walks through from File / New Project to some more advanced scenarios like Custom Validation and Authorization. The screencast videos are all short (3-5 minutes) and the sample code for the series is both available for download and browsable online. I did the screencasts, but the samples were written by the ASP.NET Web API team.

In Part 1 we looked at what ASP.NET Web API is, why you'd care, did the File / New Project thing, and did some basic HTTP testing using browser F12 developer tools.

In Part 2 we started to build up a sample that returns data from a repository in JSON format via GET methods.

In Part 3, we modified data on the server using DELETE and POST methods.

In Part 4, we extended on our simple querying methods form Part 2, adding in support for paging and querying.

In Part 5, we'll add on support for Data Annotation based validation using an Action Filter.

[Video and code on the ASP.NET site]

Adding Validation Rules using Data Annotations

To start with, we add some validation rules to our model class using Data Annotations - Required and StringLength in this case.

[Required] 
public string Text { get; set; } 
 
[Required] 
[StringLength(10, ErrorMessage = "Author is too long! This was validated on the server.")] 
public string Author { get; set; } 
 
[Required] 
public string Email { get; set; } 

Writing an Action Filter that Enforces Validation Rules

In ASP.NET MVC, that would be it - the validation rules are enforced in controller actions, and automatically passed along as HTML5 data- attributes where they're handled via unobtrusive jQuery validation in the browser. ASP.NET Web API doesn't directly enforce those validation rules without a little more work, though. I think that might be because handling validation errors in an HTTP API isn't something you'd want to do automatically. Who knows, maybe they'll add that in later just to make me look dumb(er). But for now, it takes a bit of work to enforce those validation rules.

Fortunately, by a little more work I mean about 10-15 lines of code. It's easy to hook this kind of thing up using an ASP.NET MVC action filter. Action filters are really powerful, as they allow you to modify how controller actions work using the following methods:

  • OnActionExecuting – This method is called before a controller action is executed.
  • OnActionExecuted – This method is called after a controller action is executed.
  • OnResultExecuting – This method is called before a controller action result is executed.
  • OnResultExecuted – This method is called after a controller action result is executed.

You can apply an action filter attribute on specific actions, on an entire controller class, or globally. There are a few built-in action filters to handle common cases like custom authorization or error handling, and you can extend them if you need to handle a custom scenario.

ASP.NET Web API uses that same extensibility model via Filters. If you want ASP.NET Web API to do something that's not built in, the hook for that is very often to use an action filter. As with ASP.NET Action Filters, you can either extend on any of the in-built filters ( ResultLimit, AuthorizationFilter, ExceptionFilter) or write a custom ActionFilterAttribute.

public class ValidationActionFilter : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(HttpActionContext context) 
    { 
        var modelState = context.ModelState; 
        if (!modelState.IsValid) 
        { 
            dynamic errors = new JsonObject(); 
            foreach (var key in modelState.Keys) 
            { 
                var state = modelState[key]; 
                if (state.Errors.Any()) 
                { 
                    errors[key] = state.Errors.First().ErrorMessage; 
                } 
            } 

            context.Response = new HttpResponseMessage<JsonValue>(errors, HttpStatusCode.BadRequest); 
        } 
    } 
}

This is an OnActionExecuting filter, so it executes before the Action. It takes in the context (note - this is a System.Web.Http.Controllers.HttpActionContext, not an System.Web.HttpContext), which gives it access to ModelState as well as other contextual information about the request. It runs any custom logic, and has the option of directly returning the response - as it does in case validation fails.

So, at a high level, we just need to check if the model fails validation, and if so, return an appropriate response.

1. Checking Data Annotation validation rules

As mentioned earlier in the series, ASP.NET Web API uses the same model binding system that's been in ASP.NET MVC for a while, so it already knows how to check validation rules from data annotations. That makes this step really easy - we just check the context.ModelState to see if it's valid. If it is, we're done - let the Action do its work and return the result. If it fails, go on to step 2.

2. Packaging up validation errors for the client

A single request can fail multiple validation rules, so to return useful information to the client we need to package up the error information. We could build up a structured, custom error results object, but we'd be serializing it to JSON when we were done, so why not just start there? ASP.NET Web API includes some very useful JSON classes, including JsonObject. JsonObject allows us to work with a C# dynamic that will be serialized as a JSON object very easily.

3. Returning a useful error error response

Finally, when we're done wrapping up all the validation errors, we return a response to the client. We can use an HttpResponseMessage<JsonValue> to both return the results and set the HTTP Status Code (HTTP 400 Bad Request) in one line:

context.Response = new HttpResponseMessage<JsonValue>(errors, HttpStatusCode.BadRequest); 

You'll remember from earlier in the series that HTTP Status Codes are very important to HTTP APIs, and that by always setting the appropriate status code our clients (be they JavaScript, .NET desktop clients, iOS devices, or aliens who natively speak HTTP) will be able to understand responses without reading a bunch of API documentation. If a client makes a bad request, we'll tell them it was a bad request, and send along validation errors in JSON format in case they want specifics.

Registering a Global Filter

As with ASP.NET MVC filters, ASP.NET Web API filters can be applied at whatever level of granularity you'd like - action, controller, or globally. In this case, we'd like the rules to be enforced on all actions in the application. We can do that by registering a global filter. That's a one-line change - we add a call in our Global.asax.cs Configure method to register the new filter:

public static void Configure(HttpConfiguration config) 
{ 
    config.Filters.Add(new ValidationActionFilter()); 
 
    var kernel = new StandardKernel(); 
    kernel.Bind<ICommentRepository>().ToConstant(new InitialData()); 
    config.ServiceResolver.SetResolver( 
        t => kernel.TryGet(t), 
        t => kernel.GetAll(t)); 
} 

Handling Validation Responses in a JavaScript Client

In this sample, we're working with a JavaScript client. Just for the sake of beating a dead horse a bit more, I'll remind you that JavaScript is a browser is just one possible client.

To add handle validation failures, we need to include a case for HTTP Status Code 400 in our $.ajax() jQuery call.

$.ajax({ 
    url: '/api/comments', 
    cache: false, 
    type: 'POST', 
    data: json, 
    contentType: 'application/json; charset=utf-8', 
    statusCode: { 
        201 /*Created*/: function (data) { 
            viewModel.comments.push(data); 
        }, 
        400 /* BadRequest */: function (jqxhr) { 
            var validationResult = $.parseJSON(jqxhr.responseText); 
            $.validator.unobtrusive.revalidate(form, validationResult); 
        } 
    } 
}); 

Break the rules? Talk to the HTTP 400.

So here's how this looks in action - filling out the form with an Author name that exceeds 10 characters now gets an HTTP 400 response.

2012-03-23 15h40_12

The error message shown above - "Author is too long! This was validated on the server." - is perhaps a little too smug, but is derived from the JSON error information in the request body:

2012-03-23 15h42_51

Onward!

That wraps up our look at supporting Data Annotation based validation using an Action Filter. In Part 6, we'll finish off the series with a look at Authorization using the built-in Authorize filter.

4 Comments

  • This is great. One question. If there are multiple server side validators only the last error message is displayed. I'm looking at the revalidate function and trying to figure out what's wrong but I'm not getting anywhere. Any ideas?

  • Further to my last comment. This actually completely breaks as soon as more than one server side validator is added. For example add a string length validator to the Text property and the validation doesn't work correctly.

  • I'm guessing that web api similar to mvc has model binder that triggers validation on binding. Say I don't want to use DataAnnotations and I have my own validation framework. How do I hook-up model binder to use my validator?

  • Cheap VPS or virtual private server is the ultimate solution to costly maintenance web servers. In fact, virtual private server is more advanced than shared hosting & is more like dedicated server, but to emphasize, at a much lower cost. The low cost of virtual private server is the significant difference between shared web-site hosting and dedicated server. A slightly advanced than shared website hosting and has the features of a dedicated server, but it is way cheaper than a dedicated server. The financial advantage of using virtual private server is not the only advantage it could give to its customers or users. Explained below are the additional advantages and disadvantages of using cheap virtual private servers.

    Advantages of Cheap VPS:
    The first thing that you could get from your individual virtual private server is the root access to your server. It means that you can have access to the root level of the hosting server. Thus, you have the ability to put in & configure any programs you require. Additionally, you can also host a limitless number of net sites through Apache's virtual hosts & manage them efficiently. Not only this, but you can also host other services, such as a mail server, an FTP server, or any type of server you want. You may even use VPS for file storage or as a backup for all of your files. Since VPS is isolated from other sites present on the physical server, it is secured that no harmful script or application used by other webmaster, that can harm your website.

    Disadvantages of Cheap VPS:
    There are definite disadvantages in using cheap VPS or virtual private servers. For, you cannot get managed servers. This means that in case you have no idea how to set up & configure your own VPS, it is a huge disadvantage. This disadvantage leads us to get another disadvantage, that is, you are solely responsible of all the installation, maintenance, security measures and updates on your VPS. Thus, in the event you do not possess the high-proficiency in using the VPS to control the working of the net site, the applications used, & the server resources skillfully, you will have a major issue & your VPS becomes unmanageable. Additionally, cheap VPS hosting designs might give you a whole operating process of your own to work with, you still share hardware resources with other VPS users on the host server. Therefore, in the event you are jogging intensive programs that need high performance, you may need to make use of other technique of website hosting, such as co-location or a dedicated server.

    Remember, the great features & capabilities of the dedicated server are available for pricey fees to you. So if your web-site does not need high finish performance, cheap VPS are ideal for you. They are economical, efficient and offer excellent benefits for your website. Therefore, cheap VPS or virtual private servers can be reliable, but since it on a budget plan, do not expect as much as you would from expensive VPS plans.

Comments have been disabled for this content.