Pular para o conteúdo
.NETRelacionamentos pai/filho
Módulo 03Construindo a Web API

Relacionamentos pai/filho

intermediario 30 min de leitura·Atualizado · .NET 9
Resumo

Expomos 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#

  1. Serviço que valida o pai primeiro:
C#
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);
}
  1. Controller com rota aninhada:
C#
[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#

Shell
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çaEvite
Validar o pai antes de buscar filhosBuscar filhos de um pai inexistente e retornar lista vazia
Usar rotas aninhadas para relacionamentos fortesAninhar profundamente (3+ níveis) sem necessidade
Nomear a rota do filho (ProdutoById)Gerar links por concatenação
Atenção

evite 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 404 distinto 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#

20-02 — Relacionamentos pai/filho | Curso ASP.NET Core