DEV Community

Cover image for FluentValidation in ASP.NET Core: Why One Validator per Request Is the Real Best Practice
Karuppasamy Pandian
Karuppasamy Pandian

Posted on

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

In modern .NET Web APIs, validation is not just about checking nulls or ranges. It is part of your API contract and one of the most critical layers that protects your system from bad data.

FluentValidation has become the industry standard for handling validation in a clean, testable, and maintainable way.

In this article, let's see below:

  • What FluentValidation is
  • How to implement it in ASP.NET Core
  • Why a “generic validator” is a bad idea
  • Why one request = one validator is the correct pattern

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; } 
Enter fullscreen mode Exit fullscreen mode

You define rules in a separate validator class:

RuleFor(x => x.Email)
    .NotEmpty()
    .EmailAddress(); 

Enter fullscreen mode Exit fullscreen mode

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 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

  1. Install Packages
dotnet add package FluentValidation
dotnet add package FluentValidation.AspNetCore 
Enter fullscreen mode Exit fullscreen mode
  1. Create a Request DTO
public class CreateUserRequest
{
    public string FirstName { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
} 

Enter fullscreen mode Exit fullscreen mode

This is a Request DTO — not your database entity.

  1. Create the Validator
public class CreateUserRequestValidator 
    : AbstractValidator<CreateUserRequest>
{
    public CreateUserRequestValidator()
    {
        RuleFor(x => x.FirstName)
            .NotEmpty()
            .MinimumLength(3);

        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress();

        RuleFor(x => x.Age)
            .InclusiveBetween(18, 60);
    }
} 

Enter fullscreen mode Exit fullscreen mode
  1. Register FluentValidation
builder.Services.AddControllers()
    .AddFluentValidationAutoValidation();

builder.Services.AddValidatorsFromAssemblyContaining<CreateUserRequestValidator>(); 

Enter fullscreen mode Exit fullscreen mode

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.

Here’s why:

  • Every request has different business rules
  • Generic validators become God classes
  • Rules turn into if/else chaos
  • You lose type safety
  • You break single responsibility

Validation is not generic — it is use-case specific.

The Industry Standard Pattern

CreateUserRequest      → CreateUserRequestValidator
UpdateUserRequest      → UpdateUserRequestValidator
CreateOrderRequest     → CreateOrderRequestValidator 
Enter fullscreen mode Exit fullscreen mode

Each API contract gets its own validator.

This gives you:

  • Clear rules per use case
  • Easy unit testing
  • Zero duplication
  • Clean controllers
  • Scalable architecture

Sharing Rules Without Duplication
You can still reuse logic safely.

Rule Extensions

public static class ValidationExtensions
{
    public static IRuleBuilderOptions<T, string> ValidEmail<T>(
        this IRuleBuilder<T, string> rule)
    {
        return rule.NotEmpty().EmailAddress();
    }
} 
Enter fullscreen mode Exit fullscreen mode

Usage:

RuleFor(x => x.Email).ValidEmail(); 
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)