Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
Feb 7, 2021

Web API Validation

Автор:
Manikanta Pattigulla
Источник:
Просмотров:
1672

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 2

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

public class Product : IValidatableObject
{
    public int Id
    {
        get;
        set;
    }
    public string Name
    {
        get;
        set;
    }
    public string Description
    {
        get;
        set;
    }
    public double Price
    {
        get;
        set;
    }
    public IEnumerable <ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Math.Abs(Price) < 0)
        {
            yield return new ValidationResult("InvalidPrice");
        }
    }
}
 

Step 3

Controller uses ModelState.IsValid to validate the model.

public IHttpActionResult Post(Product product)
{    
    if (ModelState.IsValid)
    {   
        // Do something with the product (not shown).   
        return Ok();   
    }
    else
    {   
        return BadRequest();   
    }   
}

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.

public class ValidateModelStateFilter : ActionFilterAttribute
{    
    public override void OnActionExecuting(HttpActionContext actionContext)
    {   
        if (!actionContext.ModelState.IsValid)
        {   
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);   
        }   
    }   
}

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

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>  
    {  
        /// <summary>  
        /// Validator rules for Product  
        /// </summary>  
        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)  
        {  
            // Web API routes  
            config.MapHttpAttributeRoutes();  
              
            // Add Custom validation filters  
            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  
{  
    /// <summary>  
    /// Controller class for Products  
    /// </summary>  
    public class ProductsController : ApiController  
    {  
        /// <summary>  
        /// Get List of all products  
        /// </summary>  
        /// <returns></returns>  
        /// <remarks>  
        /// Get List of all products  
        /// </remarks>  
        [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();  
            }  
        }    
 
        /// <summary>  
        /// Get a product  
        /// </summary>  
        /// <returns></returns>  
        /// <remarks>  
        /// Get a product by given product id   
        /// </remarks>  
        [Route("api/v1/Products/{productId}")]  
        public IHttpActionResult Get(int productId)  
        {  
            //Product Id is validated on ValidateModelStateFilter class  
            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();  
            }  
        }  
 
        /// <summary>  
        /// Create a Product  
        /// </summary>  
        /// <param name="product"></param>  
        /// <returns></returns>  
        /// <remarks>  
        /// Create a product into Databse  
        /// </remarks>  
        [Route("api/v1/Products")]  
        public IHttpActionResult Post(Product product)  
        {  
            //product object is validated on ValidateModelStateFilter class  
            try  
            {  
                //Call Data base Repository to insert product into DB  
                return Ok();  
            }  
            catch  
            {  
                return InternalServerError();  
            }  
        }  
 
        /// <summary>  
        /// Update the existing product  
        /// </summary>  
        /// <param name="product"></param>  
        /// <returns></returns>  
        /// <remarks>  
        /// Update the existing product  
        /// </remarks>  
        [Route("api/v1/Products")]  
        public IHttpActionResult Put(Product product)  
        {  
            try  
            {  
                //Logic for implementing update the product on Databse  
                return Ok();  
            }  
            catch  
            {  
                return InternalServerError();  
            }  
        }  
 
        /// <summary>  
        /// Delete the product from Databse  
        /// </summary>  
        /// <param name="productId"></param>  
        /// <returns></returns>  
        /// <remarks>  
        /// Delete the product from Databse  
        /// </remarks>  
        [Route("api/v1/Products/{productId}")]  
        public IHttpActionResult Delete(int productId)  
        {  
            try  
            {  
                //Logic for implementing delete the product from Databse  
                return Ok();  
            }  
            catch  
            {  
                return InternalServerError();  
            }  
        }  
    }  
}

6. Testing Controller Actions

  • Post Method
    Giving an empty description for the product to add. Fluent validation gives you a bad result.


     
  • Giving correct data.


     
  • Get the products.


     
  • Get a product by product Id.


     
  • 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.

public bool SchemaValidation()  
{  
    string schemaJson = @"{ 
        'properties': { 
            'Name': { 
                'type': 'string' 
            }, 
            'Surname': {
                'type': 'string' 
            } 
        }, 
        'type': 'object', 
        'additionalProperties': false 
    }"; 
   
    var schema = JsonSchema.Parse(schemaJson); 
   
    string jsondata = @"{ 
        'Name': 'Manikanta',  
        'Surname': 'Pattigulla'  
    }"; 
    var jsonObject = JObject.Parse(jsondata); 
   
    // Validate schema 
    return jsonObject.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).

Похожее
Apr 24, 2022
Author: HungryWolf
What is MediatR? Why do we need it? And How to use it? Mediator Pattern - The mediator pattern ensures that objects do not interact directly instead of through a mediator. It reduces coupling between objects which makes it easy...
Jan 2, 2023
Author: Jaydeep Patil
In this article, we are going to discuss the working of CQRS and MediatR patterns and step-by-step implementation using .NET Core 6 Web API. Agenda Introduction of CQRS Pattern When to use CQRS MediatR Step-by-step Implementation Prerequisites Visual Studio 2022...
Dec 5, 2022
Author: Jaydeep Patil
We are going to discuss Caching in .NET Core and how it works. So, we look at the following things one by one. Introduction of Caching. What is Cache. Types of cache. Cache Implementation. So, let’s start one by one....
Mar 12, 2021
Author: Bruno Joaquim
Exceptions are part of any software and can happen any time even where we are absolutely sure that nothing can go wrong. Exceptions are inevitable, we all agree with that. We should always deal with them in our applications to...
Написать сообщение
Тип
Почта
Имя
*Сообщение