Partilhar via


Identificador de erros nas APIs da Web baseadas no controlador do ASP.NET Core

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.

Aviso

Não há mais suporte para essa versão do ASP.NET Core. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. 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.

Este artigo descreve como lidar com erros e personalizar o tratamento de erros nas APIs da Web do ASP.NET Core baseadas no controlador. Para obter informações sobre o tratamento de erros nas APIs mínimas, consulte Identificador de erros no ASP.NET Core e Identificador de erros nas APIs mínimas.

Página de exceção do desenvolvedor

A Página de exceção do desenvolvedor exibe informações detalhadas sobre as exceções de solicitação não tratadas. Ele usa DeveloperExceptionPageMiddleware para capturar exceções síncronas e assíncronas do pipeline HTTP e para gerar respostas de erro. A página de exceção do desenvolvedor é executada no início do pipeline do middleware, para que ele possa capturar exceções sem tratamento geradas no middleware a seguir.

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

Os aplicativos criados usando modelos anteriores, ou seja, usando WebHost.CreateDefaultBuilder, podem ativar a página de exceção do desenvolvedor chamando app.UseDeveloperExceptionPage.

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.

A Página de Exceção do Desenvolvedor pode incluir as seguintes informações sobre a exceção e a solicitação:

  • Rastreamento de pilha
  • Parâmetros de cadeia de caracteres de consulta, se houver
  • Cookies, se houver
  • Cabeçalhos
  • Metadados de ponto de extremidade, se houver

Não existe garantia de que a Página de Exceção do Desenvolvedor fornecerá alguma informação. Use Registro em log para obter informações de erro completas.

A imagem a seguir mostra uma página de exceção do desenvolvedor de exemplo com animação para mostrar as guias e as informações exibidas:

Página de exceção do desenvolvedor animada para mostrar cada guia selecionada.

Em resposta a uma solicitação com um cabeçalho Accept: text/plain, a Página de Exceção do Desenvolvedor retorna texto sem formatação em vez de HTML. Por exemplo:

Status: 500 Internal Server Error
Time: 9.39 msSize: 480 bytes
FormattedRawHeadersRequest
Body
text/plain; charset=utf-8, 480 bytes
System.InvalidOperationException: Sample Exception
   at WebApplicationMinimal.Program.<>c.<Main>b__0_0() in C:\Source\WebApplicationMinimal\Program.cs:line 12
   at lambda_method1(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: text/plain
Host: localhost:7267
traceparent: 00-0eab195ea19d07b90a46cd7d6bf2f

Para ver a Página de Exceção do Desenvolvedor:

  • Adicione a ação do controlador a seguir a uma API baseada no controlador. A ação gera uma exceção quando o ponto de extremidade é solicitado.

    [HttpGet("Throw")]
    public IActionResult Throw() =>
        throw new Exception("Sample exception.");
    
  • Execute o aplicativo no ambiente de desenvolvimento.

  • Acesse o ponto de extremidade definido pela ação do controlador.

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 de detalhes do problema é gerada com o código anterior quando qualquer uma das seguintes condições for aplicável:

  • 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 do cliente e servidor HTTP que ainda não possuem conteúdo no 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 da 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 de detalhes do problema é gerada com o código anterior quando qualquer uma das seguintes condições for aplicável:

  • 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 de detalhes do problema é gerada com o código anterior quando qualquer uma das seguintes condições for aplicável:

  • 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:

Testar a Página de Exceção do Desenvolvedor no 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.