Introduction
Data validation is a fundamental aspect of any application, ensuring that user inputs meet the required criteria before processing. FluentValidation is a powerful .NET library that simplifies the validation process while maintaining clean and maintainable code.
In this article, we will explore:
- Why FluentValidation?
- Setting up FluentValidation in .NET Core
- Writing custom validators
- Integrating FluentValidation with CQRS
- Handling localization for multilingual validation messages
Why FluentValidation?
Key Benefits:
✅ Separation of Concerns – Keeps validation logic separate from business rules.
✅ Improved Readability – Uses an expressive, fluent API for defining validation rules.
✅ Reusable & Maintainable – Eliminates repetitive validation logic across multiple components.
✅ Supports Complex Validations – Easily handle conditional and cross-field validations.
✅ Built-in Localization – Supports multi-language validation messages.
✅ Seamless Integration – Works well with ASP.NET Core and Dependency Injection.
1️⃣ Installing FluentValidation
To get started, install the FluentValidation package via NuGet:
Install-Package FluentValidation.AspNetCore
Then, register FluentValidation in Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Register FluentValidation
builder.Services.AddControllers();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<CarValidator>();
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
app.Run();
2️⃣ Creating a Validator Class
Let's define a Car
model and create a validator for it:
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public string VIN { get; set; }
public int Year { get; set; }
public decimal PricePerDay { get; set; }
}
Writing a Validator for the Car
Model
using FluentValidation;
public class CarValidator : AbstractValidator<Car>
{
public CarValidator()
{
RuleFor(car => car.Make)
.NotEmpty().WithMessage("Make is required")
.MaximumLength(50).WithMessage("Make cannot exceed 50 characters");
RuleFor(car => car.Model)
.NotEmpty().WithMessage("Model is required")
.MaximumLength(50).WithMessage("Model cannot exceed 50 characters");
RuleFor(car => car.VIN)
.NotEmpty().WithMessage("VIN is required")
.Matches("^[A-HJ-NPR-Z0-9]{17}$").WithMessage("VIN must be exactly 17 characters");
RuleFor(car => car.Year)
.InclusiveBetween(1886, DateTime.UtcNow.Year)
.WithMessage($"Year must be between 1886 and {DateTime.UtcNow.Year}");
RuleFor(car => car.PricePerDay)
.GreaterThan(0).WithMessage("Price per day must be greater than zero");
}
}
3️⃣ Using FluentValidation in Controllers
[ApiController]
[Route("api/cars")]
public class CarController : ControllerBase
{
[HttpPost]
public IActionResult CreateCar([FromBody] Car car)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok("Car successfully created");
}
}
With FluentValidation, validation happens automatically, and invalid requests return structured error messages.
4️⃣ Advanced: Integrating FluentValidation with CQRS
In a CQRS (Command Query Responsibility Segregation) architecture, FluentValidation helps validate commands before they reach the handler.
Define a CreateCarCommand
:
using MediatR;
public class CreateCarCommand : IRequest<int>
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
}
Create a Validator for the Command:
public class CreateCarCommandValidator : AbstractValidator<CreateCarCommand>
{
public CreateCarCommandValidator()
{
RuleFor(command => command.Make).NotEmpty().WithMessage("Make is required");
RuleFor(command => command.Model).NotEmpty().WithMessage("Model is required");
RuleFor(command => command.Year).InclusiveBetween(1886, DateTime.UtcNow.Year);
}
}
Register and Validate in a Handler:
public class CreateCarCommandHandler : IRequestHandler<CreateCarCommand, int>
{
public async Task<int> Handle(CreateCarCommand request, CancellationToken cancellationToken)
{
// Business logic for car creation
return await Task.FromResult(1);
}
}
Now, whenever CreateCarCommand
is executed, FluentValidation ensures the request is valid before reaching the handler.
5️⃣ Localization Support (Multi-language Validation Messages)
To support multiple languages, integrate a translation service:
RuleFor(car => car.Make)
.NotEmpty().WithMessage(_ => _translationService.GetTranslation("MakeRequired"));
Example Translation Service:
public interface ITranslationService
{
string GetTranslation(string key);
}
public class TranslationService : ITranslationService
{
private readonly Dictionary<string, string> _translations = new()
{
{ "MakeRequired", "La marque est requise" } // French Example
};
public string GetTranslation(string key)
{
return _translations.TryGetValue(key, out var value) ? value : key;
}
}
This way, validation messages can be translated dynamically based on the user's language.
Conclusion
FluentValidation is a powerful tool for managing input validation in .NET Core applications. It keeps validation logic clean, reusable, and maintainable while supporting complex business rules, CQRS integration, and localization.
🚀 Want More Advanced Logic?
Check out a full GitHub example integrating FluentValidation with CQRS:
🔗 GitHub – FluentValidation with CQRS
Are you using FluentValidation in your projects? Let’s discuss in the comments! 👇
Top comments (0)