Validare cu FluentValidation pe Commande în .NET (CreatePostCommandValidator)
Doru Bulubasa
21 July 2025

Într-o arhitectură bazată pe CQRS, comanda (Command) reprezintă intenția de a modifica starea aplicației. De aceea, este esențial ca aceste comenzi să fie validate riguros înainte să ajungă la handlerul care le procesează.

În această etapă, vom integra FluentValidation pentru a valida comenzile trimise către MediatR, în mod particular CreatePostCommand.


✅ De ce FluentValidation?

  • Separarea clară a regulilor de validare de logica de business.

  • Suport complet pentru validarea obiectelor complexe.

  • Integrare automată cu IRequest<T> din MediatR.

  • Validări fluente, lizibile și extensibile.


🛠️ 1. Instalare FluentValidation

Adaugă în proiectul Application:

dotnet add package FluentValidation

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection


📦 2. Creăm CreatePostCommand

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


🧪 3. Adăugăm CreatePostCommandValidator

using FluentValidation;

public class CreatePostCommandValidator : AbstractValidator<CreatePostCommand>

{

    public CreatePostCommandValidator()

    {

        RuleFor(x => x.Title)

            .NotEmpty().WithMessage("Titlul este obligatoriu")

            .MaximumLength(100).WithMessage("Titlul nu poate depăși 100 de caractere");

        RuleFor(x => x.Content)

            .NotEmpty().WithMessage("Conținutul este obligatoriu")

            .MinimumLength(100).WithMessage("Conținutul trebuie să aibă minimum 100 de caractere");

    }

}


🧩 4. Integrarea validării cu MediatR

Creează un 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();

    }

}

Înregistrare în DI:

services.AddValidatorsFromAssemblyContaining<CreatePostCommandValidator>();

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


🧪 5. Exemplu de test

var validator = new CreatePostCommandValidator();

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

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


🧵 Concluzii

✅ Aplicând FluentValidation doar pe Commande, ne asigurăm că validările rigide sunt separate de citiri. Acest pattern respectă principiul Single Responsibility și pregătește terenul pentru scalare și testare.