ASP.NET
Core - Usando Fluent Validation com CQRS
![]() |
Neste artigo vou apresentar como usar a Fluent Validation com a CQRS em um projeto ASP.NET Core. |
Quando se trata de validar dados de entrada, o Fluent Validation oferece uma abordagem flexível e intuitiva e neste artigo, vamos explorar como integrar Fluent Validation com CQRS e MediatR em um aplicativo ASP.NET Core.
Criando e configurando o projeto
Vamos começar criando um novo projeto usando o template ASP.NET Core Web API com o nome ApiCqrsFluentValidation usando as seguintes configurações:
Depois que o projeto for criado, vamos instalar os seguintes pacotes nugets:
Você pode instalar
esses pacotes usando o NuGet Package Manager ou
executando o seguinte comando no Package Manager Console:
Install-Package <nome_pacote>
Nota (*): O pacote
FluentValidation.AspNetCore
foi descontinuado e não recebe mais suporte.
Essa informação é oficial e pode
ser encontrada no repositório do FluentValidation no GitHub e na documentação.
O motivo principal para a descontinuação é que a equipe do FluentValidation
recomenda a utilização do pacote principal
FluentValidation com uma abordagem de validação
manual no ASP.NET Core.
O FluentValidation ainda é open source e está disponível no
Agora vamos criar uma pasta Entities no projeto e a seguir criar a classe User :
public
class
User { public int Id { get; set; } public string? Username { get; set; } public string? Email { get; set; } public string? Password { get; set; } } |
No arquivo appsettings.json vamos definir a string de conexão com o banco de dados:
{ "ConnectionStrings": { "DefaultConnection": "Data Source=<seu_host>;Initial Catalog=ApiCqrsDB; Integrated Security=True;TrustServerCertificate=True" }, ... |
Vamos criar a pasta Context no projeto e nesta pasta criar o arquivo de contexto AppDbContext que herda de DbContext:
using
ApiCqrsFluentValidation.Entities; using Microsoft.EntityFrameworkCore; namespace ApiCqrsFluentValidation.Context;public class AppDbContext : DbContext{ public AppDbContext(DbContextOptions<AppDbContext> options):base(options) { } public DbSet<User> Users { get; set; } } |
Finalmente vamos registrar o serviço do contexto na classe Program:
... var connection = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options => ... |
Podemos agora aplicar o Migrations usando a ferramenta EF Core Tools com os seguintes comandos:
dotnet ef migrations add
MigracaoInicial
dotnet ef database update
A seguir vamos configurar e registrar os serviços do MediatR da Fluent Validation no contêiner de injeção de dependência do ASP.NET Core. Fazemos isso na classe Program:
using
FluentValidation.AspNetCore; var builder = WebApplication.CreateBuilder(args);// Add services to the container. builder.Services.AddControllers(); builder.Services.AddMediatR(cfg => builder.Services.AddFluentValidationAutoValidation() builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); |
Definindo os comandos e consultas : Commands e Queries
O CQRS - Command Query Responsibility Segregation, é um padrão de arquitetura de desenvolvimento de software que permite realizar a separação de leitura e escrita em dois modelos: Query e Command, uma para leitura e outra para escrita de dados, respectivamente.
Assim, o CQRS separa as responsabilidades em termos de leitura e escrita, o que faz muito sentido.
Todas as classes implementam IRequest<T> onde especificamos o tipo de dados que será retornado quando o comando for processado, e, também, através da qual vinculamos os comandos com as classes Command Handlers.
Para fazer a implementação temos que criar as classes para os comandos (alterações de estado) e para as consultas (recuperação de dados) e a seguir temos que criar os Handlers ou manipuladores para cada comando e consulta, implementando as interfaces IRequestHandler ou IRequestHandler<TRequest, TResponse> do MediatR.
Indo direto ao que interessa vamos criar no projeto uma pasta CQRS e dentro desta pasta vamos criar a pasta Commands e a pasta Queries.
A seguir na pasta Commands vamos criar a classe CreateUserCommand :
public
class
CreateUserCommand :
IRequest<Guid> { public string? Username { get; set; } public string? Email { get; set; } public string? Password { get; set; } } |
Na mesma pasta vamos criar a classe CreateUserCommandHandler :
using
MediatR; namespace ApiCqrsFluentValidation.CQRS.Commands;public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>{ private readonly AppDbContext _context; public CreateUserCommandHandler(AppDbContext context) { _context = context; } public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken) { var user = new User(); user.Username = request.Username; user.Email = request.Email; user.Password = request.Password; _context.Users.Add(user); await _context.SaveChangesAsync(); return await Task.FromResult(Guid.NewGuid()); } } |
A seguir na pasta Queries vamos criar a classe GetUsersQuery:
public
class
GetUsersQuery
: IRequest<IEnumerable<User>> { } |
A seguir ainda na pasta Queries vamos criar o Handler GetUsersQueryHandler :
using
ApiCqrsFluentValidation.Context; using ApiCqrsFluentValidation.Entities; using MediatR; using Microsoft.EntityFrameworkCore; namespace ApiCqrsFluentValidation.CQRS.Queries;public class GetUsersQueryHandler : IRequestHandler<GetUsersQuery, IEnumerable<User>>{ private readonly AppDbContext _context; public GetUsersQueryHandler(AppDbContext context) { _context = context; } public async Task<IEnumerable<User>> Handle(GetUsersQuery request, CancellationToken cancellationToken) { return await _context.Users.ToListAsync(); } } |
Criando o controlador
Agora vamos criar o controlador UsersController da nossa aplicação.
using ApiCqrsFluentValidation.CQRS.Commands;
using ApiCqrsFluentValidation.CQRS.Queries;
using ApiCqrsFluentValidation.Entities;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace ApiCqrsFluentValidation.Controllers;
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
IMediator _mediator;
public UsersController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create(CreateUserCommand command)
{
var userId = await _mediator.Send(command);
return Ok(userId);
}
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
try
{
var command = new GetUsersQuery();
var response = await _mediator.Send(command);
return Ok(response);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
|
Fazendo a validação com Fluent Validation
Agora vamos criar uma pasta CQRS/Commands do projeto a classe CreateUserCommandValidator onde vamos definir as regras de validação para os dados da entidade User:
using
FluentValidation; namespace ApiCqrsFluentValidation.CQRS.Commands;public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>{ public CreateUserCommandValidator() { RuleFor(x => x.Username).NotEmpty().MaximumLength(50).WithMessage("O nome é requerido(tamanho máximo 20)."); RuleFor(x => x.Email).NotEmpty().WithMessage("O Email é obrigatório."); RuleFor(x => x.Email).EmailAddress().WithMessage("O formato do email é inválido."); RuleFor(p => p.Password).NotEmpty().WithMessage("A senha não pode ser vazia") .MinimumLength(8).WithMessage("O Tamanho da senha deve ser de no mínimo 8.") .MaximumLength(15).WithMessage("O Tamanho da senha deve não pode exceder 15.") .Matches(@"[A-Z]+").WithMessage("A senha deve conter pelo menos um caractere maiúsculo.") .Matches(@"[a-z]+").WithMessage("A senha deve conter pelo menos um carcatere minúsculo.") .Matches(@"[0-9]+").WithMessage("A senha deve conter pelo menos um número.") .Matches(@"[\!\?\#\*\.]+").WithMessage("A senha deve conter pelo menos um caracstere (!?# *.)."); } } |
A seguir vamos uma pasta chamada Behaviors no projeto e nesta pasta criar a classe ValidationBehavior :
using FluentValidation;
using MediatR;
namespace ApiCqrsFluentValidation.Behaviors;
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IValidator<TRequest> _validator;
public ValidationBehavior(IValidator<TRequest> validator)
{
_validator = validator;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse>
next, CancellationToken cancellationToken)
{
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
return await next();
}
}
|
Os "Behaviors" (comportamentos) na biblioteca MediatR são uma maneira de inserir lógica adicional nas operações de manipulação de comandos e consultas durante seu processamento. Eles permitem que você adicione tarefas antes e/ou depois do manuseio dos comandos e consultas. Os behaviors podem ser usados para realizar ações como validação, logging, autorização e muito mais.
No código implementado temos um exemplo de um comportamento de validação chamado ValidationBehavior. Este comportamento é responsável por validar os objetos de solicitação (requests) antes de serem manipulados pelo manipulador correspondente.
Vejamos uma explicação detalhada desta classe:
Estamos criando uma classe genérica chamada ValidationBehavior que implementa a interface IPipelineBehavior<TRequest, TResponse>. Isso permite que ela seja usada como um behavior no pipeline de manipulação do MediatR.
Agora vamos registrar os serviços na classe Program:
... // registrar validator manualmente // builder.Services.AddTransient<IValidator<CreateUserCommand>, CreateUserCommandValidator>(); // Para detectar os validators automaticamente builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly); builder.Services.AddTransient( typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));... |
Pronto ! Agora é só
alegria...
Executando o projeto teremos os endpoints exibidos na interface do Swagger:
E tentando criar um usuário que fere as regras de validação teremos o seguinte resultado:
Com isso temos a validação feita com a Fluent Validation em nosso comando CQRS.
Pegue o código do
projeto aqui:
ApiCqrsFluentValidation.zip
"Esta é a mensagem que dele ouvimos e transmitimos a vocês: Deus é luz; nele
não há treva alguma.
Se afirmarmos que temos comunhão com ele, mas andamos
nas trevas, mentimos e não praticamos a verdade"
1 João 1:5-6
Referências:
C# - Tasks x Threads. Qual a diferença
DateTime - Macoratti.net
Null o que é isso ? - Macoratti.net
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)