Validação e refatoração com filters
ResumoMovemos a validação repetida (checagem de nulos e
ModelState) para um Action Filter reutilizável, deixando os controllers focados só no fluxo principal.
1. Objetivos de Aprendizagem#
- Extrair validação recorrente para um Action Filter.
- Curto-circuitar a requisição a partir do filtro quando inválida.
- Simplificar os controllers após a refatoração.
2. Pré-requisitos#
- Action Filters (tópico 29.1); validação (Módulo 27).
3. Conceito#
Mesmo com [ApiController] cuidando de ModelState, há validações comuns que se repetem — por exemplo, garantir que o DTO recebido não é nulo e que o ModelState é válido antes de prosseguir com regras extras. Um filtro centraliza isso e pode curto-circuitar a ação definindo context.Result.
4. Mão na Massa#
4.1. Setup#
Sem pacotes novos.
4.2. Implementação Passo a Passo#
- Filtro de validação:
public class ValidationFilterAttribute : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Garante que o DTO (parâmetro terminado em "Dto") não é nulo
var dto = context.ActionArguments
.SingleOrDefault(x => x.Key.EndsWith("Dto", StringComparison.OrdinalIgnoreCase)).Value;
if (dto is null)
{
context.Result = new BadRequestObjectResult("O objeto enviado é nulo.");
return;
}
if (!context.ModelState.IsValid)
context.Result = new UnprocessableEntityObjectResult(context.ModelState);
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
- Registro:
builder.Services.AddScoped<ValidationFilterAttribute>();
- Aplicação na ação:
[HttpPost]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public async Task<IActionResult> Criar(Guid categoriaId, [FromBody] ProdutoForCreationDto dto)
{
var criado = await _service.Produto.CreateAsync(categoriaId, dto, false);
return CreatedAtRoute("ProdutoById", new { categoriaId, id = criado.Id }, criado);
}
4.3. Executando#
Envie um corpo nulo ou inválido: o filtro responde 400/422 antes de a ação rodar.
5. Exemplo Completo#
Antes: cada ação repetia checagens de nulo e ModelState. Depois: os controllers ficam limpos, com a validação concentrada no filtro e aplicável a qualquer endpoint.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Centralizar validação repetida em um filtro | Repetir if (dto is null) em cada ação |
Curto-circuitar via context.Result | Lançar exceção para controlar fluxo comum |
| Aplicar o filtro só onde faz sentido | Aplicar globalmente e afetar GETs sem DTO |
Atençãoum filtro de validação global aplicado a ações sem DTO (GETs) apenas adiciona overhead. Prefira o escopo de ação/controller para endpoints de escrita.
7. Segurança e Produção#
- Validar cedo e curto-circuitar economiza processamento e reduz superfície de erro.
8. Exercícios#
- Fácil: aplique o filtro ao endpoint de
PUT. - Médio: faça o filtro também rejeitar coleções vazias.
- Desafio: transforme o filtro numa versão assíncrona (
IAsyncActionFilter) e compare.
9. Resumo#
Extraímos a validação recorrente para um Action Filter reutilizável que curto-circuita requisições inválidas, deixando os controllers enxutos e a validação consistente.
10. Próximos Passos#
Módulo 30: paginação para lidar com grandes coleções.
11. Referências#
- Microsoft Learn — Filtros no ASP.NET Core
- Microsoft Learn — Curto-circuito com filtros
- Microsoft Learn — Validação de modelo