RO EN

Validation with FluentValidation on Commands in .NET (CreatePostCommandValidator)

Validation with FluentValidation on Commands in .NET (CreatePostCommandValidator)
Doru Bulubasa
21 July 2025

In a CQRS-based architecture, the command (Command) represents the intention to change the application's state. Therefore, it is essential that these commands are rigorously validated before they reach the handler that processes them.

At this stage, we will integrate FluentValidation to validate the commands sent to MediatR, particularly CreatePostCommand.


✅ Why FluentValidation?

  • Clear separation of validation rules from business logic.

  • Full support for validating complex objects.

  • Automatic integration with IRequest<T> from MediatR.

  • Fluent, readable, and extensible validations.


🛠️ 1. Installing FluentValidation

Add to the Application project:

dotnet add package FluentValidation

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection


📦 2. Create CreatePostCommand

public record CreatePostCommand(string Title, string Content) : IRequest<Guid>;


🧪 3. Add CreatePostCommandValidator

using FluentValidation;

public class CreatePostCommandValidator : AbstractValidator<CreatePostCommand>

{

    public CreatePostCommandValidator()

    {

        RuleFor(x => x.Title)

            .NotEmpty().WithMessage("Title is required")

            .MaximumLength(100).WithMessage("Title cannot exceed 100 characters");

        RuleFor(x => x.Content)

            .NotEmpty().WithMessage("Content is required")

            .MinimumLength(100).WithMessage("Content must be at least 100 characters");

    }

}


🧩 4. Integrating validation with MediatR

Create a ValidationBehavior:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>

     where TRequest : IRequest<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(r => r.Errors)

            .Where(f => f != null)

            .ToList();

        if (failures.Count != 0)

            throw new ValidationException(failures);

        return await next();

    }

}

Registration in DI:

services.AddValidatorsFromAssemblyContaining<CreatePostCommandValidator>();

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));


🧪 5. Test example

var validator = new CreatePostCommandValidator();

var result = validator.Validate(new CreatePostCommand("", "Abc"));

Console.WriteLine(result.IsValid); // False


🧵 Conclusions

✅ By applying FluentValidation only on Commands, we ensure that strict validations are separated from reads. This pattern respects the Single Responsibility principle and prepares the ground for scaling and testing.