1. O que este projeto amarra
| Capítulo | Peça aplicada no projeto |
|---|---|
| 01 | Recursos REST: /api/produtos, /api/pedidos |
| 02 | Controllers com [ApiController] |
| 03 | DTOs como record, sem expor entidades |
| 04 | Login com JWT, endpoints protegidos por role |
| 05 | Swagger documentando tudo, incluindo o esquema Bearer |
| 06 | Middleware traduzindo SqlException 50010 em 409 |
| 07 | Paginação na listagem de pedidos |
2. O endpoint de checkout
[ApiController]
[Route("api/pedidos")]
[Authorize]
public class PedidoController : ControllerBase
{
private readonly IPedidoRepository _repositorio;
public PedidoController(IPedidoRepository repositorio) => _repositorio = repositorio;
[HttpPost("checkout")]
[ProducesResponseType(typeof(PedidoCriadoDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<PedidoCriadoDto>> Checkout(CheckoutDto dto)
{
int clienteId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
int pedidoId = await _repositorio.CheckoutAsync(clienteId, dto.Itens);
return CreatedAtAction(nameof(Obter), new { id = pedidoId },
new PedidoCriadoDto(pedidoId));
}
[HttpGet("{id:int}")]
public async Task<ActionResult<PedidoDto>> Obter(int id)
{
var pedido = await _repositorio.ObterPorIdAsync(id);
return pedido is null ? NotFound() : Ok(pedido);
}
[HttpGet]
public async Task<ActionResult<PaginaResultado<PedidoDto>>> Listar(
[FromQuery] int pagina = 1, [FromQuery] int tamanhoPagina = 20)
{
var clienteId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var (itens, total) = await _repositorio.ListarPorClienteAsync(clienteId, pagina, tamanhoPagina);
return Ok(new PaginaResultado<PedidoDto>(itens, pagina, tamanhoPagina, total));
}
}
public record CheckoutDto(List<ItemCarrinhoDto> Itens);
public record ItemCarrinhoDto(int ProdutoId, int Quantidade);
public record PedidoCriadoDto(int PedidoId);
User.FindFirstValue(ClaimTypes.NameIdentifier) lê o Id do cliente
diretamente do JWT decodificado pelo middleware de autenticação (capítulo 4) — o Controller nunca
precisa confiar num clienteId enviado pelo corpo da requisição, o que
fecha uma brecha de segurança óbvia (alguém tentando fazer checkout em nome de outro cliente).
Se CheckoutDto incluísse um campo ClienteId
preenchido pelo próprio cliente da requisição, qualquer usuário autenticado poderia forjar
pedidos em nome de outra pessoa só trocando esse valor. Extrair o Id do token assinado
(JWT) garante que o pedido é sempre atribuído a quem realmente está autenticado.
3. O middleware de erro, especializado para este projeto
static (int Status, string Mensagem) MapearErro(Exception? excecao) => excecao switch
{
SqlException { Number: 50010 } => (StatusCodes.Status409Conflict, "Estoque insuficiente para um ou mais itens"),
ArgumentException => (StatusCodes.Status400BadRequest, excecao.Message),
_ => (StatusCodes.Status500InternalServerError, "Erro interno"),
};
4. Program.cs completo
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<IPedidoRepository>(_ =>
new PedidoRepository(builder.Configuration.GetConnectionString("Default")!));
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opcoes => { /* ...configuração do capítulo 4... */ });
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(/* ...configuração do capítulo 5, incluindo Bearer... */);
var app = builder.Build();
app.UseExceptionHandler(appErro => appErro.Run(async context =>
{
var (status, mensagem) = MapearErro(context.Features.Get<IExceptionHandlerFeature>()?.Error);
context.Response.StatusCode = status;
await context.Response.WriteAsJsonAsync(new ProblemDetails { Status = status, Title = mensagem });
}));
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
5. Onde isso te leva
Com uma API REST completa, segura e documentada, os dois módulos finais deste curso saem do universo "requisição/resposta" para dois outros formatos de aplicação C#: serviços que rodam continuamente em segundo plano (Worker Services) e aplicações com interface gráfica própria (Desktop) — ambos ainda se conectando às mesmas procedures do Módulo 2.