Fluent Validations in ASP.NET Core

In this article we will implement  Fluent Validations using ASP.NET Core.

Most of you might have used Data Annotations for server-side model validations. We use data annotations to apply validations on public properties of the entity/model class so that when the view is submitted, these model validations are executed. 

Although Data Annotations are good, we have some limitations, e.g. the Data Annotations are bound to the properties so performing conditional validations is difficult. 

To implement conditional validations, we need to write logic in controller and execute it. This is where we need to think of an alternative to the Data Annotations. Fluent Validation is a cool solution we have for performing validations on model classes. 

Fluent Validation is a .NET library for building strongly-typed validation rules. It has support from .NET Framework v4.6.1+ and .NET Core v2.0 onwards. 

Fluent Validation has the following features:

1. It provides better control for implementing validation rules.
2. The conditional validations on different properties on the model classes is easy.
3. It provides separation of validation rules from the model classes.

Let's start implementing Fluent Validation in an ASP.NET Core 3.1 application. 

Step 1: Open Visual Studio 2019 and create a new ASP.NET Core Web Application. Name this application as Core_FluentValidations. Select the Web Application (Model-View-Controller) template. Once the project is created, add the Fluent Validation package in the project as shown in the Figure 1

Figure 1: Adding reference for the Fluent Validation package 

This package has classes using which we can implement fluent validations in ASP.NET Core. 

Step 2: In the Models folder, add a new class file, name this class file as PersonInfo.cs. In this class file we will add the Model class, Fluent Validator class and other data access classes. In the file, add code as shown in Listing 1: 
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using FluentValidation;

namespace Core_FluentValidations.Models
{
    public class Occupations
    {
        public string Name { get; set; }
    }
     
    public class PersonInfo
    {
        public int PersonId { get; set; }
        public string PersonName { get; set; }
        public string Address { get; set; }
        public int Age { get; set; }
        public string Email { get; set; }
        public int MobileNo { get; set; }
        public List Occupation { get; set; } = new List();
    }

    /// 
    /// The class used to define the validators
    /// 

    public class PersonInfoFluentVaidator : AbstractValidator
    {
        public PersonInfoFluentVaidator()
        {
            RuleFor(p => p.PersonId).NotNull().WithMessage("Person Id Cannot be null");
            RuleFor(p => p.PersonId).GreaterThan(0).WithMessage("Person Id must be greaer than 0");
            RuleFor(p => p.PersonName).NotEmpty().WithMessage("Person Name Cannot be empty");
            RuleFor(p => p.PersonName).Length(2,20).WithMessage("Person Name must be within 2 to 20 characters");
            RuleFor(p=>p.PersonName).Custom((name,context)=> {
                string pattern = @"\b[A-Z]\w*\b";
                Regex regEx = new Regex(pattern);
                if (!regEx.IsMatch(name))
                {
                    context.AddFailure("Person Name must start from Capital Letter");
                }
            });
            RuleFor(p => p.Address).NotEmpty().WithMessage("Address Cannot be empty");
            RuleFor(p => p.Address).Length(1, 100).WithMessage("Address must be within 1 to 100 characters");
            RuleFor(p => p.Age).GreaterThanOrEqualTo(18).WithMessage("Age must be greater than 18");
            RuleFor(p => p.Age).LessThanOrEqualTo(80).WithMessage("Age must be lesst than 80");
            RuleFor(p => p.Email).NotEmpty().WithMessage("Email can not be empty");
            RuleFor(p => p.Email).EmailAddress().WithMessage("Must be a valid Email");
            RuleFor(p => p.MobileNo).GreaterThan(0).WithMessage("Mobile number must be greater than 0");
            RuleFor(p => p.MobileNo).NotNull().WithMessage("Mobile no cannot be null");
            RuleFor(p => p.Occupation).Must(list =>
            list.Contains("Self-Employeed") || list.Contains("Employeed"))
                 .WithMessage("Occupation can either Self-Employeed or Employeed"); ;
        }
    }

