Validare cu FluentValidation pe Commande în .NET (CreatePostCommandValidator)
Î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.