O corpo não substitui o estado
Uma string devolvida diretamente por um Minimal API é escrita como texto, com text/plain no comportamento predefinido. Um objeto comum é serializado como JSON. Nenhuma destas escolhas deve ser confundida com a decisão de estado: TypedResults.Ok("404") continua a representar sucesso HTTP, embora o texto pareça um código de erro.
Se o contrato exige 404 para recurso ausente, usa um resultado que expresse esse estado. Um objeto { error = "missing" } dentro de Ok obriga o cliente a adivinhar uma convenção contraditória. Define primeiro o resultado da operação e depois o estado, os campos e a representação correspondentes.
Alternativas tipadas do handler
TypedResults devolve tipos concretos de resultados. Results<Ok<Item>, NotFound> declara duas alternativas e conserva metadata útil para descrever a resposta. O handler pode devolver Ok<Item> ou NotFound; acrescentar BadRequest nesse corpo exige rever o tipo de retorno para incluir essa alternativa.
Esta é uma restrição sobre os retornos da função. Um pedido pode falhar em autenticação, routing ou binding antes de Find. Não interpretes a união como garantia de que a aplicação inteira só pode responder 200 ou 404. O contrato público deve considerar essas fronteiras e o middleware efetivamente configurado.
Descrever não executa a operação
O tipo concreto de TypedResults pode fornecer metadata de resposta automaticamente. Se um helper declarar apenas IResult, a assinatura perde parte dessa informação estática. Conservar o tipo concreto, declarar a união correta ou fornecer metadata explícita coerente são opções para recuperar uma descrição útil.
Essa metadata não grava dados, não verifica permissões e não transforma um ramo Ok num erro. Uma descrição que anuncia 404 enquanto o handler devolve 200 com error=missing precisa de correção no comportamento. As revisões devem comparar assinatura, ramos e descrição, sem usar a documentação como prova de efeitos que o código não executa.
Compor Problem Details
AddProblemDetails regista o serviço de representação de erros. Middleware de exceções e de páginas de estado liga situações do pipeline a esse serviço. No exemplo, UseExceptionHandler trata exceções a jusante e UseStatusCodePages pode produzir uma representação para estados de erro sem corpo, quando a escrita é suportada.
O registo isolado não envolve automaticamente todos os handlers num catch. Também não promete reescrever um corpo já emitido. Para aceitar o contrato, identifica a origem do erro, a existência de corpo, o momento em que a resposta começa e o writer disponível. Só depois conclui qual representação pública pode ser produzida.
Detalhes públicos e diagnóstico
A Developer Exception Page é destinada ao ambiente de desenvolvimento. Pode incluir stack trace, parâmetros da query, cookies e cabeçalhos. Disponibilizá-la a visitantes em produção transforma detalhes de investigação em dados públicos, sem que isso seja necessário ao contrato do cliente.
Um erro público deve permitir interpretar a falha com campos delimitados. A investigação pode usar registos internos apropriados, com controlo do que é recolhido. Ocultar a stack mas copiar mensagens de SQL, cookies ou o pedido inteiro para detail não resolve essa fronteira. O formato Problem Details também não torna automaticamente seguro qualquer texto nele colocado.
Negociar também os erros
O writer predefinido de Problem Details suporta representações JSON e os tipos Accept compatíveis documentados. Um cliente que só aceita application/xml não ganha suporte XML por existir AddProblemDetails. Se TryWriteAsync devolver false, o ramo precisa de uma política explícita; não se deve documentar um corpo que não foi escrito.
As opções incluem acordar formatos suportados com o cliente, fornecer um writer adequado ou definir o fallback e a negociação pública. Repetir a mesma chamada não acrescenta suporte. A escolha deve preservar o estado da falha e ser coerente com o contrato, incluindo situações em que já não é possível alterar a resposta.
Ler o contrato completo
O catálogo do exemplo é deliberadamente fixo: só existe Item 42. Para /items/42, Find devolve Ok<Item>; para /items/7, NotFound. Com Accept compatível, o tratamento de estados pode preencher o erro sem corpo. Para /items/blue, o binding int falha antes de Find. Não existe base de dados nem uma alegação de pesquisa em produção.
Ao rever as alternativas de resposta, distingue o resultado do handler do resultado final do pipeline. Uma evidência de 400 anterior não invalida a união da função; mostra outra fronteira a documentar. As perguntas pedem interpretar esses ramos e escolher a correção do contrato, sem executar C# ou publicar uma API.
// Program.cs: ASP.NET Core 10.0 Web SDK, net10.0, ImplicitUsings enabled.
// Fixed teaching catalogue: item 42 exists; no database or external call.
using Microsoft.AspNetCore.Http.HttpResults;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.UseStatusCodePages();
app.MapGet("/items/{id}", Find);
app.Run();
static Results<Ok<Item>, NotFound> Find(int id)
{
if (id == 42)
return TypedResults.Ok(new Item(42, "Training item"));
return TypedResults.NotFound();
}
public sealed record Item(int Id, string Name);
Com Accept compatível com o writer, /items/42 devolve 200 com o Item do modelo. /items/7 devolve 404 e o middleware pode produzir Problem Details para o erro sem corpo. /items/blue falha o binding antes de Find. A união do handler não enumera esse 400.
Armadilhas comuns
Devolver Ok com texto 404; assumir que TypedResults comprova persistência; tratar a união de retornos como lista de todos os erros do pipeline; anunciar JSON de erro sem verificar o writer e a negociação.
Tópicos relacionados: Rotas, binding e validação · Âmbito de testes e evidência de integração · Saúde, observabilidade e diagnóstico
O estado e a representação devem refletir o resultado real. Tipos e metadata ajudam a descrever o handler; a composição do pipeline e a política de erros continuam a determinar outras respostas.
Referência: Plain string response · ASP.NET Core 10.0 / .NET 10 / C# 14