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

Web API Validation

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

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

Похожее
Mar 19, 2021
Author: Mukesh Murugan
In this article, We will talk about Onion Architecture In ASP.NET Core and it’s advantages. We will also together build a WebApi that follows a variant of Onion Architecture so that we get to see why it is important to...
Feb 7, 2021
Author: Manikanta Pattigulla
What is a helper page?Helper Page enables you to see the list of Web API endpoints, so that the consumers can easily understand what HTTP method will do.What is the use of helper page?Basically, if you work on traditional Web...
Jan 18, 2023
Author: Jay
Aha! There is always something intimidating about Dependency Injection. I distinctly remember a couple of years ago, telling a .Net recruiter for a contract project, that,“I had no idea what DI is”He immediately stopped talking to me. It’s just one...
Aug 23, 2022
Author: Luis Rodrigues
Suppose we are building a web api that contains a route to send notification messages to other systems.For security matters, before sending any notification message, we need to provide some credentials to these systems to they accept our messages.However, these...
Написать сообщение
Почта
Имя
*Сообщение


© 1999–2024 WebDynamics
1980–... Sergey Drozdov
Area of interests: .NET Framework | .NET Core | C# | ASP.NET | Windows Forms | WPF | HTML5 | CSS3 | jQuery | AJAX | Angular | React | MS SQL Server | Transact-SQL | ADO.NET | Entity Framework | IIS | OOP | OOA | OOD | WCF | WPF | MSMQ | MVC | MVP | MVVM | Design Patterns | Enterprise Architecture | Scrum | Kanban