Web API Validation

Overview

Validation is a very crucial part of Web API implementation. Regardless of what kind of Web API you are building, the bottom line is validating a request before processing it. The common thing I will do in validation is null checks, string checks and custom validation rules. Here, I'll explain about what will be the best way to validate the requests and how important it is.

Things to consider when implementing Web API

  • Validate everything
    Each and every request should validate before processing it whether an Action method is GET, POST, PUT or DELETE.

  • Don't assume about the data you’re receiving
    The data should always be assumed to be bad, until it’s been through some kind of validation process.

  • Validation as a future-proof quality check
    Validated data is more likely to be future-proof for your Web API functionality.

  • Clean code is Important.

Validation techniques

The ways given below help you to validate the data for Web API:

1. Model Validation

Model in MVC is basically a representation of our data structure. If you validate this data initially, then everything is good for processing. Web API has Model Binding and Model Validation support. The techniques given below will be used for the validation.

  • Data annotation is a technique, which can be applied on a model class for an ASP.NET Web API Application to validate the data and handle validation errors. It provides a pretty easy way to enable property-level validation logic within your Model layer. ASP.NET MVC 2 includes support for DataAnnotation attributes. Microsoft published a great article on this.
  • IValidatableObject interface
    Data annotation enables us to do properly level validation. How about class level validation?
    How to do class-level validation methods on model objects for some custom rules? The answer is IValidatableObject interface. IValidatableObject interface to implement custom validation rules on a Product model class. ASP.NET MVC 3 included support for IValidatableObject interface.

Implementation of IValidatableObject interface

Step 1
 
Inherited IvalidatableObject interface for Model class.

                              ASP.NET
         Step 2 

         Now, implement Validate method to write your custom rules to validate the model.

  1. publicclassProduct: IValidatableObject {  
  2.     publicintId {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     publicstringName {  
  7.         get;  
  8.         set;  
  9.     }  
  10.     publicstringDescription {  
  11.         get;  
  12.         set;  
  13.     }  
  14.     publicdoublePrice {  
  15.         get;  
  16.         set;  
  17.     }  
  18.     publicIEnumerable < ValidationResult > Validate(ValidationContextvalidationContext) {  
  19.         if (Math.Abs(Price) < 0) {  
  20.             yieldreturnnewValidationResult("InvalidPrice");  
  21.         }  
  22.     }  
  23. }   

 Step 3

Controller uses ModelState.IsValid to validate the model.

  1. publicIHttpActionResultPost(Productproduct) {  
  2.     if (ModelState.IsValid) {  
  3.         //Dosomethingwiththeproduct(notshown).  
  4.         returnOk();  
  5.     } else {  
  6.         returnBadRequest();  
  7.     }  
  8. }   
         Step 4
         
         (Customized Validation with Action Filter):

To avoid having to check for model state in every Put/Post action method of every controller, we can generalize it by creating a custom action filter.

  1. publicclassValidateModelStateFilter: ActionFilterAttribute {  
  2.     publicoverridevoidOnActionExecuting(HttpActionContextactionContext) {  
  3.         if (!actionContext.ModelState.IsValid) {  
  4.             actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);  
  5.         }  
  6.     }  
  7. }   
 2. Fluent Validation

It is an open source, which uses validation library for .NET that uses a fluent interface and lambda expressions for building validation rules. This is a  very popular validation tool, light weight and it supports all kinds of custom validation rules.

Please follow the steps given below to implement fluent validation on Web API:

1. Install NuGet package

Install-Package FluentValidation.WebAPI -Version 6.4.0 or above.

2. Modle Class 
  1. namespace ProductsApi.Models  
  2. {  
  3.     [Validator(typeof(ProductValidator))]  
  4.     public class Product  
  5.     {  
  6.         public int Id { get; set; }  
  7.   
  8.         public string Name { get; set; }  
  9.   
  10.         public string Description { get; set; }  
  11.   
  12.         public double Price { get; set; }  
  13.     }  
  14. }   
 3. Product Validator
 
