FluentValidation

fluent validation article header

Throughout my career as a developer, I’ve built, maintained, and extended hundreds of .NET applications. While each application and context presents its own unique challenges, there’s no reason to face them without having some tools up your sleeve 🙂

Just as an explorer packs their favorite tools in their backpack, developers can leverage established libraries to handle common cross-cutting concerns, such as validations.

Regardless of the application we’re building, we’ll inevitably encounter numerous validations – numeric checks, string validations, and much more. In this case, our ace in the hole is FluentValidation (documentation here), a tool you won’t be able to live without once you’ve used it.

In today’s article, we’ll explore how to use this library and how it can benefit our daily projects.

What is FluentValidation and What Does it Do?

FluentValidation is an open-source .NET library that allows you to define validation rules for your classes in an elegant and readable way using a fluent interface (hence the name). Instead of using traditional validation attributes like [Required] or [StringLength], FluentValidation lets you write validation rules with a LINQ-like syntax.

Installation is straightforward – simply install the appropriate NuGet package for your project:

ShellScript
Install-Package FluentValidation

//------------------------------

Install-Package FluentValidation.AspNetCore

Validators

The library revolves around validators – classes where we centralize all validation logic for our classes.

Let’s say we have a class representing a product:

C#
public class Product
{
    public string Code { get; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

We want to add some classic validation rules. Let’s create a class called ProductValidator that implements the AbstractValidator<T> interface, where T is our product.

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    
  }
}

FluentValidation provides a wide range of built-in validation elements, though we can always develop our own custom methods.

Regardless of the type of check, we’ll add them in the constructor, in this case ProductValidator. Let’s verify that the Name property isn’t empty:

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    RuleFor(product => product.Name).NotEmpty();
  }
}

Executing validation rules is simple – just instantiate (or inject via dependency injection) our validator and call the Validate() function.

C#
FoodProductValidator fValidator = new FoodProductValidator();
ValidationResult fResult = fValidator.Validate(foodProduct);

Validation rules can be chained one after another. Let’s add to our example a check that at least 2 characters have been entered (and that it doesn’t exceed 250 characters):

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    RuleFor(product => product.Name)
      .NotEmpty()
      .Length(2, 250);
  }
}

We can also add specific validation messages for when validation rules fail:

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    RuleFor(product => product.Name)
      .NotEmpty()
      .Length(2, 250)
      .WithMessage("Name is required and length must be between 2 and 250 chars");
  }
}

This approach can be particularly useful for multilingual applications, where we could pass a string retrieved from a language dictionary in the .WithMessage function.

We can also split the message based on the specific validation that fails:

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    RuleFor(product => product.Name)
      .NotEmpty()
      .WithMessage("Name is required");
      .Length(2, 250)
      .WithMessage("Length must be between 2 and 250 chars");
  }
}

Now let’s implement a check on the Price property, verifying that it’s greater than 0:

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    RuleFor(product => product.Name)
      .NotEmpty()
      .WithMessage("Name is required");
      .Length(2, 250)
      .WithMessage("Length must be between 2 and 250 chars");
      
    RuleFor(product => product.Price)
      .GreaterThan(0)
      .WithMessage("Invalid price");  
  }
}

Finally, let’s add validations for the remaining properties:

C#
public class ProductValidator: AbstractValidator<Product>
{
  public ProductValidator()
  {
    RuleFor(product => product.Name)
      .NotEmpty()
      .WithMessage("Name is required");
      .Length(5, 250)
      .WithMessage("Length must be between 5 and 250 chars");
      
    RuleFor(product => product.Price)
      .GreaterThan(0)
      .WithMessage("Invalid price");

    RuleFor(product => product.Description)
        .NotNull()
        .NotEmpty()
        .WithMessage("Description is required");
    
    RuleFor(product => product.Code)
        .NotEmpty()
        .WithMessage("Invalid product code");
  }
}

Inheritance

The validation rules we’ve written can also be inherited. Let’s create a new class called FoodProduct that inherits from the Product class:

C#
public class FoodProduct : Product
{
    public List<ProductTypeGrouped> QuantityByExpiratioDate { get; set; }
    public new int Quantity { get; private set; }
    public bool IsFreshProduct { get; set; }
}

