ASP.NET Core - Adicionando Fluent Validation
Este artigo vai apresentar algumas noções básicas sobre a sintaxe de validação fluente mostrando como configurar validações fluentes em projetos ASP .NET Core Web API. |
Existem várias maneiras pelas quais as validações de negócios são aplicadas. Alguns projetos podem ter usado vários padrões de design para criar camadas personalizadas, o que ajudaria a manter todas as validações em um só lugar.
A biblioteca Fluent Validations fornece uma maneira de organizar suas validações em um único local. Também ajuda a manter a sintaxe consistente em várias validações.
Vamos criar um projeto Web API usando o .NET 7.0 chamado ApiFluentValidation que será usado como exemplo.
Criando o projeto
Abra o VS 2022 e selecione o template ASP.NET Core Web API e informe o nome ApiFluentValidation definindo as seguintes configurações para o projeto:
Crie no projeto a pasta Entities e nesta pasta crie a classe Estudante :
public
class
Estudante { public int Id { get; set; } public string Nome { get; set; } = null!; public List<string> Cursos { get; set; } = null!; } |
Na pasta Controllers crie o controlador EstudantesController definindo o código abaixo:
using
ApiFluentValidation.Entities; using Microsoft.AspNetCore.Mvc; namespace ApiFluentValidation.Controllers;[Route( "api/[controller]")][ApiController] public class EstudantesController : ControllerBase { private readonly ILogger<EstudantesController> logger; public EstudantesController(ILogger<EstudantesController> logger) { this.logger = logger; } [HttpPost] public IActionResult GetEstudante([FromBody] Estudante estudante) { this.logger.LogInformation($"API Input : {{ Id : {estudante.Id}, Nome: {estudante.Nome}}}"); return Ok(estudante); } } |
Vamos incluir no projeto o pacote nuget FluentValidation.AspNetCore :
dotnet add package FluentValidation.AspNetCore
Podemos também incluir o pacote usando o Manage Packages for Solution no VS 2022 :
Agora podemos usar os recursos da FluentValidation em nosso projeto.
Definindo e criando as validações
Com isso podemos pensar em quais validações podemos adicionar ao nosso projeto. Para simplificar vamos definir o seguinte cenário :
- Queremos que o
id do aluno seja sempre maior que zero;
- O tamanho do nome do aluno não deve ser superior a 100 caracteres e não deve
ser inferior a 8 caracteres;
- Para cursos , que é um array de strings, vamos definir duas validações;
- Para cada curso, o tamanho mínimo deve ser 8 e o tamanho máximo
deve ser 80;
- A coleção de cursos deve ter mais de 5 cursos;
A seguir veremos como adicionar as validações mencionadas acima usando fluent validatiors.. Vamos criar uma pasta Validators e adicionar um novo validador chamado EstudanteValidator.
Criando o Validator
Vejamos os fundamentos uasdos para criar um Validator usando a Fluent Validation.
Para criar um
validador personalizado, a classe de validação personalizada precisa ser
derivada de uma classe genérica, AbstractValidator<T>,
onde o parâmetro de tipo T deve ser o tipo que queremos validar. Em nosso
exemplo de código, queremos validar o tipo Estudante, logo, precisamos derivar
EstudanteValidator de AbstractValidator<Estudante>.
Outra coisa a observar – todas as regras de validação devem ser descritas no
construtor e o método RuleFor deve ser chamado para
configurar a validação. Este método recebe como entrada uma expressão lambda,
que deve especificar a propriedade que queremos validar, e, esta chamada pode
ser encadeada para chamar outros validadores.
Por exemplo, suponha que queremos expressar que Id deve ser sempre maior que
zero. Para isso, teremos que começar chamando o método
RuleFor e então podemos usar o validador
GreaterThan :
RuleFor(x => x.Id).GreaterThan(0);
O pacote Fluent Validation fornece muitos validadores integrados que podem ser
usados para validar tipos de dados básicos. Usamos um dos validadores
integrados, GreaterThan, no exemplo acima.
Semelhante a isso, existem validadores para verificação nula, strings vazias,
comprimento mínimo, comprimento máximo, etc.
Podemos usar esses validadores para implementar os requisitos de validação
hipotéticos mencionados acima. Se você não encontrar o validador pretendido na
lista de validadores integrados, também poderá escrever seu próprio validador
personalizado.
Existe também um método Must (e MustAsync) que
aceita um Func como parâmetro. Então, basicamente,
podemos implementar facilmente a lógica de validação personalizada usando isso.
Podemos criar um método separado e chamá-lo do validador
Must/MustAsync ou podemos especificar a expressão lambda.
Os validadores integrados têm mensagens de erro padrão, mas se você não quiser
usar essas mensagens padrão por qualquer motivo, existe uma maneira de
especificar a mensagem de erro personalizada usando o método
WithMessage :
RuleFor(x => x.Id).GreaterThan(0).WithMessage(“Id é
inválido.”);
Se necessário, também podemos encadear mais de um validador para uma determinada
propriedade. Por exemplo, o tamanho do nome do aluno não deve ter mais de 100
caracteres e não deve ter menos de 8 caracteres.
Essa validação precisa de dois validadores diferentes, validador de tamanho
mínimo e validador de tamanho máximo. Estes podem ser encadeados como mostrado
na linha abaixo:
RuleFor(x => x.Name).MinimumLength(10).MaximumLength(250);
Agora que apresentamos os recursos básicos que devemos saber para criar um validator vamos definir o código na classe EstudanteValidator :
using ApiFluentValidation.Entities;
using FluentValidation;
namespace ApiFluentValidation.Validators;
public class EstudanteValidator : AbstractValidator<Estudante>
{
public EstudanteValidator()
{
// Id deve ser maior que 0
RuleFor(x => x.Id)
.GreaterThan(0)
.WithMessage("Id é inválido.");
// Nome - MinLength 8, MaxLength 100
RuleFor(x => x.Nome)
.MinimumLength(8)
.WithMessage("O 'Nome' deve ter no mínimo 8 caracteres.")
.MaximumLength(100)
.WithMessage("O 'Nome' deve er no máximo 100 caracteres.");
// Cursos - A coleção não pode ser nula
// Cursos - A coleção deve ter no mínimo 5 cursos
RuleFor(x => x.Cursos)
.NotNull()
.WithMessage("A coleção 'Cursos' deve ter no mínimo 5 cursos.")
.Must((estudante, cursos, builder) => cursos.Count >= 5)
.WithMessage("A coleção 'Cursos' deve ter no mínimo 5 cursos.");
// Cursos - coleção - regra para cada registro
// Cada entrada não pode ser null
// Cada entrada deve ter no mínimo 8 e no máximo 80 caracteres
RuleForEach(x => x.Cursos)
.NotNull()
.MinimumLength(8)
.WithMessage("O nome do curso deve ter no mínimo 8 caracteres.")
.MaximumLength(80)
.WithMessage("O nome do curso deve ter no máximo 80 caracteres.");
}
}
|
Como acionar a validação ?
Agora temos um
novo endpoint que aceita um tipo personalizado Estudante
como entrada e, também temos o validador para o tipo personalizado. Mas agora a
questão é – como essa lógica de validação seria acionada ?
Para acionar a lógica de validação, precisamos criar a instância do validador (podemos
usar o operador new ou a injeção de dependência).
Na instância do
validador, o método Validate ou
ValidateAsync pode ser chamado para acionar a
validação, e, isso leva o objeto a ser validado como entrada e retorna o objeto
ValidationResult. Este objeto de saída possui uma
propriedade IsValid, indicando se o objeto é válido. Se esta propriedade
for falsa, isso significa que a validação falhou e o objeto é inválido.
O ValidationResult também possui uma coleção,
Errors, que como o nome sugere, contém a lista de
erros que são identificados pela lógica de validação. Cada entrada na coleção
Errors é uma instância do tipo ValidationFailure e
cada entrada contém detalhes sobre cada erro de validação.
Aplicando isso ao nosso exemplo vamos alterar o código do controlador EstudantesController no endpoint GetEstudante :
[HttpPost]
public IActionResult GetEstudante([FromBody] Estudante estudante)
{
this.logger.LogInformation($"API Input : {{ Id : {estudante.Id}, Nome: {estudante.Nome}}}");
// Cria uma instância do validador
var estudanteValidator = new EstudanteValidator();
// Chama o método Validate ou ValidateAsync e passa o objeto que queremos validar
var result = estudanteValidator.Validate(estudante);
if (result.IsValid)
{
return Ok(estudante);
}
var errorMessages = result.Errors.Select(x => x.ErrorMessage).ToList();
return BadRequest(errorMessages);
}
|
Neste código acionamos a validação usando o método Validate, e , se o objeto for válido ele será retornado como está, no response. Se o objeto for inválido as mensagens de erro serão selecionadas na coleção de erros e serão retornadas na resposta.
Esta é uma estratégia muito primitiva para mostrar os erros, apenas para demonstrar como funciona a Fluent Validation. Em aplicativos do mundo real, os objetos podem ser mais complexos e você pode usar uma estratégia diferente para garantir que o chamador saiba exatamente onde ocorreu o erro de validação.
Na próxima parte do artigo vamos continuar a tratar de outros recursos da Fluent Validation.
Pegue o projeto aqui : ApiFluentValidation.zip ...
"E do modo por que Moisés levantou a serpente no deserto, assim importa que o
Filho do Homem seja levantado, para que todo o que nele crê tenha a vida
eterna."
João 3:14-15
Referências:
NET - Unit of Work - Padrão Unidade de ...