Como lidar com erros em APIs Web do ASP.NET Core

Este artigo descreve como lidar com erros e personalizar o tratamento de erros com APIs web do ASP.NET Core.

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. Por exemplo, considere a seguinte ação do controlador, que gera uma exceção:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

Quando a Página de Exceção do Desenvolvedor detecta uma exceção sem tratamento, ela gera 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
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

Se o cliente solicitar uma resposta formatada em HTML, a Página de Exceção do Desenvolvedor gerará uma resposta semelhante ao exemplo a seguir:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

Para solicitar uma resposta formatada em HTML, defina o cabeçalho da solicitação HTTP Accept como text/html.

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 de Tratamento de Exceções para produzir um payload de erro:

  1. Em Program.cs, chame UseExceptionHandler para adicionar o Middleware de Tratamento de Exceções:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Configure uma ação do controlador para responder à rota /error:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

A ação HandleError anterior envia um payload compatível com RFC 7807 para o cliente.

Aviso

Não marque o método de ação do manipulador de erro com atributos de método HTTP, como HttpGet. Verbos explícitos impedem algumas solicitações de chegar ao método da ação.

Para APIs web que usam Swagger/OpenAPI, marque a ação do manipulador de erros com o atributo [ApiExplorerSettings] e defina sua propriedade IgnoreApi como true. Essa configuração de atributo exclui a ação do manipulador de erros da especificação OpenAPI do aplicativo:

[ApiExplorerSettings(IgnoreApi = true)]

Permita o acesso anônimo ao método se os usuários não autenticados tiverem que ver o erro.

O Middleware de Tratamento de Exceções também pode ser usado no ambiente de desenvolvimento para produzir um formato de payload consistente em todos os ambientes:

  1. No Program.cs, registre instâncias de Middleware de Tratamento de Exceção específicas do ambiente:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    No código anterior, o middleware é registrado com:

    • uma rota de /error-development no ambiente de Desenvolvimento.
    • Uma rota de /error em ambientes de Não Desenvolvimento.

  2. Adicione ações de controlador para as rotas Desenvolvimento e Não Desenvolvimento:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Usar exceções para modificar a resposta

O conteúdo da resposta pode ser modificado de fora do controlador usando uma exceção personalizada e um filtro de ação:

  1. Crie um tipo de exceção conhecido chamado HttpResponseException:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. Crie um filtro de ação chamado HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    O filtro anterior especifica um Order do valor inteiro máximo menos 10. Esse Order permite que outros filtros sejam executados no final do pipeline.

  3. Em Program.cs, adicione o filtro de ação à coleção de filtros:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

Resposta de erro de falha de validação

Para controladores de API web, o MVC responde com um tipo de resposta ValidationProblemDetails quando a validação do modelo falha. O MVC usa os resultados de InvalidModelStateResponseFactory para construir a resposta de erro para uma falha de validação. O exemplo a seguir substitui a fábrica padrão por uma implementação que também dá suporte à formatação de respostas como XML, em Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

Resposta de erro do cliente

Um resultado de erro é definido como um resultado com um código de status HTTP de 400 ou superior. Para controladores de API web, o MVC transforma um resultado de erro para produzir um ProblemDetails.

A criação automática de um ProblemDetails para códigos de status de erro é habilitada por padrão, mas as respostas de erro podem ser configuradas usando uma das seguintes maneiras:

  1. Usar o serviço de detalhes do problema
  2. Implementar ProblemDetailsFactory
  3. Usar ApiBehaviorOptions.ClientErrorMapping

Resposta padrão aos detalhes do problema

O arquivo Program.cs a seguir foi gerado pelos modelos de aplicativo Web para controladores de API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Considere o seguinte controlador, que retorna BadRequest quando a entrada é inválida:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }
}

Uma resposta aos detalhes do problema é gerada com o código anterior quando qualquer uma das seguintes condições se aplica:

  • O ponto de extremidade /api/values2/divide é chamado com um denominador zero.
  • O ponto de extremidade /api/values2/squareroot é chamado com um radicando menor que zero.

O corpo da resposta aos detalhes do problema padrão tem os seguintes valores type, title e status:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}

Serviço de detalhes do problema

O ASP.NET Core dá suporte à criação de Detalhes do Problema para APIs HTTP usando o IProblemDetailsService. Para obter mais informações, confira a seção Serviço de detalhes do problema.

O código a seguir configura o aplicativo para gerar uma resposta de detalhes do problema para todas as respostas de erro de servidor e cliente HTTP que ainda não têm um conteúdo do corpo:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

Considere o controlador de API da seção anterior, que retorna BadRequest quando a entrada é inválida:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }
}

