In modern web development, the CQRS (Command Query Responsibility Segregation) pattern and MediatR library have gained popularity for building scalable and maintainable applications. When it comes to validating input data, Fluent Validation provides a flexible and intuitive approach. In this article, we will explore how to integrate Fluent Validation with CQRS and MediatR in an ASP.NET Core application.
Prerequisites
Before we dive into the implementation, make sure you have the following prerequisites:
Basic knowledge of ASP.NET Core.
Familiarity with CQRS and MediatR.
Understanding of Fluent Validation.
Setting Up the Project
Let's start by setting up a new ASP.NET Core project. Open Visual Studio and follow these steps:
Select "Create a new project."
Choose the ASP.NET Core Web Application template.
Set the project name and location.
Select the desired framework and click "Create."
Once the project is created, we can move on to installing the necessary NuGet packages.
Installing Required Packages
To begin, install the following NuGet packages:
FluentValidation.AspNetCore: This package provides integration with ASP.NET Core for Fluent Validation.
MediatR.Extensions.Microsoft.DependencyInjection: This package is required for setting up MediatR in the ASP.NET Core application.
You can install these packages using the NuGet Package Manager or by running the following command in the Package Manager Console:
Install-Package FluentValidation.AspNetCore
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
Commands and Queries
Let's start by defining our commands and queries. In the CQRS pattern, commands are used to perform actions that modify the system's state, while queries are used to retrieve data without modifying the state.
// CreateAccountCommand.cs
public class CreateAccountCommand : IRequest
{
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
// GetAccountQuery.cs
public class GetAccountQuery : IRequest<AccountDto>
{
public int AccountId { get; set; }
}
In the above code, we have defined a CreateAccountCommand
that represents the action of creating a new user account. It contains properties for the username, email, and password. Additionally, we have a GetAccountQuery
that retrieves an account by its ID and returns an AccountDto
object.
Validators
Next, we need to create validators for our commands and queries using Fluent Validation. Validators define the rules and constraints that the input data should adhere to.
// 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.");
}
}
// GetAccountQueryValidator.cs
public class GetAccountQueryValidator : AbstractValidator<GetAccountQuery>
{
public GetAccountQueryValidator()
{
RuleFor(query => query.AccountId)
.GreaterThan(0).WithMessage("Account ID must be greater than zero.");
}
}
In the above code, we have defined a CreateAccountValidator
that validates the CreateAccountCommand
based on various rules. Similarly, the GetAccountQueryValidator
validates the GetAccountQuery
by ensuring the account ID is greater than zero.
Handlers
Now that we have our commands, queries, and validators in place, let's implement the handlers that will process these requests and perform the necessary actions or data retrieval.
//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;
}
}
In the handlers, we inject the corresponding validator via the constructor using the IValidator<T>
interface. This allows us to access the validator instance and validate the command or query by calling the ValidateAsync
method. If the validation fails, we throw a ValidationException
and provide the validation errors.
Validation using IPipelineBehavior
In addition to manual validation inside the handlers, Fluent Validation can also be integrated into the MediatR pipeline using the IPipelineBehavior
interface. This approach allows for automatic validation of commands and queries before they reach the corresponding handlers. Let's explore how to set up and use this approach.
Setting up Fluent Validation Pipeline Behavior
To use Fluent Validation with MediatR pipeline behavior, we need to create a pipeline behavior class that implements the IPipelineBehavior
interface. This behavior class will intercept incoming commands and queries and perform the validation before passing them to the handlers.
// 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();
}
}
In the above code, we have created a ValidationPipelineBehavior
class that implements the IPipelineBehavior
interface. The behavior class takes an IValidator<TRequest>
as a dependency via the constructor. It intercepts the request, validates it using the injected validator, and throws a ValidationException
if the validation fails. If the validation succeeds, it calls the next handler in the pipeline using the next()
delegate.
Registering Fluent Validation Pipeline Behavior
To register the Fluent Validation pipeline behavior, we need to add it to the MediatR pipeline configuration in the Startup.cs
file of our ASP.NET Core application.
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>));
services.AddMediatR(Assembly.GetExecutingAssembly());
// ...
}
In the above code, we use the AddValidatorsFromAssembly
method to scan and register all the validator classes from the current assembly. Then, we use AddTransient
to register the ValidationPipelineBehavior
as a transient service for all request and response types. Finally, we call AddMediatR
to register MediatR and its dependencies.
Using Validators with Handlers
With the Fluent Validation pipeline behavior set up, we can now remove the manual validation from our handlers since the validation will be automatically performed by the pipeline behavior.
// 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;
}
}
In the above code, we no longer manually validate the commands and queries within the handlers. Instead, the validation is automatically triggered by the pipeline behavior, which intercepts the requests before they reach the handlers.
Conclusion
In this article, we have learned how to leverage Fluent Validation in combination with the CQRS pattern and MediatR in an ASP.NET Core application. We started by defining commands and queries to represent actions and data retrieval requests. Then, we created validators using Fluent Validation to enforce validation rules on the input data.
We explored two approaches for incorporating validation into our application. First, we manually validated the commands and queries within the handlers by injecting the corresponding validators and invoking the ValidateAsync
method. This approach provided us with fine-grained control over the validation process.
Next, we looked at using the IPipelineBehavior
interface to integrate Fluent Validation into the MediatR pipeline. By creating a validation pipeline behavior, we could automatically validate the incoming commands and queries before they reached the handlers. This approach allowed for a more centralized and automated validation process.
To further assist you in understanding the concepts discussed in this article, you can take a look at the following demo project: FluentValidationDemo. The demo project provides a simple practical example and implementation for using Fluent Validation with CQRS and MediatR in an ASP.NET Core application.