Fluent Validation with CQRS

MBark September 01, 2023
fluent-validation-with-cqrs

Introduction

The CQRS pattern and MediatR library have gained prominence in modern web development for creating scalable and maintainable applications. Fluent Validation provides a versatile and easy technique to validating input data. In this post, we will look at how to use Fluent Validation in conjunction with CQRS and MediatR in an ASP.NET Core application.

Prerequisites

Before we begin, please ensure that you have the necessary prerequisites:

  1. Basic understanding of ASP.NET Core.
  2. Knowledge of CQRS and MediatR.
  3. Understanding of Fluent Validation.

Setting Up the Project

Let's begin by creating a new ASP.NET Core project. Open Visual Studio and perform the following steps:

  • Choose "Create a new project."
  • Select ASP.NET Core Web Application as the template.
  • Set the name and location of the project.
  • Choose the preferred framework and press "Create."

After we've built the project, we can install the required NuGet packages.

Installing Required Packages

Install the following NuGet packages:

FluentValidation.AspNetCore:
This package integrates Fluent Validation with ASP.NET Core.
MediatR.Extensions.Microsoft.DependencyInjection:
This package is required for MediatR to be installed in an ASP.NET Core application.

These packages can be installed with the NuGet Package Manager or by performing the following command in the Package Manager Console.

Commands and Queries

Begin by defining our commands and queries. The CQRS pattern uses commands to conduct operations that change the state of the system, while queries are used to retrieve data without changing the state.

We defined a CreateAccountCommand in the code below to represent the action of creating a new user account. It has properties for the username, email address, and password.

// CreateAccountCommand.cs

public class CreateAccountCommand : IRequest
{
    public string Username { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}	

We also have a GetAccountQuery method that retrieves an account by ID and returns an AccountDTO object.

// GetAccountQuery.cs

public class GetAccountQuery : IRequest
{
    public int AccountId { get; set; }
}	

Validators

Next, we'll use Fluent Validation to generate validators for our commands and queries. Validators specify the rules and constraints that the input data must follow.

We defined a CreateAccountValidator in the code below, which validates the CreateAccountCommand based on several rules.

// CreateAccountValidator.cs
public class CreateAccountValidator: AbstractValidator < CreateAccountCommand > {
  public CreateAccountValidator() {
    RuleFor(command => command.Username)
      .NotEmpty().WithMessage("Username is required.")
      .Length(3, 20).WithMessage("Username must be between 3 and 20 characters.");

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

    RuleFor(command => command.Password)
      .NotEmpty().WithMessage("Password is required.")
      .MinimumLength(8).WithMessage("Password must be at least 8 characters long.");
  }
}	

The GetAccountQueryValidator, on the other hand, verifies the GetAccountQuery by ensuring the account ID is larger than zero.

// GetAccountQueryValidator.cs
public class GetAccountQueryValidator: AbstractValidator < GetAccountQuery > {
  public GetAccountQueryValidator() {
    RuleFor(query => query.AccountId)
      .GreaterThan(0).WithMessage("Account ID must be greater than zero.");
  }
}

Handlers

Now that we've established our commands, queries, and validators, let's build the handlers that will take these requests and conduct the appropriate actions or data retrieval.

Using the IValidator< T > interface, we inject the corresponding validator into the handlers via the constructor. By invoking the ValidateAsync method, we may access the validator object and validate the command or query. If the validation fails, a ValidationException is thrown and the validation errors are returned.

//CreateAccountCommandHandler.cs

public class CreateAccountCommandHandler: IRequestHandler < CreateAccountCommand, Unit > {
  private readonly IValidator < CreateAccountCommand > _validator;

  public CreateAccountCommandHandler(IValidator < CreateAccountCommand > validator) {
    _validator = validator;
  }

  public async Task < Unit > Handle(CreateAccountCommand command, CancellationToken cancellationToken) {
    var validationResult = await _validator.ValidateAsync(command);

    if (!validationResult.IsValid) {
      throw new ValidationException(validationResult.Errors);
    }

    // Perform account creation logic

    return Unit.Value;
  }
}
// GetAccountQueryHandler.cs

public class GetAccountQueryHandler: IRequestHandler < GetAccountQuery, AccountDto > {
  private readonly IValidator < GetAccountQuery > _validator;

