Relacionamentos pai/filho
ResumoExpomos recursos filhos por rotas aninhadas (
/api/categorias/{categoriaId}/produtos), validando primeiro a existência do pai antes de buscar os filhos.
1. Objetivos de Aprendizagem#
- Modelar rotas aninhadas para relacionamentos 1:N.
- Validar a existência do recurso pai antes de acessar filhos.
- Buscar um filho específico dentro de um pai.
2. Pré-requisitos#
- Recurso único e
NotFoundException(tópico 20.1).
3. Conceito#
Quando um recurso pertence a outro (produto pertence a categoria), a URL expressa essa hierarquia. O porquê: comunica o relacionamento e permite validar o contexto — não faz sentido buscar produtos de uma categoria que não existe. Nesse caso retornamos 404 para o pai antes de consultar filhos.
4. Mão na Massa#
4.1. Setup#
Reuse repositórios e o handler global.
4.2. Implementação Passo a Passo#
- Serviço que valida o pai primeiro:
public async Task<IEnumerable<ProdutoDto>> GetProdutosAsync(Guid categoriaId, bool trackChanges)
{
_ = await _repo.Categoria.GetByIdAsync(categoriaId, trackChanges)
?? throw new CategoriaNotFoundException(categoriaId);
var produtos = await _repo.Produto.GetByCategoriaAsync(categoriaId, trackChanges);
return _mapper.Map<IEnumerable<ProdutoDto>>(produtos);
}
- Controller com rota aninhada:
[ApiController]
[Route("api/categorias/{categoriaId:guid}/produtos")]
public class ProdutosController : ControllerBase
{
private readonly IServiceManager _service;
public ProdutosController(IServiceManager service) => _service = service;
[HttpGet]
public async Task<IActionResult> GetProdutos(Guid categoriaId)
{
var produtos = await _service.Produto.GetProdutosAsync(categoriaId, trackChanges: false);
return Ok(produtos);
}
[HttpGet("{id:guid}", Name = "ProdutoById")]
public async Task<IActionResult> GetProduto(Guid categoriaId, Guid id)
{
var produto = await _service.Produto.GetProdutoAsync(categoriaId, id, trackChanges: false);
return Ok(produto);
}
}
4.3. Executando#
curl http://localhost:5000/api/categorias/{catId}/produtos
5. Exemplo Completo#
O serviço de produto único segue o mesmo padrão: valida a categoria, depois busca o produto, lançando ProdutoNotFoundException se necessário.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Validar o pai antes de buscar filhos | Buscar filhos de um pai inexistente e retornar lista vazia |
| Usar rotas aninhadas para relacionamentos fortes | Aninhar profundamente (3+ níveis) sem necessidade |
Nomear a rota do filho (ProdutoById) | Gerar links por concatenação |
Atençãoevite aninhamento excessivo. Rotas com muitos níveis (
/a/{}/b/{}/c/{}) ficam frágeis; às vezes um recurso de topo com filtro é mais claro.
7. Segurança e Produção#
- Verifique que o filho realmente pertence ao pai informado, evitando IDOR (acesso a um produto de outra categoria/usuário via troca de id).
8. Exercícios#
- Fácil: implemente
GET .../produtos/{id}retornando um produto. - Médio: retorne
404distinto para "categoria não existe" e "produto não existe na categoria". - Desafio: adicione verificação de propriedade (o produto pertence à categoria da rota) e teste o cenário de IDOR.
9. Resumo#
Rotas aninhadas expressam relacionamentos 1:N; validamos o pai antes dos filhos e reutilizamos exceções de domínio para respostas 404 corretas.
10. Próximos Passos#
Módulo 21: content negotiation — deixar o cliente escolher o formato da resposta.
11. Referências#
- Microsoft Learn — Roteamento por atributos
- Microsoft Learn — Relacionamentos no EF Core
- OWASP — Insecure Direct Object References (IDOR)