Supun Vindula
The journal
.NETEngineering

Validating Requests with FluentValidation and IRequestValidator in Clean Architecture

Validating requests using fluent validation

sv.
Supun Imbulana23 July 2025 · 2 min read

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.