public class ProductTypeGrouped(string description, DateTime expiryDate, int quantity)
{
    public string TypeDescription { get; set; } = description;
    public DateTime ExpiryDate { get; set; } = expiryDate;
    public int Quantity { get; set; } = quantity;
}

We’ve added two new properties and overridden Quantity to manage quantities based on expiration dates (in the QuantityByExpiratioDate dictionary).

To inherit validation rules written in ProductValidator, we simply use the syntax RuleFor(p => p).SetValidator(new ProductValidator()); at the beginning of our validation function:

C#
public class FoodProductValidator : AbstractValidator<FoodProduct>
{
  public FoodProductValidator()
  {
      RuleFor(p => p).SetValidator(new ProductValidator());
  }
}

In this situation, we might want to override some checks, such as the Name property validations. Nothing could be simpler – just rewrite the rule as we did before:

C#
public class FoodProductValidator : AbstractValidator<FoodProduct>
{
  RuleFor(p => p).SetValidator(new ProductValidator());

  RuleFor(x => x.Name)
      .Length(10, 50)  //--> Previous lenght: between 5 and 250
      .WithMessage("Invalid name for this food");
}

As you can see, writing validation checks is extremely simple, especially for those already familiar with LINQ syntax.

Custom validators

The default checks work great for all standard cases we face daily, but each project brings complexities that require specific, case-by-case validation. In these cases, we can write custom validation functions using .Must().

In this example, I’ve implemented a check that verifies the code prefix based on the .IsFreshProduct property:

C#
    public class FoodProductValidator : AbstractValidator<FoodProduct>
    {
        public FoodProductValidator()
        {
            RuleFor(p => p).SetValidator(new ProductValidator());

            RuleFor(x => x.Name)
                .Length(3, 250)
                .WithMessage("Invalid name for this food");

            //Code FR_XXX if fresh food
            //Code FP_XXX if not fresh food
            RuleFor(x => x.Code)
                .Must((item, list) =>
                {
                    if (item.IsFreshProduct)
                    {
                        return item.Code.StartsWith("FR_");
                    }
                    else
                    {
                        return item.Code.StartsWith("FP_");
                    }
                })
                //Regular expression to match code format "XX_000"
                .Matches("[A-Z]{2}_\\d{3}");

        }
    }

There’s actually a more elegant way to write custom validation by creating a reusable method, similar to LINQ extension methods. Let’s move the validation logic into a static method:

C#
public static class ProductCustomValidator
{
  public static IRuleBuilderOptions<T, TElement> CheckFoodProductCode<T, TElement>(this IRuleBuilder<T, TElement> ruleBuilder, string regexCodeFormat) where TElement : FoodProduct
  {
      //validation here
  }
}
C#
public static class ProductCustomValidator
{
    public static IRuleBuilderOptions<T, TElement> CheckIfProductCodeHasRightFormat<T, TElement>(this IRuleBuilder<T, TElement> ruleBuilder, string regexCodeFormat) where TElement : FoodProduct
    {
        Regex r = new Regex(regexCodeFormat);
        return ruleBuilder
            .NotNull()
            .NotEmpty()
            //Check format with regular expression (passed as param)
            .Must((p) => 
            { 
                return r.IsMatch(p.Code); 
            })
            //Check initial letters
            .Must((list) =>
            {
                if (list.IsFreshProduct)
                {
                    return list.Code.StartsWith("FR_");
                }
                else
                {
                    return list.Code.StartsWith("FP_");
                }
            })
            .WithMessage("Invalid product code!");
    }
}

Our validation class will then become:

C#
    public class FoodProductValidator : AbstractValidator<FoodProduct>
    {
        public FoodProductValidator()
        {
            RuleFor(p => p).SetValidator(new ProductValidator());

            RuleFor(x => x.Name)
                .Length(3, 250)
                .WithMessage("Invalid name for this food");

            RuleFor(p => p).CheckIfProductCodeHasRightFormat("[A-Z]{2}_\\d{3}");

        }
    }

The advantages are the same as discussed in my article on LINQ extension methods: we centralize the validation logic in a single, reusable location, and code readability improves significantly.

List validations

Often our DTOs have properties that are actually lists, and fortunately, FluentValidation allows us to work with this type of data too! Let’s look at an example of how to apply validation to the QuantityByExpirationDate property.

