Validating Inputs with FluentValidation

Validating Inputs with FluentValidation
Validating Inputs with FluentValidation

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
  2. Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging
  3. Clean Architecture: Validating Inputs with FluentValidation (You are here)
  4. Clean Architecture: Dependency Injection Setup Across Layers
  5. Clean Architecture: Handling Authorization and Role-Based Access Control (RBAC)
  6. Clean Architecture: Implementing Activity Logging with Custom Attributes
  7. Clean Architecture: Unit of Work Pattern and Its Role in Managing Transactions
  8. Clean Architecture: Using Dapper for Data Access and Repository Pattern
  9. Clean Architecture: Best Practices for Creating and Using DTOs in the API
  10. Clean Architecture: Error Handling and Exception Management in the API
  11. Clean Architecture: Dockerizing the .NET Core API and MS SQL Server
  12. Clean Architecture: Seeding Initial Data Using Docker Compose and SQL Scripts


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


Discover more from Nitin Singh

Subscribe to get the latest posts sent to your email.

9 Responses

  1. December 3, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  2. December 3, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  3. December 3, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  4. December 5, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  5. December 5, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  6. December 5, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  7. December 16, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  8. December 16, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

  9. December 16, 2024

    […] Clean Architecture: Validating Inputs with FluentValidation […]

Leave a Reply