Uma resposta aos detalhes do problema é gerada com o código anterior quando qualquer uma das seguintes condições se aplica:

  • Uma entrada inválida é fornecida.
  • O URI não tem nenhum ponto de extremidade correspondente.
  • Ocorre uma exceção sem tratamento.

A criação automática de um ProblemDetails para códigos de status de erro fica desabilitada quando a propriedade SuppressMapClientErrors é definida como true:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressMapClientErrors = true;
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Usando o código anterior, quando um controlador de API retorna BadRequest, um status de resposta HTTP 400 é retornado sem nenhum corpo de resposta. SuppressMapClientErrors impede que uma resposta ProblemDetails seja criada, mesmo ao chamar WriteAsync para um ponto de extremidade do Controlador de API. O WriteAsync é explicado posteriormente neste artigo.

A próxima seção mostra como personalizar o corpo da resposta aos detalhes do problema, usando CustomizeProblemDetails, para retornar uma respostas mais útil. Para obter mais opções de personalização, confira como Personalizar detalhes do problema.

Personalizar detalhes do problema com CustomizeProblemDetails

O código abaixo usa ProblemDetailsOptions para definir CustomizeProblemDetails:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
        options.CustomizeProblemDetails = (context) =>
        {

            var mathErrorFeature = context.HttpContext.Features
                                                       .Get<MathErrorFeature>();
            if (mathErrorFeature is not null)
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError =>
                    ("Divison by zero is not defined.",
                                          "https://wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                                          "https://wikipedia.org/wiki/Square_root")
                };

                context.ProblemDetails.Type = details.Type;
                context.ProblemDetails.Title = "Bad Input";
                context.ProblemDetails.Detail = details.Detail;
            }
        }
    );

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStatusCodePages();

app.UseAuthorization();

app.MapControllers();

app.Run();

O controlador de API atualizado:

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

O código a seguir contém MathErrorFeature e MathErrorType, que são usados com o exemplo anterior:

// Custom Http Request Feature
class MathErrorFeature
{
    public MathErrorType MathError { get; set; }
}

// Custom math errors
enum MathErrorType
{
    DivisionByZeroError,
    NegativeRadicandError
}

Uma resposta aos detalhes do problema é gerada com o código anterior quando qualquer uma das seguintes condições se aplica:

  • O ponto de extremidade /divide é chamado com um denominador zero.
  • O ponto de extremidade /squareroot é chamado com um radicando menor que zero.
  • O URI não tem nenhum ponto de extremidade correspondente.

O corpo da resposta aos detalhes do problema contém o seguinte quando qualquer ponto de extremidade squareroot é chamado com um radicando menor que zero:

{
  "type": "https://en.wikipedia.org/wiki/Square_root",
  "title": "Bad Input",
  "status": 400,
  "detail": "Negative or complex numbers are not allowed."
}

Exibir ou baixar o código de exemplo

Implementa ProblemDetailsFactory

O MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory para produzir todas as instâncias de ProblemDetails e ValidationProblemDetails. Este alocador é usado para:

Para personalizar a resposta aos detalhes do problema, registre uma implementação personalizada de ProblemDetailsFactory em Program.cs:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

Use ApiBehaviorOptions.ClientErrorMapping.

Use a propriedade ClientErrorMapping para configurar o conteúdo da resposta ProblemDetails. Por exemplo, o código a seguir em Program.cs atualiza a propriedade Link para respostas 404:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Recursos adicionais

Este artigo descreve como lidar com erros e personalizar o tratamento de erros com APIs web do ASP.NET Core.

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. Por exemplo, considere a seguinte ação do controlador, que gera uma exceção:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

Quando a Página de Exceção do Desenvolvedor detecta uma exceção sem tratamento, ela gera 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
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

Se o cliente solicitar uma resposta formatada em HTML, a Página de Exceção do Desenvolvedor gerará uma resposta semelhante ao exemplo a seguir:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

Para solicitar uma resposta formatada em HTML, defina o cabeçalho da solicitação HTTP Accept como text/html.

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 de Tratamento de Exceções para produzir um payload de erro:

  1. Em Program.cs, chame UseExceptionHandler para adicionar o Middleware de Tratamento de Exceções:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Configure uma ação do controlador para responder à rota /error:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

A ação HandleError anterior envia um payload compatível com RFC 7807 para o cliente.

Aviso

Não marque o método de ação do manipulador de erro com atributos de método HTTP, como HttpGet. Verbos explícitos impedem algumas solicitações de chegar ao método da ação.

Para APIs web que usam Swagger/OpenAPI, marque a ação do manipulador de erros com o atributo [ApiExplorerSettings] e defina sua propriedade IgnoreApi como true. Essa configuração de atributo exclui a ação do manipulador de erros da especificação OpenAPI do aplicativo:

[ApiExplorerSettings(IgnoreApi = true)]