All model validation rules will be defined in this validator class. 
  1. namespace ProductsApi.BusinessServices  
  2. {  
  3.     public class ProductValidator : AbstractValidator<Product>  
  4.     {  
  5.         /// <summary>  
  6.         /// Validator rules for Product  
  7.         /// </summary>  
  8.         public ProductValidator()  
  9.         {  
  10.             RuleFor(x => x.Id).GreaterThan(0).WithMessage("The Product ID must be at greather than 0.");  
  11.   
  12.             RuleFor(x => x.Name)  
  13.                 .NotEmpty()  
  14.                 .WithMessage("The Product Name cannot be blank.")  
  15.                 .Length(0, 100)  
  16.                 .WithMessage("The Product Name cannot be more than 100 characters.");  
  17.   
  18.             RuleFor(x => x.Description)  
  19.                 .NotEmpty()  
  20.                 .WithMessage("The Product Description must be at least 150 characters long.");  
  21.   
  22.             RuleFor(x => x.Price).GreaterThan(0).WithMessage("The Product Price must be at greather than 0.");  
  23.         }  
  24.     }  
  25. }   
4. Validation Action Filter

An action filter consists of the logic, which runs directly before or directly after an Action method runs. You can use action filters for logging, authentication, output caching, Validations or other tasks.

You implement an action filter as an attribute, which is inherited from the ActionFilterAttribute class. You override the OnActionExecuting method, if you want your logic to run before the Action Method. You override the OnActionExecuted method, if you want your logic to run after the Action method. After you define an action filter, you can use the attribute to mark any action methods, which you want the filter to apply to. 

  1. namespace ProductsApi  
  2. {  
  3.     public class ValidateModelStateFilter: ActionFilterAttribute  
  4.     {  
  5.         public override void OnActionExecuting(HttpActionContext actionContext)  
  6.         {  
  7.             if (!actionContext.ModelState.IsValid)  
  8.             {  
  9.                 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);  
  10.             }  
  11.         }  
  12.     }  
  13. }  

ASP.NET

Configuration custom filters in web.config file are given.

  1. namespace ProductsApi  
  2. {  
  3.     public static class WebApiConfig  
  4.     {  
  5.         public static void Register(HttpConfiguration config)  
  6.         {  
  7.             // Web API routes  
  8.             config.MapHttpAttributeRoutes();  
  9.               
  10.             // Add Custom validation filters  
  11.             config.Filters.Add(new ValidateModelStateFilter());  
  12.             FluentValidationModelValidatorProvider.Configure(config);  
  13.   
  14.             config.Routes.MapHttpRoute(  
  15.                 name: "DefaultApi",  
  16.                 routeTemplate: "api/{controller}/{id}",  
  17.                 defaults: new { id = RouteParameter.Optional }  
  18.             );  
  19.         }  
  20.     }  
  21. }   
5. Controller
 
