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

Published: (February 3, 2026 at 08:02 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

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

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/else chaos
  • 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.

Back to Blog

Related posts

Read more »