Permita o acesso anônimo ao método se os usuários não autenticados tiverem que ver o erro.

O Middleware de Tratamento de Exceções também pode ser usado no ambiente de desenvolvimento para produzir um formato de payload consistente em todos os ambientes:

  1. No Program.cs, registre instâncias de Middleware de Tratamento de Exceção específicas do ambiente:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    No código anterior, o middleware é registrado com:

    • uma rota de /error-development no ambiente de Desenvolvimento.
    • Uma rota de /error em ambientes de Não Desenvolvimento.

  2. Adicione ações de controlador para as rotas Desenvolvimento e Não Desenvolvimento:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Usar exceções para modificar a resposta

O conteúdo da resposta pode ser modificado de fora do controlador usando uma exceção personalizada e um filtro de ação:

  1. Crie um tipo de exceção conhecido chamado HttpResponseException:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. Crie um filtro de ação chamado HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    O filtro anterior especifica um Order do valor inteiro máximo menos 10. Esse Order permite que outros filtros sejam executados no final do pipeline.

  3. Em Program.cs, adicione o filtro de ação à coleção de filtros:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

Resposta de erro de falha de validação

Para controladores de API web, o MVC responde com um tipo de resposta ValidationProblemDetails quando a validação do modelo falha. O MVC usa os resultados de InvalidModelStateResponseFactory para construir a resposta de erro para uma falha de validação. O exemplo a seguir substitui a fábrica padrão por uma implementação que também dá suporte à formatação de respostas como XML, em Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

Resposta de erro do cliente

Um resultado de erro é definido como um resultado com um código de status HTTP de 400 ou superior. Para controladores de API web, o MVC transforma um resultado de erro para produzir um ProblemDetails.

A resposta de erro pode ser configurada usando uma das seguintes maneiras:

  1. Implementar ProblemDetailsFactory
  2. Usar ApiBehaviorOptions.ClientErrorMapping

Implementa ProblemDetailsFactory

O MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory para produzir todas as instâncias de ProblemDetails e ValidationProblemDetails. Este alocador é usado para:

Para personalizar a resposta aos detalhes do problema, registre uma implementação personalizada de ProblemDetailsFactory em Program.cs:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

Use ApiBehaviorOptions.ClientErrorMapping.

Use a propriedade ClientErrorMapping para configurar o conteúdo da resposta ProblemDetails. Por exemplo, o código a seguir em Program.cs atualiza a propriedade Link para respostas 404:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Middleware personalizado para tratar exceções

Os padrões na exceção que tratam o middleware funcionam bem para a maioria dos aplicativos. Para aplicativos que exigem tratamento de exceção especializado, considere personalizar o middleware de tratamento de exceções.

Produzir um conteúdo ProblemDetails para exceções

O ASP.NET Core não produz um payload de erro padronizado quando ocorre uma exceção sem tratamento. Para cenários em que é desejável retornar uma resposta ProblemDetails padronizada ao cliente, o middleware ProblemDetails pode ser usado para mapear exceções e respostas 404 para um payload ProblemDetails . O middleware de tratamento de exceção também pode ser usado para retornar um payload ProblemDetails para exceções sem tratamento.

Recursos adicionais

Este artigo descreve como manipular e personalizar o tratamento de erros com APIs web do ASP.NET Core.

Exibir ou baixar código de exemplo (Como baixar)

Página de exceção do desenvolvedor

A Página de Exceção do Desenvolvedor é uma ferramenta útil para obter 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. Para ilustrar, considere a seguinte ação do controlador:

[HttpGet("{city}")]
public WeatherForecast Get(string city)
{
    if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase))
    {
        throw new ArgumentException(
            $"We don't offer a weather forecast for {city}.", nameof(city));
    }
    
    return GetWeather().First();
}

Execute comando curl a seguir para testar a ação anterior:

curl -i https://localhost:5001/weatherforecast/chicago

A Página de Exceção do Desenvolvedor exibe uma resposta de texto sem formatação se o cliente não solicitar saída formatada em HTML. O seguinte resultado é exibido:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:13:16 GMT

System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city')
   at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Host: localhost:44312
User-Agent: curl/7.55.1

Para exibir uma resposta formatada em HTML, defina o cabeçalho da solicitação HTTP Accept como tipo de mídia text/html. Por exemplo:

curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago

Considere o seguinte trecho da resposta HTTP:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:55:37 GMT

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

A resposta formatada em HTML torna-se útil ao testar por meio de ferramentas como o Postman. A captura de tela a seguir mostra o texto sem formatação e as respostas formatadas em HTML no Postman:

Test the Developer Exception Page in Postman.

Aviso

Habilite a página de exceção do desenvolvedor somente quando o aplicativo estiver 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.

