Nel precedente articolo ho mostrato come implementare il modello CQRS con MediatR in ASP.NET Core, creando comandi, query e gestori per separare le responsabilità di lettura e scrittura. Se non hai familiarità con CQRS e MediatR, ti consiglio di iniziare da quel post prima di continuare qui.
In questo articolo vedrai come integrare FluentValidation per garantire la corretta validazione dei dati prima della loro elaborazione.
Cos’è FluentValidation?
FluentValidation è una libreria .NET popolare e potente che consente di definire regole di validazione per i tuoi modelli in modo semplice e dichiarativo. Con FluentValidation, puoi separare la logica di validazione dal tuo codice principale, rendendolo più pulito e manutenibile.
Implementazione
Per cominciare, è necessario scaricare il codice sorgente dell’articolo precedente, che è disponibile a questo indirizzo: CQRSExample.NET su GitHub. Questo progetto contiene una configurazione di base per ASP.NET Core con CQRS e MediatR.
Installazione SignalR
Install-Package FluentValidation
Install-Package FluentValidation.AspNetCore
ValidationBehavior
Puoi evitare di richiamare i validatori all’interno del handler di ogni feature, definendo una pipeline per MediatR che esegue la validazione automaticamente. Questo approccio centralizza la logica di validazione, rendendo il tuo codice più pulito e manutenibile.
Behaviors/ValidationBehavior.cs
using FluentValidation;
using MediatR;
namespace CQRSExample.NET.Behaviors;
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators) : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
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.Count != 0)
{
throw new ValidationException(failures);
}
return await next();
}
}
Registrare ValidationBehavior e FluentValidation
Ora che hai definito il ValidationBehavior
, registra FluentValidation
e la pipeline di validazione nel file Program.cs
.
// Register MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
// Register FluentValidation Service
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
// Register the validation behavior
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
Validazione AddUserName
È il momento di aggiungere il primo validatore per il comando AddUserName
, che garantirà la validazione dei dati prima dell’elaborazione nel handler.
Crea una nuova classe nella cartella Features/AddUserName
che estende AbstractValidator<AddUsernameCommand>
per definire le regole di validazione.
using CQRSExample.NET.Database;
using CQRSExample.NET.Features.AddUserName;
using FluentValidation;
public class AddUsernameCommandValidator : AbstractValidator<AddUserNameCommand>
{
public AddUsernameCommandValidator()
{
RuleFor(x => x.UserName).NotEmpty().WithMessage("Username is required.")
.Must(BeUniqueUsername).WithMessage("Username already exists.");
}
private static bool BeUniqueUsername(string username)
{
return !FakeDatabase.UserNames.Contains(username);
}
}
RuleFor(x => x.UserName)
Specifica che la proprietàUserName
del comando deve rispettare le seguenti regole:NotEmpty()
Assicura che il campo non sia vuoto, fornendo un messaggio di errore personalizzato.Must(BeUniqueUsername)
Verifica che ilUsername
sia unico utilizzando una funzione personalizzataBeUniqueUsername
, che controlla se il nome utente è già presente nel database fittizio.
Ora si può avviare l’applicazione e provare le API utilizzando Swagger.
Conclusione
Il pattern di validazione centralizzata con MediatR e FluentValidation che hai implementato offre una solida base per le tue applicazioni. Questo approccio semplifica la manutenzione del codice e rende la validazione più coerente e gestibile.
Ricorda, ogni pattern ha il suo contesto ideale – valuta sempre i requisiti specifici del tuo progetto per sfruttare al meglio questa soluzione.