CodeToClarity Logo
Published on ·.NET

How to Use FluentValidation in ASP.NET Core — Write Powerful and Clean Validations

Kishan KumarKishan Kumar

Learn how to use FluentValidation in ASP.NET Core to build clean and powerful validation logic. Understand why it’s better than Data Annotations with real examples and step-by-step setup.

If you’ve ever built an ASP.NET Core Web API, you already know how crucial it is to validate user input.
Most developers start with Data Annotations — it’s easy, quick, and built right into .NET. But as your project grows, it can start feeling messy and restrictive.

In this guide, we’ll see why Data Annotations aren’t always the best choice and how to use FluentValidation, a powerful library that gives you cleaner, more flexible, and maintainable validation in ASP.NET Core.


🚦 Why Validation Matters

Validation is one of the simplest yet most important steps in building secure, reliable applications. Without proper validation, even the most robust API can break or be exploited.
Here’s why validation is so essential:

  • 🧩 Data Integrity: Ensures user input follows required formats and constraints, maintaining consistent and accurate data in your system.
  • 🔒 Security: Prevents attacks like SQL Injection, XSS, or CSRF by sanitizing user inputs.
  • 💡 Better UX: Helps users fix mistakes faster by showing clear and instant feedback.
  • ⚙️ Business Logic Enforcement: Makes sure the data aligns with your business rules (like matching passwords or valid emails).

⚠️ Why Data Annotations Aren’t Always Ideal

Let’s start with what most developers already use — Data Annotations.

public class UserRegistrationRequest
{
    [Required(ErrorMessage = "First name is required.")]
    public string? FirstName { get; set; }

    [Required(ErrorMessage = "Last name is required.")]
    public string? LastName { get; set; }

    [Required(ErrorMessage = "Email is required.")]
    [EmailAddress(ErrorMessage = "Invalid email address.")]
    public string? Email { get; set; }

    [Required(ErrorMessage = "Password is required.")]
    [MinLength(6, ErrorMessage = "Password must be at least 6 characters.")]
    public string? Password { get; set; }

    [Compare("Password", ErrorMessage = "Passwords do not match.")]
    public string? ConfirmPassword { get; set; }
}

This works fine for small apps or quick demos.
But as soon as your project grows, you start mixing validation logic with your model, violating the Separation of Concerns principle.

Here’s the problem:

  • Validation rules are tightly coupled to your models.
  • It’s hard to reuse or maintain validation logic.
  • Large models become cluttered with attributes.

So what’s the alternative?


🚀 Meet FluentValidation — A Cleaner Way to Validate

FluentValidation is an open-source .NET library that makes your validation:

  • Easier to read and maintain.
  • Separated from your domain models.
  • Highly customizable and testable.

It uses a fluent API to define rules, so you can write validations that read like English sentences.

For small apps, Data Annotations are fine. But for enterprise-level or cleanly architected projects, FluentValidation is a game changer.


⚙️ Setting Up FluentValidation in ASP.NET Core

Let’s walk through a real example using .NET 8 Web API and Visual Studio 2022.

Step 1: Install the NuGet Packages

Open the Package Manager Console and run:

Install-Package FluentValidation
Install-Package FluentValidation.DependencyInjectionExtensions

⚠️ Note: The FluentValidation.AspNetCore package is now deprecated.


Step 2: Create a Request Model

public class UserRegistrationRequest
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public string? Email { get; set; }
    public string? Password { get; set; }
    public string? ConfirmPassword { get; set; }
}

Step 3: Create a Validator

Create a new folder named Validators and add a class UserRegistrationValidator.cs:

using FluentValidation;

public class UserRegistrationValidator : AbstractValidator<UserRegistrationRequest>
{
    public UserRegistrationValidator()
    {
        RuleFor(x => x.Email).EmailAddress();
    }
}

Step 4: Register the Validator in Program.cs

You can register validators manually or automatically.

Option 1: Register a single validator

builder.Services.AddScoped<IValidator<UserRegistrationRequest>, UserRegistrationValidator>();

Option 2: Register all validators in an assembly

builder.Services.AddValidatorsFromAssemblyContaining<UserRegistrationValidator>();

🧠 Validating Requests in an API Endpoint

Here’s a simple minimal API example:

app.MapPost("/register", async (UserRegistrationRequest request, IValidator<UserRegistrationRequest> validator) =>
{
    var result = await validator.ValidateAsync(request);
    if (!result.IsValid)
        return Results.ValidationProblem(result.ToDictionary());

    return Results.Accepted();
});

Now, if a user sends an invalid email, you’ll get a clean validation error response:

{
  "errors": {
    "Email": ["'Email' is not a valid email address."]
  }
}

💬 Adding More Validation Rules

Let’s add more logic to make it practical.

public class UserRegistrationValidator : AbstractValidator<UserRegistrationRequest>
{
    public UserRegistrationValidator()
    {
        RuleFor(x => x.FirstName).NotEmpty().MinimumLength(4);
        RuleFor(x => x.LastName).NotEmpty().MaximumLength(10);
        RuleFor(x => x.Email).EmailAddress().WithMessage("{PropertyName} is invalid! Please check!");
        RuleFor(x => x.Password).Equal(x => x.ConfirmPassword).WithMessage("Passwords do not match!");
    }
}

Each rule is expressive and easy to read:

  • FirstName must not be empty and at least 4 characters long.
  • LastName must not exceed 10 characters.
  • ✅ Custom message for invalid email.
  • ✅ Password and ConfirmPassword must match.

🧩 Custom Validation Logic

You can even define custom validation functions:

private bool IsValidName(string name) => name.All(Char.IsLetter);

RuleFor(x => x.FirstName)
    .Cascade(CascadeMode.Stop)
    .NotEmpty()
    .MinimumLength(4)
    .Must(IsValidName).WithMessage("{PropertyName} should contain only letters.");

Now if someone enters 1234 as a name, they’ll get a clear message:
"First Name should contain only letters."


🧾 Summary

FluentValidation is a must-have library for any modern ASP.NET Core project that values clean code and maintainability.

In summary:

  • Data Annotations = simple, quick, but limited.
  • FluentValidation = flexible, clean, and scalable.
  • Perfect for teams following SOLID and clean architecture principles.

Grab the Source Code: (Click here)