Recurso único e requisições inválidas
ResumoBuscamos um recurso por
id, retornando404 Not Foundquando ele não existe. A verificação de existência fica na Service Layer, mantendo o controller limpo.
1. Objetivos de Aprendizagem#
- Implementar
GET /recurso/{id}com resposta404adequada. - Centralizar a validação de existência na Service Layer.
- Usar exceções de domínio integradas ao handler global (Módulo 19).
2. Pré-requisitos#
- Endpoints GET e tratamento global de erros (Módulos 04–05).
3. Conceito#
Buscar um único recurso exige responder corretamente quando ele não existe: 404 Not Found, não 200 com corpo nulo nem 500. O porquê: o status code é parte do contrato — clientes automatizam decisões com base nele.
Colocar a checagem de existência na Service Layer evita duplicar if (x is null) return NotFound() em cada ação; lançamos uma NotFoundException que o handler global converte em 404.
4. Mão na Massa#
4.1. Setup#
Reuse o GlobalExceptionHandler do Módulo 19.
4.2. Implementação Passo a Passo#
- Exceção de domínio:
public abstract class NotFoundException : Exception
{
protected NotFoundException(string message) : base(message) { }
}
public sealed class CategoriaNotFoundException : NotFoundException
{
public CategoriaNotFoundException(Guid id)
: base($"A categoria com id {id} não foi encontrada.") { }
}
- Serviço com verificação:
public async Task<CategoriaDto> GetByIdAsync(Guid id, bool trackChanges)
{
var categoria = await _repo.Categoria.GetByIdAsync(id, trackChanges)
?? throw new CategoriaNotFoundException(id);
return _mapper.Map<CategoriaDto>(categoria);
}
- Controller enxuto:
[HttpGet("{id:guid}", Name = "CategoriaById")]
public async Task<IActionResult> GetCategoria(Guid id)
{
var categoria = await _service.Categoria.GetByIdAsync(id, trackChanges: false);
return Ok(categoria);
}
4.3. Executando#
curl -i http://localhost:5000/api/categorias/00000000-0000-0000-0000-000000000000
# HTTP/1.1 404 Not Found (ProblemDetails no corpo)
5. Exemplo Completo#
Fluxo: controller → serviço (lança CategoriaNotFoundException) → handler global → resposta 404 ProblemDetails.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
Retornar 404 para recurso inexistente | Retornar 200 com corpo null |
| Centralizar a checagem de existência no serviço | Repetir if null em todo controller |
Nomear a rota para uso em CreatedAtRoute | Construir a URL do recurso manualmente |
Atençãoexceções são para fluxos excepcionais. Se "não encontrado" for um caminho extremamente frequente e esperado, considere um result pattern (Módulo 44) para evitar o custo de exceções.
7. Segurança e Produção#
- A mensagem de erro não deve revelar se o
idexiste para outro usuário (evite enumeration attacks em recursos sensíveis).
8. Exercícios#
- Fácil: crie
ProdutoNotFoundExceptione use-a no serviço de produtos. - Médio: retorne
400 Bad Requestquando oidnão for um GUID válido (dica: route constraint). - Desafio: compare o custo de exceções vs. result pattern num benchmark simples.
9. Resumo#
Buscamos recurso único retornando 404 via exceção de domínio tratada globalmente, mantendo o controller limpo e o comportamento HTTP correto.
10. Próximos Passos#
A seguir, relacionamentos pai/filho: obter os produtos de uma categoria.
11. Referências#
- Microsoft Learn — Tipos de retorno de ações
- Microsoft Learn — Tratar erros em APIs web
- Microsoft Learn — Route constraints