Implementing custom validation using IValidatableObject

I recently started Implementing custom validation using IValidatableObject in my DOT NET CORE application.

We often use data annotation for simple validation for the properties in our model class. I was wondering how to validate a particular property against some data in the database. I’m happy to know that IValidatableObject can be used to do any sort of validation.

In this post, I’m going to show how to make use of IValidatableObject to validate a property for different scenarios.

I have used DOT NET CORE Web API for this demo.

I have created a folder named, Validation and created an abstract class named AbstractValidatableObject. This class inherits IValidatableObject class and we can implement a common logic to asyn and async Validate methods as shown below.

public abstract class AbstractValidatableObject : IValidatableObject
    {

        public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            CancellationTokenSource source = new CancellationTokenSource();

            var task = ValidateAsync(validationContext, source.Token);

            Task.WaitAll(task);

            return task.Result;
        }

        public virtual Task<IEnumerable<ValidationResult>> ValidateAsync(ValidationContext validationContext, CancellationToken cancellation)
        {
            return Task.FromResult((IEnumerable<ValidationResult>)new List<ValidationResult>());
        }
    }

IValidatableObject Interface provides a way for an object to be invalidated.

It has a method named Validate(ValidationContext) that determines whether the specified object is valid.

Parameters

validationContextValidationContext – The validation context.

Returns

IEnumerable<ValidationResult> – A collection that holds failed-validation information.

For demonstration purposes, I have created a Model class named Employee and a service for Employee.

Model

public class Employee
    {
        public int EmployeeId { get; set; }
        public string Name { get; set; }
        public decimal Salary { get; set; }
        public int Age { get; set; }
   }

Implementing Service

using System.Threading.Tasks;

namespace ArticlesDemo.Service
{
    public interface IEmployeeService
    {
        Task IsEmployeeExist(int employeeId);
    }
}

using System.Threading.Tasks;

namespace ArticlesDemo.Service
{
    public class EmployeeService : IEmployeeService
    {
        public async Task IsEmployeeExist(int employeeId)
        {
            await Task.Delay(1000);
            if (employeeId > 1000 && employeeId < 1500)
                return true;
            else
                return false;
        }
    }
}

Implementing custom validation using IValidatableObject

I want to use different validation for Employee model class that can be validated whenever it was used.

I have four properties in this model, I'm using data annotation for Name property. The other three properties are validated in the ValidateAsync method which is from IValidatableObject Interface.

using ArticlesDemo.Service;
using ArticlesDemo.Validation;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;

namespace ArticlesDemo.Model
{
    public class Employee : AbstractValidatableObject
    {
        public int EmployeeId { get; set; }

        [MinLength(2, ErrorMessage = "Minimum 2 characters required"), MaxLength(50, ErrorMessage = "Maximum 50 characters allowed")]
        public string Name { get; set; }
        public decimal Salary { get; set; }
        public int Age { get; set; }


        public override async Task<IEnumerable<ValidationResult>> ValidateAsync(ValidationContext validationContext,
         CancellationToken cancellation)
        {
            var errors = new List<ValidationResult>();
           

            if (Salary < 10000)
                errors.Add(new ValidationResult("Salary cannot be less than $10,000", new[] { nameof(Salary) }));

            if (Age < 18)
                errors.Add(new ValidationResult($"Age: {Age} not allowed to work. Minimum age is 18 or more", new[] { nameof(Age) }));

            //get the required service injected
            var dbContext = validationContext.GetService<IEmployeeService>();

            // Database call through service for validation
            var isExist = await dbContext.IsEmployeeExist(EmployeeId);
            if (isExist)
            {
                errors.Add(new ValidationResult("EmployeeId exist", new[] { nameof(EmployeeId) }));
            }

            return errors;
        }
    }
}

As you have seen, there was a method in the IEmployeeService to check whether a given employee id exist in the system or not. We can even inject any service and validate the data against database as part of our validation.

Don't forget to add the IEmployeeService in the Startup class.

Startup Class

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IEmployeeService, EmployeeService>();
        }

Output Validation Message

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|87776fa6-4a037412bfcb49c8.",
    "errors": {
        "Age": [
            "Age: 17 not allowed to work. Minimum age is 18 or more"
        ],
        "Salary": [
            "Salary cannot be less than $10,000"
        ],
        "EmployeeId": [
            "EmployeeId exist"
        ]
    }
}

You may now use this errors property in the UI and show appropriate validation message.

Implementing custom validation using IValidatableObject
Implementing custom validation using IValidatableObject

Advantage of using this approach

There is one good advantage for using this approach in ASP NET CORE application. The call lands inside the controller's action method only when the validation is success.

Related Post

Conclusion

In this post, I showed Implementing custom validation using IValidatableObject in ASP.NET Core. That’s all from this post. If you have any questions or just want to chat with me, feel free to leave a comment below.

Leave a Reply

Your email address will not be published. Required fields are marked *