The controller has GET, POST, PUT and DELETE actions. ProductValidator will be called before executing each Action method. In this way, all validation rules will be at one place. 
  1. namespace ProductsApi.Controllers  
  2. {  
  3.     /// <summary>  
  4.     /// Controller class for Products  
  5.     /// </summary>  
  6.     public class ProductsController : ApiController  
  7.     {  
  8.         /// <summary>  
  9.         /// Get List of all products  
  10.         /// </summary>  
  11.         /// <returns></returns>  
  12.         /// <remarks>  
  13.         /// Get List of all products  
  14.         /// </remarks>  
  15.         [Route("api/v1/Products")]  
  16.         public IHttpActionResult Get()  
  17.         {  
  18.             try  
  19.             {  
  20.                 List<Product> productList = new List<Product>  
  21.                 {  
  22.                     new Product {Id = 1,Name = "Apple S7", Description = "Description about product Apple S7", Price = 607.99},  
  23.                     new Product {Id = 2,Name = "Apple S6", Description = "Description about product Apple S6", Price = 507.99},  
  24.                     new Product {Id = 3,Name = "Apple S5", Description = "Description about product Apple S5", Price = 407.99}  
  25.                 };  
  26.                 return Ok(productList);  
  27.             }  
  28.             catch  
  29.             {  
  30.                 return InternalServerError();  
  31.             }  
  32.         }  
  33.   
  34.   
  35.         /// <summary>  
  36.         /// Get a product  
  37.         /// </summary>  
  38.         /// <returns></returns>  
  39.         /// <remarks>  
  40.         /// Get a product by given product id   
  41.         /// </remarks>  
  42.         [Route("api/v1/Products/{productId}")]  
  43.         public IHttpActionResult Get(int productId)  
  44.         {  
  45.             //Product Id is validated on ValidateModelStateFilter class  
  46.             try  
  47.             {  
  48.                 List<Product> productList = new List<Product>  
  49.                 {  
  50.                     new Product {Id = 1,Name = "Apple S7", Description = "Description about product Apple S7", Price = 607.99},  
  51.                     new Product {Id = 2,Name = "Apple S6", Description = "Description about product Apple S6", Price = 507.99},  
  52.                     new Product {Id = 3,Name = "Apple S5", Description = "Description about product Apple S5", Price = 407.99}  
  53.                 };  
  54.                 Product product = productList.Where(p => p.Id.Equals(productId)).FirstOrDefault();  
  55.                 return Ok(product);  
  56.             }  
  57.             catch  
  58.             {  
  59.                 return InternalServerError();  
  60.             }  
  61.         }  
  62.   
  63.         /// <summary>  
  64.         /// Create a Product  
  65.         /// </summary>  
  66.         /// <param name="product"></param>  
  67.         /// <returns></returns>  
  68.         /// <remarks>  
  69.         /// Create a product into Databse  
  70.         /// </remarks>  
  71.         [Route("api/v1/Products")]  
  72.         public IHttpActionResult Post(Product product)  
  73.         {  
  74.             //product object is validated on ValidateModelStateFilter class  
  75.             try  
  76.             {  
  77.                 //Call Data base Repository to insert product into DB  
  78.                 return Ok();  
  79.             }  
  80.             catch  
  81.             {  
  82.                 return InternalServerError();  
  83.             }  
  84.         }  
  85.   
  86.         /// <summary>  
  87.         /// Update the existing product  
  88.         /// </summary>  
  89.         /// <param name="product"></param>  
  90.         /// <returns></returns>  
  91.         /// <remarks>  
  92.         /// Update the existing product  
  93.         /// </remarks>  
  94.         [Route("api/v1/Products")]  
  95.         public IHttpActionResult Put(Product product)  
  96.         {  
  97.             try  
  98.             {  
  99.                 //Logic for implementing update the product on Databse  
  100.                 return Ok();  
  101.             }  
  102.             catch  
  103.             {  
  104.                 return InternalServerError();  
  105.             }  
  106.         }  
  107.   
  108.         /// <summary>  
  109.         /// Delete the product from Databse  
  110.         /// </summary>  
  111.         /// <param name="productId"></param>  
  112.         /// <returns></returns>  
  113.         /// <remarks>  
  114.         /// Delete the product from Databse  
  115.         /// </remarks>  
  116.         [Route("api/v1/Products/{productId}")]  
  117.         public IHttpActionResult Delete(int productId)  
  118.         {  
  119.             try  
  120.             {  
  121.                 //Logic for implementing delete the product from Databse  
  122.                 return Ok();  
  123.             }  
  124.             catch  
  125.             {  
  126.                 return InternalServerError();  
  127.             }  
  128.         }  
  129.     }  
  130. }   
6. Testing Controller Actions
  • Post Method
    Giving an empty description for the product to add. Fluent validation gives you a bad result. 

    ASP.NET

  • Giving correct data.

    ASP.NET

  • Get the products.

    ASP.NET

  • Get a product by product Id.

    ASP.NET
  • 3. JSON Schema Validation
    Schema validation comes into the picture when you are using dynamic or string to accept the request for the actions or the other scenarios.

JSON schema is used to validate the structure and the data types of a piece of JSON. “additionalProperties” property is used to check the keys present in JSON or not. 

  1. public bool SchemaValidation()  
  2. {  
  3. strings chemaJson=@"{  
  4. 'properties':{  
  5. 'Name':{  
  6. 'type':'string'  
  7. },  
  8. 'Surname':{  
  9. 'type':'string'  
  10. }  
  11. },  
  12. 'type':'object',  
  13. 'additionalProperties':false  
  14. }";  
  15.    
  16. varschema=JsonSchema.Parse(schemaJson);  
  17.    
  18. stringjsondata=@"{  
  19.         'Name':'Manikanta',  
  20.         'Surname':'Pattigulla'  
  21. }";  
  22. varjsonObject=JObject.Parse(jsondata);  
  23.    
  24. //ValidateSchema  
  25. returnjsonObject.IsValid(schema);  
  26. }   

 Conclusion

As I explained above, all the three validations are good enough. You need to choose what will be the best way to validate your model or request, as per your requirements. Basically, I am a fan of Fluent Validation because of all types of rules, which it allows ( like regular expressions).