    public class PersonInfoDb : List
    {
        public PersonInfoDb()
        {
            Add(new PersonInfo()
            {
                PersonId=101,
                PersonName="Mahesh",
                Address="PF, BV, Pune",
                Age=44,
                Email="mahesh@msit.com",
                MobileNo=188586784
            });
            Add(new PersonInfo()
            {
                PersonId = 102,
                PersonName = "Nilesh",
                Address = "VB, FP, Pune",
                Age = 42,
                Email = "nilesh@msit.com",
                MobileNo = 188286784
            });
        }
    }

    public class DataAccess
    {
        private readonly PersonInfoDb PersonDb;
        public DataAccess()
        {
            PersonDb = new PersonInfoDb();
        }
        public List GetPersons()
        {
            return PersonDb;
        }
        public List AddPerson(PersonInfo person)
        {
            PersonDb.Add(person);
            return PersonDb;
        }
    }
}


Listing 1: Model class, Fluent validator class and otehr data access classes 

The code in the listing 1 have following specification 

1. The PersonInfo class is an entity model class. This class contains various public properties to store person information. This class also has a property named Occupation, of the type List. The class Occupations is one more model class with the Name property and will be used by the controller to define list of occupations. 

2. The most important code in listing 1 is the PersonInfoFluentValidator class. This class is derived from AbstractValidator class. The AbstractValidator class is used to to define fluent validations on the model class properties. This is the base class which we use to define object validators. 

This class contains methods to perform synchronous and asynchronous validations. The AbstractValidator is generic class. In the listing 1, we passed PersonInfo model class as a generic parameter to this class. This means that the AbstractValidator class will write the validation rules on the properties of the PersonInfo class. 

3. The constructor of the PersonInfoFluentValidator uses the RuleFor() method to define validators on properties by accepting these properties as function predicate experssions. 

This method returns IRuleBuilder instance on which the validators can be defined. The IRuleBuilder is a multi-type generic interface as IRuleBuilder, where T is the type of which property that is being validated by defining rule and Property is the property from the type on which the rule is applied. 

Validations are applied using the developer friendly methods as follows:
- NotNull()
- GreaterThan()
- NotEmpty()
- Length()
- GreaterThanEqualTo() / LessThanEqualTo()
- EmailAddress() 
- Custom() - This method is used to define custom validation. In this case we are defining custom validations to make sure that the PersonName must start from uppercase character. 
- Must() - This method is used to define a specific type of custom validations, to make sure that the value of the property will be read based on the predicate. E.g. in the code we are making sure that the value of Occupations will be either Self-Employed or Employed.

4. The PersonInfoDb class is derived from the List class. This is used to contain default values for the PersonInfo. 

5. The DataAccess class contains method to read and write data in PersonInfoDb. 

Step 3: Once we have created a fluent validation class, we need to register this class in the ConfigureServices() method of the Startup class. Open the Startup.cs and add the line in the ConfigureService() method as shown in listing 2
services.AddControllersWithViews().AddFluentValidation(validator=> 
                validator
                .RegisterValidatorsFromAssemblyContaining()
            );
            services.AddScoped();

Listing2: Registering fluent validator class 

The important thing to note here is the AddFluentValidation() method. This method is used to define the fluent validation options using FluentValidationMvcOptions class. This class has RegisterValidatorsFromAssemblyContaining() method. This generic method accepts the fluent validator class as type parameter, in our case it is PersonInfoFluentValidator class which we have created in Step 2. 

The RegisterValidatorsFromAssemblyContaining() method registers the PersonInfoFluentVaidator class as Transient in the dependency container. The listing also contains code to register the DataAccess class in container. 