Não marque o método de ação do manipulador de erro com atributos de método HTTP, como HttpGet. Verbos explícitos impedem algumas solicitações de chegar ao método da ação. Permita o acesso anônimo ao método se os usuários não autenticados tiverem que ver o erro.

Manipulador de exceção

Em ambientes de não desenvolvimento, o middleware de tratamento de exceção pode ser usado para produzir um payload de erro:

  1. No Startup.Configure, invoque UseExceptionHandler para usar o middleware:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  2. Configure uma ação do controlador para responder à rota /error:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

A ação Error anterior envia um payload compatível com RFC 7807 para o cliente.

O Middleware de Tratamento de Exceções também pode fornecer uma saída mais detalhada negociada por conteúdo mais detalhada no ambiente de desenvolvimento local. Use as seguintes etapas para produzir um formato de payload consistente em ambientes de desenvolvimento e produção:

  1. No Startup.Configure, registre instâncias de Middleware de Tratamento de Exceção específicas do ambiente:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }
    

    No código anterior, o middleware é registrado com:

    • Uma rota de /error-local-development no ambiente de Desenvolvimento.
    • Uma rota de /error em ambientes de Não Desenvolvimento.

  2. Aplicar o roteamento de atributo às ações do controlador:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IWebHostEnvironment webHostEnvironment)
        {
            if (webHostEnvironment.EnvironmentName != "Development")
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
    
            return Problem(
                detail: context.Error.StackTrace,
                title: context.Error.Message);
        }
    
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

    O código anterior chama ControllerBase.Problem para criar uma resposta ProblemDetails.

Usar exceções para modificar a resposta

O conteúdo da resposta pode ser modificado de fora do controlador. No ASP.NET API Web 4.x, uma maneira de fazer isso era USAR o tipo HttpResponseException. o ASP.NET Core não inclui um tipo equivalente. O suporte para HttpResponseException pode ser adicionado por meio das seguintes etapas:

  1. Crie um tipo de exceção conhecido chamado HttpResponseException:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. Crie um filtro de ação chamado HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; } = int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException exception)
            {
                context.Result = new ObjectResult(exception.Value)
                {
                    StatusCode = exception.Status,
                };
                context.ExceptionHandled = true;
            }
        }
    }
    

    O filtro anterior especifica um Order do valor inteiro máximo menos 10. Esse Order permite que outros filtros sejam executados no final do pipeline.

  3. Em Startup.ConfigureServices, adicione o filtro de ação à coleção de filtros:

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    

Resposta de erro de falha de validação

Para controladores de API web, o MVC responde com um tipo de resposta ValidationProblemDetails quando a validação do modelo falha. O MVC usa os resultados de InvalidModelStateResponseFactory para construir a resposta de erro para uma falha de validação. O exemplo a seguir usa o alocador para alterar o tipo de resposta padrão para SerializableError em Startup.ConfigureServices:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

            // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
            result.ContentTypes.Add(MediaTypeNames.Application.Json);
            result.ContentTypes.Add(MediaTypeNames.Application.Xml);

            return result;
        };
    });

Resposta de erro do cliente

Um resultado de erro é definido como um resultado com um código de status HTTP de 400 ou superior. Para controladores de API web, o MVC transforma um resultado de erro em um resultado com ProblemDetails.

A resposta de erro pode ser configurada usando uma das seguintes maneiras:

  1. Implementar ProblemDetailsFactory
  2. Usar ApiBehaviorOptions.ClientErrorMapping

Implementa ProblemDetailsFactory

O MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory para produzir todas as instâncias de ProblemDetails e ValidationProblemDetails. Este alocador é usado para:

Para personalizar a resposta aos detalhes do problema, registre uma implementação personalizada de ProblemDetailsFactory em Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection serviceCollection)
{
    services.AddControllers();
    services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}

Usar ApiBehaviorOptions.ClientErrorMapping

Use a propriedade ClientErrorMapping para configurar o conteúdo da resposta ProblemDetails. Por exemplo, o código a seguir em Startup.ConfigureServices atualiza a propriedade type para respostas 404:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
        options.DisableImplicitFromServicesParameters = true;
    });

Middleware personalizado para tratar exceções

Os padrões na exceção que tratam o middleware funcionam bem para a maioria dos aplicativos. Para aplicativos que exigem tratamento de exceção especializado, considere personalizar o middleware de tratamento de exceções.

Produzir um payload ProblemDetails para exceções

O ASP.NET Core não produz um payload de erro padronizado quando ocorre uma exceção sem tratamento. Para cenários em que é desejável retornar uma resposta ProblemDetails padronizada ao cliente, o middleware ProblemDetails pode ser usado para mapear exceções e respostas 404 para um payload ProblemDetails . O middleware de tratamento de exceção também pode ser usado para retornar um payload ProblemDetails para exceções sem tratamento.