1. API Controller — igual ao MVC, devolvendo dados em vez de HTML
Se você entendeu o Módulo 3, já sabe 90% disto — a diferença é que em vez de
Controller (que devolve Views), uma Web API herda de
ControllerBase e devolve dados diretamente:
[ApiController]
[Route("api/clientes")]
public class ClienteController : ControllerBase
{
private readonly IClienteRepository _repositorio;
public ClienteController(IClienteRepository repositorio) => _repositorio = repositorio;
[HttpGet]
public async Task<ActionResult<List<ClienteDto>>> Listar([FromQuery] string? nome, [FromQuery] bool? ativo)
{
var clientes = await _repositorio.ListarAsync(nome, ativo);
return Ok(clientes.Select(ClienteMapper.ParaDto).ToList());
}
[HttpGet("{id:int}")]
public async Task<ActionResult<ClienteDto>> ObterPorId(int id)
{
var cliente = await _repositorio.ObterPorIdAsync(id);
return cliente is null ? NotFound() : Ok(ClienteMapper.ParaDto(cliente));
}
[HttpPost]
public async Task<ActionResult<ClienteDto>> Criar(ClienteCadastroDto dto)
{
var novoId = await _repositorio.InserirAsync(ClienteMapper.ParaEntidade(dto));
var criado = await _repositorio.ObterPorIdAsync(novoId);
return CreatedAtAction(nameof(ObterPorId), new { id = novoId }, ClienteMapper.ParaDto(criado!));
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> Excluir(int id)
{
await _repositorio.ExcluirAsync(id);
return NoContent();
}
}
| Método | Status devolvido |
|---|---|
Ok(dado) | 200, com o dado no corpo |
NotFound() | 404 |
CreatedAtAction(...) | 201, com header Location apontando para ObterPorId |
NoContent() | 204, sem corpo |
[ApiController]
Habilita comportamentos automáticos específicos de API: validação de ModelState
automática (devolve 400 sozinho, sem você checar
ModelState.IsValid manualmente), inferência de origem dos parâmetros,
e respostas de erro em formato padronizado.
2. Minimal APIs — a alternativa mais direta
Introduzidas no .NET 6, Minimal APIs dispensam a classe Controller inteira para casos simples —
tudo é declarado direto no Program.cs:
var app = builder.Build();
var clientes = app.MapGroup("/api/clientes");
clientes.MapGet("/", async (IClienteRepository repo, string? nome, bool? ativo) =>
{
var lista = await repo.ListarAsync(nome, ativo);
return Results.Ok(lista.Select(ClienteMapper.ParaDto));
});
clientes.MapGet("/{id:int}", async (IClienteRepository repo, int id) =>
{
var cliente = await repo.ObterPorIdAsync(id);
return cliente is null ? Results.NotFound() : Results.Ok(ClienteMapper.ParaDto(cliente));
});
clientes.MapPost("/", async (IClienteRepository repo, ClienteCadastroDto dto) =>
{
var novoId = await repo.InserirAsync(ClienteMapper.ParaEntidade(dto));
return Results.Created($"/api/clientes/{novoId}", new { id = novoId });
});
clientes.MapDelete("/{id:int}", async (IClienteRepository repo, int id) =>
{
await repo.ExcluirAsync(id);
return Results.NoContent();
});
app.Run();
Repare que a dependência (IClienteRepository repo) é recebida
diretamente como parâmetro da função — o container de DI (Módulo 3, capítulo 6) resolve isso
automaticamente, sem precisar de um construtor de classe.
3. Controllers vs. Minimal APIs — quando usar cada um
| Controllers | Minimal APIs | |
|---|---|---|
| Organização | Uma classe por recurso, actions agrupadas | Endpoints declarados individualmente (ou agrupados com MapGroup) |
| Melhor para | APIs grandes, muitos endpoints relacionados, filters/binding complexo | APIs pequenas/médias, microsserviços, quando simplicidade é prioridade |
| Curva de aprendizado | Mais convenções para entender | Mais direto para quem já conhece Express/Node (rota + handler) |
Minimal APIs devem parecer bem próximas de Express: app.MapGet(rota, handler)
lê quase como app.get(rota, handler). A diferença central continua
sendo a tipagem — o handler C# recebe parâmetros já tipados e validados, sem parsing manual do
corpo da requisição.
4. Onde isso te leva
Com os dois estilos de implementação cobertos, o próximo capítulo aprofunda DTOs (que você já usou nos exemplos acima) e como controlar exatamente o formato JSON que entra e sai da API — incluindo versionamento, para quando a API precisa evoluir sem quebrar clientes existentes.