ASP.NET Core para de transformar 413 em 500 no UseExceptionHandler
Um PR integrado à main do dotnet/aspnetcore em 2026-08-19 faz o ExceptionHandlerMiddleware respeitar BadHttpRequestException.StatusCode em vez de sobrescrevê-lo com 500.
Se você usa app.UseExceptionHandler() em produção, toda requisição que o Kestrel rejeita por ser grande demais vem aparecendo na sua telemetria como falha do servidor. O PR #68632 entrou na main do dotnet/aspnetcore em 2026-08-19 e corrige isso. Ele fecha a issue #43831, aberta em setembro de 2022.
O 500 que na verdade era um 413
O ExceptionHandlerMiddleware define o código de status da resposta antes de invocar o seu handler e, até este PR, fixava 500 sempre que ExceptionHandlerOptions.StatusCodeSelector era null. A BadHttpRequestException carrega o próprio StatusCode, e esse valor era descartado.
O formato é este, verificado contra o ASP.NET Core 10.0.0 no SDK 10.0.201:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
builder.WebHost.ConfigureKestrel(k => k.Limits.MaxRequestBodySize = 100);
var app = builder.Build();
app.UseExceptionHandler();
app.MapPost("/upload", async (HttpContext ctx) =>
{
using var ms = new MemoryStream();
await ctx.Request.Body.CopyToAsync(ms); // throws when the body exceeds 100 bytes
return Results.Ok(ms.Length);
});
app.Run();
Faça um POST de 500 bytes para /upload. A exceção que chega ao middleware é BadHttpRequestException com StatusCode = 413 e a mensagem “Request body too large. The max request body size is 100 bytes.” A resposta que você realmente recebe é:
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
{"type":"https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title":"An error occurred while processing your request.","status":500,...}
O cliente é informado de que quebrou o servidor. Seus painéis de 5xx concordam. É a mesma classe de confusão por trás de 413 Request Entity Too Large ao enviar um arquivo, só que aqui o status correto nunca chega à rede.
O que mudou
Agora o middleware faz correspondência de padrões na exceção antes de cair no 500:
context.Response.StatusCode = _options.StatusCodeSelector?.Invoke(edi.SourceException)
?? (edi.SourceException switch
{
BadHttpRequestException badHttpRequestException => badHttpRequestException.StatusCode,
_ => DefaultStatusCode,
});
Três detalhes que vale conhecer. O StatusCodeSelector continua tendo prioridade se você definir um, então sobrescritas existentes mantêm o comportamento. Delegates ExceptionHandler personalizados e serviços IExceptionHandler ainda podem alterar o código depois. E um 404 carregado por uma BadHttpRequestException passa a ser tratado como deliberado, e não como handler mal configurado, então não precisa mais de AllowStatusCode404Response = true para sobreviver.
O escopo é estreito de propósito: apenas BadHttpRequestException é remapeada. Chamar Request.ReadFormAsync() com um corpo text/plain lança InvalidOperationException (“Incorrect Content-Type”), e isso continua devolvendo 500 antes e depois. O model binding das minimal APIs também não é afetado, porque um corpo JSON malformado vira um 400 seco pelo request delegate antes que qualquer exceção escape.
No momento em que escrevo, o commit está apenas na main. Ele não está no branch release/11.0-rc1, então espere-o em um build posterior do .NET 11, e não no RC1. Se você está hoje no .NET 8 até 11, a alternativa continua sendo um StatusCodeSelector que desempacote a exceção por conta própria.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.