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.
Step 2Now, implement Validate method to write your custom rules to validate the model.
- publicclassProduct: IValidatableObject {
- publicintId {
- get;
- set;
- }
- publicstringName {
- get;
- set;
- }
- publicstringDescription {
- get;
- set;
- }
- publicdoublePrice {
- get;
- set;
- }
- publicIEnumerable < ValidationResult > Validate(ValidationContextvalidationContext) {
- if (Math.Abs(Price) < 0) {
- yieldreturnnewValidationResult("InvalidPrice");
- }
- }
- }
Step 3
Controller uses ModelState.IsValid to validate the model.
- publicIHttpActionResultPost(Productproduct) {
- if (ModelState.IsValid) {
-
- returnOk();
- } else {
- returnBadRequest();
- }
- }
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.
- publicclassValidateModelStateFilter: ActionFilterAttribute {
- publicoverridevoidOnActionExecuting(HttpActionContextactionContext) {
- if (!actionContext.ModelState.IsValid) {
- actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
- }
- }
- }
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
- namespace ProductsApi.Models
- {
- [Validator(typeof(ProductValidator))]
- public class Product
- {
- public int Id { get; set; }
-
- public string Name { get; set; }
-
- public string Description { get; set; }
-
- public double Price { get; set; }
- }
- }
3. Product Validator
All model validation rules will be defined in this validator class.
- namespace ProductsApi.BusinessServices
- {
- public class ProductValidator : AbstractValidator<Product>
- {
-
-
-
- public ProductValidator()
- {
- RuleFor(x => x.Id).GreaterThan(0).WithMessage("The Product ID must be at greather than 0.");
-
- RuleFor(x => x.Name)
- .NotEmpty()
- .WithMessage("The Product Name cannot be blank.")
- .Length(0, 100)
- .WithMessage("The Product Name cannot be more than 100 characters.");
-
- RuleFor(x => x.Description)
- .NotEmpty()
- .WithMessage("The Product Description must be at least 150 characters long.");
-
- RuleFor(x => x.Price).GreaterThan(0).WithMessage("The Product Price must be at greather than 0.");
- }
- }
- }
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.
- namespace ProductsApi
- {
- public class ValidateModelStateFilter: ActionFilterAttribute
- {
- public override void OnActionExecuting(HttpActionContext actionContext)
- {
- if (!actionContext.ModelState.IsValid)
- {
- actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
- }
- }
- }
- }
Configuration custom filters in web.config file are given.
- namespace ProductsApi
- {
- public static class WebApiConfig
- {
- public static void Register(HttpConfiguration config)
- {
-
- config.MapHttpAttributeRoutes();
-
-
- config.Filters.Add(new ValidateModelStateFilter());
- FluentValidationModelValidatorProvider.Configure(config);
-
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{id}",
- defaults: new { id = RouteParameter.Optional }
- );
- }
- }
- }
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.
- namespace ProductsApi.Controllers
- {
-
-
-
- public class ProductsController : ApiController
- {
-
-
-
-
-
-
-
- [Route("api/v1/Products")]
- public IHttpActionResult Get()
- {
- try
- {
- List<Product> productList = new List<Product>
- {
- new Product {Id = 1,Name = "Apple S7", Description = "Description about product Apple S7", Price = 607.99},
- new Product {Id = 2,Name = "Apple S6", Description = "Description about product Apple S6", Price = 507.99},
- new Product {Id = 3,Name = "Apple S5", Description = "Description about product Apple S5", Price = 407.99}
- };
- return Ok(productList);
- }
- catch
- {
- return InternalServerError();
- }
- }
-
-
-
-
-
-
-
-
-
- [Route("api/v1/Products/{productId}")]
- public IHttpActionResult Get(int productId)
- {
-
- try
- {
- List<Product> productList = new List<Product>
- {
- new Product {Id = 1,Name = "Apple S7", Description = "Description about product Apple S7", Price = 607.99},
- new Product {Id = 2,Name = "Apple S6", Description = "Description about product Apple S6", Price = 507.99},
- new Product {Id = 3,Name = "Apple S5", Description = "Description about product Apple S5", Price = 407.99}
- };
- Product product = productList.Where(p => p.Id.Equals(productId)).FirstOrDefault();
- return Ok(product);
- }
- catch
- {
- return InternalServerError();
- }
- }
-
-
-
-
-
-
-
-
-
- [Route("api/v1/Products")]
- public IHttpActionResult Post(Product product)
- {
-
- try
- {
-
- return Ok();
- }
- catch
- {
- return InternalServerError();
- }
- }
-
-
-
-
-
-
-
-
-
- [Route("api/v1/Products")]
- public IHttpActionResult Put(Product product)
- {
- try
- {
-
- return Ok();
- }
- catch
- {
- return InternalServerError();
- }
- }
-
-
-
-
-
-
-
-
-
- [Route("api/v1/Products/{productId}")]
- public IHttpActionResult Delete(int productId)
- {
- try
- {
-
- return Ok();
- }
- catch
- {
- return InternalServerError();
- }
- }
- }
- }
6.
Testing Controller Actions
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.
- public bool SchemaValidation()
- {
- strings chemaJson=@"{
- 'properties':{
- 'Name':{
- 'type':'string'
- },
- 'Surname':{
- 'type':'string'
- }
- },
- 'type':'object',
- 'additionalProperties':false
- }";
-
- varschema=JsonSchema.Parse(schemaJson);
-
- stringjsondata=@"{
- 'Name':'Manikanta',
- 'Surname':'Pattigulla'
- }";
- varjsonObject=JObject.Parse(jsondata);
-
-
- returnjsonObject.IsValid(schema);
- }
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).