Introduction
In Clean Architecture, keeping your core domain logic free from clutter such as validation rules is crucial. MediatR combined with FluentValidation provides a structured, clean approach by using IRequestValidator as a pipeline behavior, ensuring that request validation is decoupled from your handlers and domain logic.
Why Use IRequestValidator with FluentValidation?
Using IRequestValidator:
Centralizes request validation, keeping your handlers clean.
Provides a single place for validation logic, promoting reusability and maintainability.
Improves testing efficiency by isolating validation logic.
Implementation Steps
Step 1: Define Your Command
public record CreateUserCommand(string Email, string Password) : IRequest<int>;
Step 2: Create a FluentValidation Validator
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Email)
.NotEmpty().EmailAddress().WithMessage("Valid email required.");
RuleFor(x => x.Password)
.MinimumLength(6).WithMessage("Password must be at least 6 characters.");
}
}
Step 3: IRequestValidator Pipeline Behavior
Implement a pipeline behavior that executes validation before handling the command:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return await next();
}
}
Step 4: Dependency Injection Configuration
Register your validators and pipeline behavior in the Application layer's startup:
services.AddValidatorsFromAssemblyContaining<CreateUserCommandValidator>();
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
Comparing IRequestValidator with Other Methods
Example Unit Test
[Fact]
public void CreateUser_InvalidEmail_FailsValidation()
{
var validator = new CreateUserCommandValidator();
var command = new CreateUserCommand("invalid-email", "123456");
var result = validator.Validate(command);
Assert.False(result.IsValid);
Assert.Contains(result.Errors, x => x.PropertyName == "Email");
}
Conclusion
Implementing request validation using FluentValidation with IRequestValidator and MediatR pipeline behaviors aligns perfectly with Clean Architecture. It ensures clear separation of concerns, centralizes validation logic, simplifies maintenance, and enhances testability.
Originally published on Substack. Original publication date retained.