Let’s add a check to verify that there are no expired products:

C#
public class FoodProductValidator : AbstractValidator<FoodProduct>
{
    public FoodProductValidator()
    {
        RuleFor(p => p).SetValidator(new ProductValidator());

        RuleFor(x => x.Name)
            .Length(3, 250)
            .WithMessage("Invalid name for this food");

        RuleFor(p => p).CheckIfProductCodeHasRightFormat("[A-Z]{2}_\\d{3}");

        RuleForEach(productWithExpiry => productWithExpiry.QuantityByExpirationDate)
            //Error if expired
            .Must(pg => pg.ExpiryDate >= DateTime.Now)
            .WithMessage((product, pg) => $"Product #{product.Code} '{pg.TypeDescription}' is expired!");
            
    }
}

All logic written within RuleForEach will be applied to each element in the list.

Let’s continue our journey and add another piece: asynchronous validations.

Asynchronous validations

I’ve often needed to validate data based on API calls: verifying document validity using APIs or retrieving user validity information through an asynchronous method. All these cases involve making asynchronous calls, and once again, the library provides several asynchronous methods we can use (e.g., MustAsync or WhenAsync).

Let’s continue with our FoodProduct class validations. For this example, we’ll call a hypothetical API that checks product availability in the warehouse. We’ll write the logic in our FoodProductValidator class, with the difference being that we’ll need to pass the HTTP client as a parameter.

Note:

NOTE

In a real implementation, I wouldn’t directly pass the HttpClient parameter – I would probably inject an interface using Dependency Injection or use an HttpClientFactory pattern to better manage the application’s connection pool.

C#
    public class FoodProductValidator : AbstractValidator<FoodProduct>
    {
        public FoodProductValidator(HttpClient client)
        {
            HttpClient _httpClient = client;

            RuleFor(p => p).SetValidator(new ProductValidator());

            RuleFor(x => x.Name)
                .Length(3, 250)
                .WithMessage("Invalid name for this food");

            RuleFor(p => p).CheckIfProductCodeHasRightFormat("[A-Z]{2}_\\d{3}");

            RuleForEach(productWithExpiry => productWithExpiry.QuantityByExpiratioDate)
                //Error if expired
                .Must(pg => pg.ExpiryDate >= DateTime.Now)
                .WithSeverity(Severity.Error)
                .WithMessage((product, pg) => $"Product #{product.Code} '{pg.TypeDescription}' is expired!");

            RuleFor(p => p.Code).MustAsync(async (productCode, cancellation) =>
            {
                bool isVailable = false;
                var response = await _httpClient.GetAsync($"/api/v1/products/{productCode}/is-available");
                //[...] check response
                return !isVailable;
            }).WithMessage("Product not available");
        }
    }

IMPORTANT Since we’ve used an asynchronous rule in our validation class, we must ALWAYS use the ValidateAsync() function, or an exception will be thrown.

C#
FoodProductValidator fValidator = new FoodProductValidator(client);
ValidationResult fResult = await fValidator.ValidateAsync(foodProduct);

Conclusion

FluentValidation proves to be an extremely versatile and powerful tool for handling validations in the .NET environment. The library offers numerous advantages:

  • An intuitive fluent syntax familiar to developers who already know LINQ
  • The ability to inherit validation rules
  • Support for custom validations through custom methods
  • The capability to handle validations on lists and collections
  • Support for asynchronous operations, allowing validations that require API calls or I/O operations

Thanks to its flexibility and ease of use, FluentValidation proves to be an indispensable “ace up the sleeve” for any .NET developer looking to implement robust and maintainable validations in their projects. The centralization of validation logic in dedicated classes, along with the ability to reuse and compose rules, allows for writing cleaner, more testable, and easier-to-maintain code.

Share this article
Shareable URL
Prev Post

Hybrid Cache released!

Leave a Reply

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

Read next

Hybrid Cache released!

Hey devs! Great news – .NET 9.0.3 just dropped a few days ago, and with it comes the long-awaited official…
Hybrid cache released header image

LINQ Extension Method

At the 2024 edition of Overnet’s WPC conference, I attended an insightful talk about LINQ extension…

HashSet<T> in .NET 9

What is a HashSet<T>? In .NET, a HashSet<T> is a collection that implements an unordered set of unique…