Fluent Validation in .NET 6

2022, Apr 04

Fluent Validation is a validation library available as a NuGet package, that can be easily implemented by developers. It uses a fluent API that leverages lambda expressions to validate rules.

What are the benefits of Fluent Validation?

  • It uses the Fail Fast principle1
  • Validation with errors, instead of exceptions.
  • Rules are centralized on validators.
  • Unit Testable

Nuget Packages

In this example we use FluentValidation.AspNetCore, but in Libraries FluentValidation is also an option.

Sample

The solution has a POST endpoint that allows users to submit a Product. The Fluent Validation is executed on the request, and in case it fails, it returns a BadRequest.

Solution

Program.cs

using FluentValidation.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddFluentValidation(conf =>
{
    conf.RegisterValidatorsFromAssembly(typeof(Program).Assembly);
    conf.AutomaticValidationEnabled = false;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

As the package is added, the AddFluentValidation extension is added to the Service Collection. It is necessary to register the validators using RegisterValidatorsFromAssembly. The AutomaticValidationEnabled was set to false, so we can control the validation when debugging the solution. If you don't do that, ASP.NET validation occurs automatically by default, at the time of model binding.

ProductViewModel.cs

public class ProductViewModel
{
    public string Name { get; set; }
    public string Sku { get; set; }
    public int Quantity { get; set; }
    public double Price { get; set; }
}

ProductViewModelValidator.cs

public class ProductViewModelValidator : AbstractValidator<ProductViewModel>
{
    public ProductViewModelValidator()
    {
        RuleFor(model => model.Name).NotNull().NotEmpty().WithMessage("Please specify a name");
        RuleFor(model => model.Sku).NotNull().NotEmpty().Length(3, 10);
        RuleFor(model => model.Quantity).GreaterThanOrEqualTo(0);
        RuleFor(model => model.Price).NotEqual(0).When(model => model.Quantity > 0)
            .WithMessage("Please specify a price");
    }
}

In here some validations were added to the ProductViewModel. We check whether the properties are not null, not empty, we specify error messages, having the minimal length of 3 and max length of 10, value is greater than or equal to zero, and also a property validation based on property value.

Looking at the controller, the validation happens within the POST method:

[ApiController]
[Route("[controller]")]
public class ProductController : Controller
{
    private readonly IValidator<ProductViewModel> _validator;

    public ProductController(IValidator<ProductViewModel> validator)
    {
        _validator = validator;
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] ProductViewModel model)
    {
        var validation = await _validator.ValidateAsync(model);

        if (!validation.IsValid)
        {
            return BadRequest(validation.Errors?.Select(e => new ValidationResult()
            {
                Code = e.ErrorCode,
                PropertyName = e.PropertyName,
                Message = e.ErrorMessage
            }));
        }

        return Ok();
    }
}

Running the solution

Run the solution and open Swagger to perform the POST:

POST missing quantity

Because Price was not provided when Quantity is greater than zero, an error message occurs:

Quantity error

The model is validated and the error message is displayed, as expected.

Check this sample in the PlayGoKids repository


  1. Fail Fast principle Fail Fast principle