Step 4: Add a new empty MVC controller in the Controllers folder and name it as PersonInfoController. Add the code in this controller as shown in listing 3: 
public class PersonInfoController : Controller
    {
        private readonly DataAccess ds;

        public PersonInfoController(DataAccess ds)
        {
            this.ds = ds;
        }
        
        public IActionResult Index()
        {
            var persons = ds.GetPersons();
            return View(persons);
        }

        public IActionResult Create()
        {
            ViewData["Occupation"] = new List()
            {
                new Occupations(){ Name="Employeed"},
                new Occupations(){ Name = "Self-Employeed"}
            };
            return View(new PersonInfo());
        }

        [HttpPost]
        public IActionResult Create(PersonInfo person)
        {
            if (ModelState.IsValid)
            {
                var persons = ds.AddPerson(person);
                return RedirectToAction("Index");
            }
            ViewData["Occupation"] = new List()
            {
                new Occupations(){ Name="Employeed"},
                new Occupations(){ Name = "Self-Employeed"}
            };
            return View("Create", person);
        }
    }


Listing 3: The PersonInfoController 

As shown in listing 3, the Create() method defines the ViewData object that contains the list of occupations. We will be showing this data in the combobox on the Create view. The HttpPost Create method checks model validations using ModelState property. If model properties are invalid, then validation errors will be shown on the Create view. 

Step 5: Scaffold Index and Create views for the PersonInfo model. The HTML markup code of the Create.cshtml is shown in listing 4. 

@model PersonInfo
 
 @model PersonInfo

<h2>Create Person</h2>

<div class="container">
    <form asp-action="Create">
        <div class="form-group">
            <label asp-for="PersonId">PersonId</label>
            <input asp-for="PersonId" class="form-control" />
            <span class="alert-danger" asp-validation-for="PersonId"></span>
        </div>
        <div class="form-group">
            <label asp-for="PersonName">Person Name</label>
            <input asp-for="PersonName" class="form-control" />
            <span class="alert-danger" asp-validation-for="PersonName"></span>
        </div>
        <div class="form-group">
            <label asp-for="Address">Address</label>
            <textarea asp-for="Address" class="form-control"></textarea>
            <span class="alert-danger" asp-validation-for="Address"></span>
        </div>
        <div class="form-group">
            <label asp-for="Age">Age</label>
            <input asp-for="Age" class="form-control" />
            <span class="alert-danger" asp-validation-for="Age"></span>
        </div>
        <div class="form-group">
            <label asp-for="Email">Email</label>
            <input asp-for="Email" class="form-control" />
            <span class="alert-danger" asp-validation-for="Email"></span>
        </div>
        <div class="form-group">
            <label asp-for="MobileNo">Mobile No</label>
            <input asp-for="MobileNo" class="form-control" />
            <span class="alert-danger" asp-validation-for="MobileNo"></span>
        </div>
        <div class="form-group">
            <label asp-for="Occupation">Occupation</label>
            <select asp-for="Occupation" class="form-control"
                   asp-items='@new SelectList((IEnumerable<Occupations>)ViewData["Occupation"],"Name", "Name")'>
                <option>Select The Occupation</option>
            </select>
            <span class="alert-danger" asp-validation-for="Occupation"></span>
        </div>
        <div class="form-group">
            <input type="submit" value="Save" class="btn btn-success" />
        </div>
    </form>
</div> 
<br/>
<a asp-action="Index">Back to List</a>
 
Listing 4: The Create.cshtml 

Run the application and navigate to the following URL in the brower 

https://localhost:5001/PersonInfo/Create 

Enter a value in PersonName textbox that starts from lowercase character e.g. mah, Select the Occupation value from the Drop down and click on the Save button, the page will show validation messages as shown in figure 2

Figure 2: The Create.html with vaidation messages 

The view shows error messages which we have defined in the fluent validator class. 

Conclusion: The Fluent Validators in ASP.NET Core provides great control over writing validations on the model properties. Consider using fluent validations when you want to define custom or conditional validations on model class properties. Instead of using Data Annotations, the fluent validation approach makes the code more easy and maintainable.





About The Author

Mahesh Sabnis is a Microsoft MVP having over 18 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). He also blogs regularly at DotNetCurry.com. Follow him on twitter @maheshdotnet

No comments: