Skip to main content
  1. Blog/

Validating Inputs with FluentValidation

·6 mins
Nitin Kumar Singh
Author
Nitin Kumar Singh
I build enterprise AI solutions and cloud-native systems. I write about architecture patterns, AI agents, Azure, and modern development practices — with full source code.
Table of Contents

Introduction
#

In this article, we will explore how to implement input validation in the Contact Management Application using FluentValidation. FluentValidation is a robust .NET library that facilitates the creation of flexible and extensible validation rules for your models. By integrating seamlessly with ASP.NET Core, it allows you to maintain clean separation between validation logic and business logic, adhering to the principles of Clean Architecture.

We’ll cover:

  1. The role of FluentValidation in Clean Architecture.

  2. How to set up FluentValidation in the application.

  3. How to define validation rules for DTOs.

  4. How to register FluentValidation and handle validation failures.


Part of the Series
#

This article is part of a series covering the Contact Management Application, a sample project demonstrating Clean Architecture principles. Other articles include:

  1. Clean Architecture: Introduction to the Project Structure{:target="_blank" rel=“noopener noreferrer”}

  2. Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging{:target="_blank" rel=“noopener noreferrer”}

  3. Clean Architecture: Validating Inputs with FluentValidation (You are here)

  4. Clean Architecture: Dependency Injection Setup Across Layers{:target="_blank" rel=“noopener noreferrer”}

  5. Clean Architecture: Handling Authorization and Role-Based Access Control (RBAC){:target="_blank" rel=“noopener noreferrer”}

  6. Clean Architecture: Implementing Activity Logging with Custom Attributes{:target="_blank" rel=“noopener noreferrer”}

  7. Clean Architecture: Unit of Work Pattern and Its Role in Managing Transactions{:target="_blank" rel=“noopener noreferrer”}

  8. Clean Architecture: Using Dapper for Data Access and Repository Pattern{:target="_blank" rel=“noopener noreferrer”}

  9. Clean Architecture: Best Practices for Creating and Using DTOs in the API{:target="_blank" rel=“noopener noreferrer”}

  10. Clean Architecture: Error Handling and Exception Management in the API{:target="_blank" rel=“noopener noreferrer”}

  11. Clean Architecture: Dockerizing the .NET Core API, Angular and MS SQL Server{:target="_blank" rel=“noopener noreferrer”}

  12. Clean Architecture: Seeding Initial Data Using Docker Compose and SQL Scripts{:target="_blank" rel=“noopener noreferrer”}


#

1. The role of FluentValidation in Clean Architecture.

Validation is a crucial part of any web application to ensure that data provided by users or external systems is valid and meets predefined criteria. In a Clean Architecture, we want to keep validation logic decoupled from business rules and domain logic. FluentValidation allows you to define validation rules for DTOs in a simple, declarative manner and keeps your API Layer clean by separating validation logic from the controllers and services.

Some key advantages of using FluentValidation include:

  • Separation of concerns: Validation logic is maintained independently from business logic, ensuring a clear and organized structure.

  • Readability: Fluent API makes it easy to read and understand validation rules.

  • Reusability: You can reuse validation logic across different layers or projects.

  • Customizable error messages: Provides user-friendly error messages when validation fails.


2. Setting Up FluentValidation
#

To start using FluentValidation in your ASP.NET Core project, you first need to install the FluentValidation.AspNetCore package:

Install-Package FluentValidation.AspNetCore

Once installed, you can register FluentValidation in your Startup.cs file by modifying the ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    
    // Register FluentValidation services
    services.AddFluentValidation(fv => 
        fv.RegisterValidatorsFromAssemblyContaining<Startup>());
}

This ensures that FluentValidation scans the assembly for any validator classes and automatically applies them when appropriate.


3. Creating and Defining Validation Rules
#

In FluentValidation, validation rules are encapsulated within specialized classes known as validators. These classes validate your DTOs (Data Transfer Objects), ensuring that incoming data is valid before it reaches the Application Layer. For the Contact Management Application, let’s create a validator for the CreateContactPersonDto.

3.1 Defining Validation Rules for DTOs
#

The following code defines validation rules for the CreateContactPersonDto, ensuring that required fields are not empty and that the email is in the correct format:

public class CreateContactPersonDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public long Mobile { get; set; }
}

Next, we create a validator for this DTO:

public class CreateContactPersonValidator : AbstractValidator<CreateContactPersonDto>
{
    public CreateContactPersonValidator()
    {
        RuleFor(x => x.FirstName)
            .NotEmpty().WithMessage("First name is required.")
            .Length(2, 50).WithMessage("First name must be between 2 and 50 characters.");

        RuleFor(x => x.LastName)
            .NotEmpty().WithMessage("Last name is required.")
            .Length(2, 50).WithMessage("Last name must be between 2 and 50 characters.");

        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("Email is required.")
            .EmailAddress().WithMessage("Invalid email format.");

        RuleFor(x => x.Mobile)
            .NotEmpty().WithMessage("Mobile number is required.")
            .GreaterThan(999999999).WithMessage("Mobile number should have at least 10 digits.");
    }
}

In this validator:

  • The FirstName and LastName fields are mandatory, with a required length ranging from 2 to 50 characters.

  • The Email field is mandatory and must adhere to a valid email format.

  • Mobile must have at least 10 digits.

3.2 Defining Validation for Update Operations
#

Similarly, we can define a validator for the UpdateContactPersonDto, which may have different validation rules since not all fields might be required during updates:

public class UpdateContactPersonDto
{
    public string Email { get; set; }
    public long Mobile { get; set; }
}
public class UpdateContactPersonValidator : AbstractValidator<UpdateContactPersonDto>
{
    public UpdateContactPersonValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("Email is required.")
            .EmailAddress().WithMessage("Invalid email format.");
        
        RuleFor(x => x.Mobile)
            .GreaterThan(999999999).WithMessage("Mobile number should have at least 10 digits.");
    }
}

In this validator:
#

• Email is required and must be in a valid format.

• Mobile must have at least 10 digits.

4. Handling Validation in Controllers

Once validators are in place, FluentValidation automatically validates incoming requests. If validation fails, it returns a 400 Bad Request response with details about the validation errors.

Here’s an example of how the ContactPersonController uses FluentValidation for creating a new contact:

[ApiController]
[Route("api/[controller]")]
public class ContactPersonController : ControllerBase
{
    private readonly IContactPersonService _contactPersonService;

    public ContactPersonController(IContactPersonService contactPersonService)
    {
        _contactPersonService = contactPersonService;
    }

    [HttpPost]
    public async Task<IActionResult> Add(CreateContactPersonDto contactDto)
    {
        var createdContactPerson = await _contactPersonService.Add(contactDto);
        return CreatedAtAction(nameof(GetById), new { id = createdContactPerson.Id }, createdContactPerson);
    }
}

If the incoming request does not meet the validation rules, the controller will automatically return a response like:

{
  "errors": {
    "FirstName": [
      "First name is required."
    ],
    "Email": [
      "Invalid email format."
    ]
  }
}

4.1 Customizing Validation Failure Responses
#

You can also customize the structure of the validation failure response if you prefer a specific format. Here’s an example of how you can override the default behavior in Startup.cs:

services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = context =>
    {
        var errors = context.ModelState
            .Where(e => e.Value.Errors.Count > 0)
            .SelectMany(x => x.Value.Errors)
            .Select(x => x.ErrorMessage)
            .ToArray();

        var errorResponse = new
        {
            Status = 400,
            Errors = errors
        };

        return new BadRequestObjectResult(errorResponse);
    };
});

This ensures that validation errors are returned in a consistent format.


5. Benefits of FluentValidation in Clean Architecture
#

Using FluentValidation in a Clean Architecture provides several benefits:

  • Separation of concerns: Validation logic is kept separate from both the domain and business logic, making it easier to maintain.

  • Extensibility: FluentValidation’s fluent API makes it easy to extend or modify validation rules as the application grows.

  • Consistency: By defining validation logic in one place, you ensure consistent validation across the application.

  • Reusability: Validators can be reused across different controllers and services, ensuring that validation logic isn’t duplicated.


Conclusion
#

In this article, we explored how to use FluentValidation to handle input validation in the Contact Management Application. FluentValidation helps ensure that incoming data meets the defined criteria before it reaches the application’s business logic, ensuring clean separation of concerns and consistency across the application.

We defined validators for our DTOs, registered FluentValidation in our ASP.NET Core project, and saw how validation failures are handled automatically by the framework.

In the next article, we’ll discuss how to set up Dependency Injection (DI) across the application layers and how to wire up services, repositories, and external dependencies.

For more detailed examples and the complete project code, visit the GitHub repository:

GitHub Repository: Contact Management Application{:target="_blank" rel=“noopener noreferrer”}

Related

Dependency Injection Setup Across Layers

·6 mins
Introduction # Dependency Injection (DI) is a key design pattern in Clean Architecture that facilitates loose coupling between components. By injecting dependencies rather than hard-coding them, we create more maintainable, testable code where components can be easily swapped or mocked. This article explores how to implement DI across different architectural layers in the Contact Management Application.

Clean Architecture: Introduction to the Project Structure

·11 mins
Overview # Clean Architecture is a powerful software design pattern that promotes a clear separation of concerns, making your application’s core business logic independent of external dependencies like databases, user interfaces, or frameworks. By following this architecture, systems become maintainable, testable, and adaptable, preparing them for both current demands and future growth.

Implementing AutoMapper for DTO Mapping with Audit Details

·7 mins
AutoMapper is a powerful object-object mapper that simplifies the transformation between data models. In Clean Architecture, it plays a vital role in maintaining a clear separation of concerns between layers. By automating mapping and handling fields like CreatedBy, CreatedOn, UpdatedBy, and UpdatedOn, AutoMapper reduces boilerplate code and ensures consistency.