Como tratar erros em aplicativos de API mínima

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Com contribuições de David Acker

Este artigo descreve como tratar erros em aplicativos de API mínima.

Exceções

Em um aplicativo de API mínima, há dois mecanismos centralizados internos diferentes para lidar com exceções sem tratamento:

Esta seção se refere ao aplicativo de API mínima a seguir para demonstrar formas de tratar exceções. Ele gera uma exceção quando o ponto de extremidade /exception é solicitado:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Página de exceção do desenvolvedor

A Página de exceção do desenvolvedor mostra rastreamentos de pilha detalhados para erros de servidor. Ele usa DeveloperExceptionPageMiddleware para capturar exceções síncronas e assíncronas do pipeline HTTP e para gerar respostas de erro.

Os aplicativos ASP.NET Core habilitam a página de exceção do desenvolvedor por padrão quando ocorrerem estes dois pontos:

Para obter mais informações sobre como configurar o middleware, confira Middleware em aplicativos de API mínima.

Ao usar o aplicativo de API mínima anterior, quando a Developer Exception Page detectar uma exceção sem tratamento, será gerada uma resposta de texto sem formatação padrão semelhante ao exemplo a seguir:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Oct 2022 18:00:59 GMT
Server: Kestrel
Transfer-Encoding: chunked

    System.InvalidOperationException: Sample Exception
    at Program.<>c.<<Main>$>b__0_1() in ....:line 17
    at lambda_method2(Closure, Object, HttpContext)
    at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5239
Accept-Encoding: gzip, deflate, br

Aviso

Não habilite a Página de exceção do desenvolvedor a menos que o aplicativo esteja em execução no Ambiente de desenvolvimento. Não compartilhe informações de exceção detalhadas publicamente quando o aplicativo é executado em produção. Para obter mais informações sobre como configurar ambientes, confira Use vários ambientes no ASP.NET Core.

Manipulador de exceção

Em ambientes de não desenvolvimento, use o Middleware do manipulador de exceções para produzir um conteúdo de erro. Para configurar o Exception Handler Middleware, chame UseExceptionHandler.

Por exemplo, o código a seguir altera o aplicativo para responder com um conteúdo compatível com RFC 7807 para o cliente. Para obter mais informações, confira Detalhes do problema.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp 
    => exceptionHandlerApp.Run(async context 
        => await Results.Problem()
                     .ExecuteAsync(context)));

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Respostas de erro de cliente e servidor

Cogite o aplicativo de API mínima a seguir.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

O ponto de extremidade /users produz 200 OK com uma representação json do User quando id for maior que 0, caso contrário, produzirá um código de status 400 BAD REQUEST sem um corpo de resposta. Para obter mais informações sobre como criar uma resposta, confira Criar respostas em aplicativos de API mínimo.

O Status Code Pages middleware pode ser configurado para produzir um conteúdo de corpo comum, quando vazio, para todas as respostas de cliente HTTP (400-499) ou servidor (500 -599). O middleware é configurado chamando o método de extensão UseStatusCodePages.

O exemplo a seguir altera o aplicativo para responder com uma carga compatível com RFC 7807 para o cliente para todas as respostas de cliente e servidor, incluindo erros de roteamento (por exemplo, 404 NOT FOUND). Para obter mais informações, confira a seção Detalhes do problema.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStatusCodePages(async statusCodeContext 
    => await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
                 .ExecuteAsync(statusCodeContext.HttpContext));

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Detalhes do problema

Os Detalhes do problema não são o único formato de resposta a descrever um erro de API HTTP; no entanto, eles geralmente são usados para relatar erros para APIs HTTP.

O serviço de detalhes do problema implementa a interface IProblemDetailsService, que dá suporte à criação de detalhes do problema no ASP.NET Core. O método de extensão AddProblemDetails em IServiceCollection regista a implementação IProblemDetailsService padrão.

Em aplicativos ASP.NET Core, o middleware abaixo gera respostas HTTP de detalhes do problema quando AddProblemDetails é chamado, exceto quando o cabeçalho HTTP da solicitação Accept não inclui um dos tipos de conteúdo com suporte pelo IProblemDetailsWriter registrado (padrão: application/json):

Aplicativos de API mínima podem ser configurados para gerar uma resposta de detalhes do problema para todas as respostas de erro de cliente e servidor HTTP que ainda não tenham um conteúdo de corpo usando o método de extensão AddProblemDetails.

O código a seguir configura o aplicativo para gerar detalhes do problema:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Para obter mais informações sobre como usar AddProblemDetails, confira Detalhes do problema

Fallback de IProblemDetailsService

No código a seguir, httpContext.Response.WriteAsync("Fallback: An error occurred.") retornará um erro se a implementação IProblemDetailsService não conseguir gerar um ProblemDetails:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/exception", () =>
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

O código anterior:

O exemplo a seguir é semelhante ao anterior, exceto que ele chama o Status Code Pages middleware.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseStatusCodePages(statusCodeHandlerApp =>
{
    statusCodeHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/users/{id:int}", (int id) =>
{
    return id <= 0 ? Results.BadRequest() : Results.Ok(new User(id));
});

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Este artigo descreve como tratar erros em aplicativos de API mínima.

Exceções

Em um aplicativo de API mínima, há dois mecanismos centralizados internos diferentes para lidar com exceções sem tratamento:

Esta seção se refere ao aplicativo de API mínima a seguir para demonstrar formas de tratar exceções. Ele gera uma exceção quando o ponto de extremidade /exception é solicitado:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample Exception"); });

app.Run();

Página de exceção do desenvolvedor

A Página de exceção do desenvolvedor mostra rastreamentos de pilha detalhados para erros de servidor. Ele usa DeveloperExceptionPageMiddleware para capturar exceções síncronas e assíncronas do pipeline HTTP e para gerar respostas de erro.

Os aplicativos ASP.NET Core habilitam a página de exceção do desenvolvedor por padrão quando ocorrerem estes dois pontos:

Para obter mais informações sobre como configurar o middleware, confira Middleware em aplicativos de API mínima.

Ao usar o aplicativo de API mínima anterior, quando a Developer Exception Page detectar uma exceção sem tratamento, será gerada uma resposta de texto sem formatação padrão semelhante ao exemplo a seguir:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Oct 2022 18:00:59 GMT
Server: Kestrel
Transfer-Encoding: chunked

    System.InvalidOperationException: Sample Exception
    at Program.<>c.<<Main>$>b__0_1() in ....:line 17
    at lambda_method2(Closure, Object, HttpContext)
    at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5239
Accept-Encoding: gzip, deflate, br

Aviso

Não habilite a Página de exceção do desenvolvedor a menos que o aplicativo esteja em execução no Ambiente de desenvolvimento. Não compartilhe informações de exceção detalhadas publicamente quando o aplicativo é executado em produção. Para obter mais informações sobre como configurar ambientes, confira Use vários ambientes no ASP.NET Core.

Manipulador de exceção

Em ambientes de não desenvolvimento, use o Middleware do manipulador de exceções para produzir um conteúdo de erro. Para configurar o Exception Handler Middleware, chame UseExceptionHandler.

Por exemplo, o código a seguir altera o aplicativo para responder com um conteúdo compatível com RFC 7807 para o cliente. Para obter mais informações, confira Detalhes do problema.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp 
    => exceptionHandlerApp.Run(async context 
        => await Results.Problem()
                     .ExecuteAsync(context)));

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample Exception"); });

app.Run();

Respostas de erro de cliente e servidor

Cogite o aplicativo de API mínima a seguir.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Run();

public record User(int Id);

O ponto de extremidade /users produz 200 OK com uma representação json do User quando id for maior que 0, caso contrário, produzirá um código de status 400 BAD REQUEST sem um corpo de resposta. Para obter mais informações sobre como criar uma resposta, confira Criar respostas em aplicativos de API mínimo.

O Status Code Pages middleware pode ser configurado para produzir um conteúdo de corpo comum, quando vazio, para todas as respostas de cliente HTTP (400-499) ou servidor (500 -599). O middleware é configurado chamando o método de extensão UseStatusCodePages.

O exemplo a seguir altera o aplicativo para responder com uma carga compatível com RFC 7807 para o cliente para todas as respostas de cliente e servidor, incluindo erros de roteamento (por exemplo, 404 NOT FOUND). Para obter mais informações, confira a seção Detalhes do problema.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStatusCodePages(async statusCodeContext 
    =>  await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
                 .ExecuteAsync(statusCodeContext.HttpContext));

app.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Run();

public record User(int Id);

Detalhes do problema

Os Detalhes do problema não são o único formato de resposta a descrever um erro de API HTTP; no entanto, eles geralmente são usados para relatar erros para APIs HTTP.

O serviço de detalhes do problema implementa a interface IProblemDetailsService, que dá suporte à criação de detalhes do problema no ASP.NET Core. O método de extensão AddProblemDetails em IServiceCollection regista a implementação IProblemDetailsService padrão.

Em aplicativos ASP.NET Core, o middleware abaixo gera respostas HTTP de detalhes do problema quando AddProblemDetails é chamado, exceto quando o cabeçalho HTTP da solicitação Accept não inclui um dos tipos de conteúdo com suporte pelo IProblemDetailsWriter registrado (padrão: application/json):

Aplicativos de API mínima podem ser configurados para gerar uma resposta de detalhes do problema para todas as respostas de erro de cliente e servidor HTTP que ainda não tenham um conteúdo de corpo usando o método de extensão AddProblemDetails.

O código a seguir configura o aplicativo para gerar detalhes do problema:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample Exception"); });

app.Run();

Para obter mais informações sobre como usar AddProblemDetails, confira Detalhes do problema