  public GetAccountQueryHandler(IValidator < GetAccountQuery > validator) {
    _validator = validator;
  }

  public async Task < AccountDto > Handle(GetAccountQuery query, CancellationToken cancellationToken) {
    var validationResult = await _validator.ValidateAsync(query);

    if (!validationResult.IsValid) {
      throw new ValidationException(validationResult.Errors);
    }

    // Retrieve account logic

    return accountDto;
  }
}

Validation using IPipelineBehavior

Fluent Validation, in addition to manual validation within handlers, can be implemented into the MediatR pipeline using the IPipelineBehavior interface. This method enables automatic validation of instructions and queries before they reach the appropriate handlers. Let's look at how to set this up and use it.

Setting up Fluent Validation Pipeline Behavior

We need to construct a pipeline behavior class that implements the IPipelineBehavior interface in order to use Fluent Validation with MediatR pipeline behavior. This behavior class intercepts incoming commands and queries and validates them before delivering them to handlers.

We've written a ValidationPipelineBehavior class that implements the IPipelineBehavior interface in the code below. The constructor of the behavior class accepts an IValidator< TRequest > as a dependency. It intercepts the request, verifies it with the validator that was injected, and throws a ValidationException if the validation fails. If the validation is successful, the next() delegate is used to contact the next handler in the pipeline.

// ValidationPipelineBehavior.cs

public class ValidationPipelineBehavior < TRequest, TResponse >: IPipelineBehavior < TRequest, TResponse >
  where TRequest: IRequest < TResponse > {
    private readonly IValidator < TRequest > _validator;

    public ValidationPipelineBehavior(IValidator < TRequest > validator) {
      _validator = validator;
    }

    public async Task < TResponse > Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate < TResponse > next) {
      var validationResult = await _validator.ValidateAsync(request);

      if (!validationResult.IsValid) {
        throw new ValidationException(validationResult.Errors);
      }

      return await next();
    }
  }

Registering Fluent Validation Pipeline Behavior

To register the Fluent Validation pipeline behavior, we must add it to the MediatR pipeline settings in our ASP.NET Core application's Startup.cs file.

The AddValidatorsFromAssembly function is used in the below code to scan and register all validator classes in the current assembly. The ValidationPipelineBehavior is then registered as a transient service for all request and response types using AddTransient. Finally, AddMediatR is called to register MediatR and its dependencies.

// Startup.cs

public void ConfigureServices(IServiceCollection services) {
  // ...

  services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
  services.AddTransient(typeof (IPipelineBehavior < , > ), typeof (ValidationPipelineBehavior < , > ));
  services.AddMediatR(Assembly.GetExecutingAssembly());

  // ...
}

Using Validators with Handlers

With the Fluent Validation pipeline behavior configured, we can now eliminate manual validation from our handlers because the validation will be handled automatically by the pipeline behavior.

We no longer manually validate the commands and queries within the handlers in the code below. Instead, the pipeline behavior, which intercepts requests before they reach the handlers, automatically triggers the validation.

// CreateAccountCommandHandler.cs

public class CreateAccountCommandHandler: IRequestHandler < CreateAccountCommand, Unit > {
  public async Task < Unit > Handle(CreateAccountCommand command, CancellationToken cancellationToken) {
    // Perform account creation logic

    return Unit.Value;
  }
}
// GetAccountQueryHandler.cs

public class GetAccountQueryHandler: IRequestHandler < GetAccountQuery, AccountDto > {
  public async Task < AccountDto > Handle(GetAccountQuery query, CancellationToken cancellationToken) {
    // Retrieve account logic

    return accountDto;
  }
}

Conclusion

In this post, we learnt how to use Fluent Validation in an ASP.NET Core application in conjunction with the CQRS pattern and MediatR. To begin, we created commands and queries to describe activities and data retrieval requests. Then, we used Fluent Validation to develop validators that enforced validation rules on the input data.

We investigated two methods for implementing validation into our application. We started by manually validating the commands and queries within the handlers by injecting the appropriate validators and running the ValidateAsync function. This method gave us fine-grained control over the validation process.

The IPipelineBehavior interface was then used to integrate Fluent Validation into the MediatR pipeline. We could validate incoming commands and queries before they reached the handlers by implementing a validation pipeline behavior. This strategy enabled a more centralized and automated validation procedure.

also view other related posts,