Wednesday, January 2, 2013

MVC.Net 4 WebAPI Server Side Model Validation

My latest work is implementing a site using MVC.Net and the WebAPI interface.  I wanted to use the data annotations in my UI models to support server side validation of incoming data passed in via JSON or XML with POST requests to my WebAPI methods.  I found a lot of confusion when I was researching the proper method of implementing a custom filter for WebAPI to implement this type of validation.

To clear things up, it is important to realize that data annotations in your model are validated using unique namespaces for MVC controllers vs. Webapi controllers.  The syntax and naming conventions within these namespaces are very similar.  MVC controllers utilize use System.Web.MVC while WebAPI uses System.Web.Http.

To enable server side validation for MVC.Net WebAPI, you will need the following code:

   1:  using System.Collections.Generic;
   2:  using System.Net;
   3:  using System.Web.Http.Filters;
   4:  using System.Web.Http.Controllers;
   5:  using Newtonsoft.Json;
   6:  using System.Net.Http;
   7:   
   8:  namespace SampleMVC
   9:  {
  10:      public class ValidationActionFilter : ActionFilterAttribute
  11:      {
  12:   
  13:          /// <summary>
  14:          /// System.Web.Http.Filters implementation for model validation (WebAPI)
  15:          /// </summary>
  16:          /// <param name="actionContext"></param>
  17:          public override void OnActionExecuting(HttpActionContext actionContext)
  18:          {
  19:              base.OnActionExecuting(actionContext);
  20:   
  21:              var modelState = actionContext.ModelState;
  22:              if (!modelState.IsValid)
  23:              {
  24:                  List<string> errors = new List<string>();
  25:                  foreach (var value in modelState)
  26:                  {
  27:                      errors.Add(value.Value.Errors[0].ErrorMessage);
  28:                  }
  29:                  var response = new System.Net.Http.HttpResponseMessage(HttpStatusCode.BadRequest);
  30:                  response.Content = new StringContent(JsonConvert.SerializeObject(errors));
  31:   
  32:                  actionContext.Response = response;
  33:              }
  34:          }
  35:      }
  36:  }

This code executes for each action, reads the model state from the action context and prepares a list of validation errors to return in JSON format to the client.  An http status of BadRequest is returned along with the body containing the error details in JSON.  While this may not be the particular action you wish to take when an error is found and it may not be very RESTful, it shows an example of what could be done and how to read through the validation errors list.

To make this code execute you'll need to add these lines to your FilterConfig.cs file, don't forget to add a namespace reference for System.Web.Http.Filters:

   1:  public static void RegisterHttpFilters(HttpFilterCollection filters)
   2:  {
   3:       filters.Add(new ValidationActionFilter());
   4:  }

Be sure to add a registration in your Global.asax.cs file, the MVC controller filters are registered there, but not the webapi filters.

FilterConfig.RegisterHttpFilters(GlobalConfiguration.Configuration.Filters);

If you wish to add validation to a standard MVC.Net controller, the code is as follows.

   1:  using System.Web;
   2:  using System.Web.Mvc;
   3:   
   4:  namespace SampleMVC
   5:  {
   6:      public class ValidationActionFilterMvc : ActionFilterAttribute
   7:      {
   8:          /* MVC Controller implementation of model validation
   9:           * Uses System.Web.Mvc
  10:           * Lists of ActionResult Implementations found at:
  11:           * http://brendan.enrick.com/post/types-of-aspnet-mvc-3-action-results.aspx
  12:           * */
  13:          public override void OnActionExecuting(ActionExecutingContext filterContext)
  14:          {
  15:              base.OnActionExecuting(filterContext);
  16:              var modelState = filterContext.Controller.ViewData.ModelState;
  17:              if (!modelState.IsValid)
  18:              {
  19:                  var result = new HttpStatusCodeResult(HttpStatusCode.BadRequest, "ModelError");
  20:                  filterContext.Result = result;
  21:              }
  22:          }
  23:      }
  24:  }

The proper entry in your FilterConfig.cs for this validation filter is, of course this requires a namespace reference to System.Web.MVC:

   1:  public static void RegisterGlobalFilters(GlobalFilterCollection filters)
   2:  {
   3:      filters.Add(new HandleErrorAttribute());
   4:  }

The MVC validation code executes for each action the same as the webapi controller, but in this case MVC.Net provides a built in mechanism for returning http status codes.  I found a post linked to in the code that has a list of the implementations provided by Microsoft for the ActionResult class that allows you to return these status codes to the controller.  Of course you could iterate the validation errors just the same as was done in the WebAPI example if you chose to. Note the different usings here that are referencing the proper namespaces for this implementation versus the usings in the WebAPI sample.  The similar names in different namespaces are the source of confusion around these implementations.

2 comments: