FluentValidation in ASP.NET Core: Why One Validator per Request Is the Real Best Practice
Source: Dev.to

What is FluentValidation?
FluentValidation is a popular .NET library that allows you to define validation rules using a fluent, strongly typed syntax instead of attributes or controller logic.
Instead of this:
[Required]
[EmailAddress]
public string Email { get; set; }
You define rules in a separate validator class:
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress();
This keeps your API models clean and moves validation into a dedicated validation layer.
Why Validation Should NOT Be in Controllers
Putting validation in controllers leads to:
- Duplicated logic
- Hard‑to‑test code
- Fat controllers
- Business rules leaking into the API layer
FluentValidation integrates directly into the ASP.NET Core pipeline, so validation runs automatically before your controller executes. If validation fails, the framework returns a 400 Bad Request — no extra code required in the controller.
How to Implement FluentValidation in ASP.NET Core
Install Packages
dotnet add package FluentValidation
dotnet add package FluentValidation.AspNetCore
Create a Request DTO
public class CreateUserRequest
{
public string FirstName { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
This is a request DTO — not your database entity.
Create the Validator
public class CreateUserRequestValidator : AbstractValidator
{
public CreateUserRequestValidator()
{
RuleFor(x => x.FirstName)
.NotEmpty()
.MinimumLength(3);
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress();
RuleFor(x => x.Age)
.InclusiveBetween(18, 60);
}
}
Register FluentValidation
builder.Services.AddControllers()
.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining();
That’s it. Every request is now validated automatically.
Why NOT Use a Generic Validator?
Many developers ask: “Can I create one generic validator for all requests?”
Short answer: No.
Reasons:
- Every request has different business rules
- Generic validators become God classes
- Rules turn into
if/elsechaos - You lose type safety
- You break the Single Responsibility Principle
Validation is not generic — it is use‑case specific.
The Industry Standard Pattern
CreateUserRequest → CreateUserRequestValidator
UpdateUserRequest → UpdateUserRequestValidator
CreateOrderRequest → CreateOrderRequestValidator
Each API contract gets its own validator, giving you:
- Clear rules per use case
- Easy unit testing
- Zero duplication
- Clean controllers
- Scalable architecture
Sharing Rules Without Duplication
Rule Extensions
public static class ValidationExtensions
{
public static IRuleBuilderOptions ValidEmail(
this IRuleBuilder rule)
{
return rule.NotEmpty().EmailAddress();
}
}
Usage
RuleFor(x => x.Email).ValidEmail();
This keeps validators clean and reusable without breaking architecture.
Final Thought
FluentValidation is not just a validation tool — it is a design decision. By using:
- Request DTOs
- One validator per request
- Automatic pipeline validation
you build APIs that are:
- Safer
- Cleaner
- Easier to test
- Easier to maintain
That is what real‑world, production‑grade .NET systems look like.