Tratar erros em APIs da Web ASP.NET CoreHandle errors in ASP.NET Core web APIs

Este artigo descreve como lidar e personalizar o tratamento de erros com as APIs da Web do ASP.NET Core.This article describes how to handle and customize error handling with ASP.NET Core web APIs.

Exibir ou baixar o código de exemplo (como baixar)View or download sample code (How to download)

Página de exceção do desenvolvedorDeveloper Exception Page

A página de exceção do desenvolvedor é uma ferramenta útil para obter rastreamentos de pilha detalhados para erros do servidor.The Developer Exception Page is a useful tool to get detailed stack traces for server errors. Ele usa DeveloperExceptionPageMiddleware capturar exceções síncronas e assíncronas do pipeline HTTP e gerar respostas de erro.It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses. Para ilustrar, considere a seguinte ação do controlador:To illustrate, consider the following controller action:

[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 o seguinte comando curl para testar a ação anterior:Run the following curl command to test the preceding action:

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

No ASP.NET Core 3,0 e posterior, a página de exceção do desenvolvedor exibirá uma resposta de texto sem formatação se o cliente não solicitar saída formatada em HTML.In ASP.NET Core 3.0 and later, the Developer Exception Page displays a plain-text response if the client doesn't request HTML-formatted output. A saída a seguir é exibida:The following output appears:

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 Accept cabeçalho de solicitação HTTP para o tipo de mídia text/html.To display an HTML-formatted response instead, set the Accept HTTP request header to the text/html media type. Por exemplo:For example:

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

Considere o seguinte trecho da resposta HTTP:Consider the following excerpt from the HTTP response:

No ASP.NET Core 2,2 e anteriores, a página de exceção do desenvolvedor exibe uma resposta formatada em HTML.In ASP.NET Core 2.2 and earlier, the Developer Exception Page displays an HTML-formatted response. Por exemplo, considere o seguinte trecho da resposta HTTP:For example, consider the following excerpt from the HTTP response:

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 se torna útil durante o teste por meio de ferramentas como o postmaster.The HTML-formatted response becomes useful when testing via tools like Postman. A captura de tela a seguir mostra o texto sem formatação e as respostas formatadas em HTML no postmaster:The following screen capture shows both the plain-text and the HTML-formatted responses in Postman:

Teste de página de exceção do desenvolvedor no postmaster

Aviso

Habilite a página de exceção do desenvolvedor somente quando o aplicativo estiver em execução no ambiente de desenvolvimento.Enable the Developer Exception Page only when the app is running in the Development environment. Não é recomendável compartilhar informações de exceção detalhadas publicamente quando o aplicativo é executado em produção.You don't want to share detailed exception information publicly when the app runs in production. Para saber mais sobre a configuração de ambientes, confira Usar vários ambientes no ASP.NET Core.For more information on configuring environments, see Usar vários ambientes no ASP.NET Core.

Manipulador de exceçãoException handler

Em ambientes que não são de desenvolvimento, o middleware de manipulação de exceção pode ser usado para produzir uma carga de erro:In non-development environments, Exception Handling Middleware can be used to produce an error payload:

  1. Em Startup.Configure, invoque UseExceptionHandler para usar o middleware:In Startup.Configure, invoke UseExceptionHandler to use the 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();
        });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseMvc();
    }
    
  2. Configure uma ação do controlador para responder à /error rota:Configure a controller action to respond to the /error route:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public ActionResult Error([FromServices] IHostingEnvironment webHostEnvironment)
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            var ex = feature?.Error;
            var isDev = webHostEnvironment.IsDevelopment();
            var problemDetails = new ProblemDetails
            {
                Status = (int)HttpStatusCode.InternalServerError,
                Instance = feature?.Path,
                Title = isDev ? $"{ex.GetType().Name}: {ex.Message}" : "An error occurred.",
                Detail = isDev ? ex.StackTrace : null,
            };
    
            return StatusCode(problemDetails.Status.Value, problemDetails);
        }
    }
    

A ação de Error anterior envia uma carga compatível com RFC 7807para o cliente.The preceding Error action sends an RFC 7807-compliant payload to the client.

O middleware de manipulação de exceção também pode fornecer uma saída de negociação de conteúdo mais detalhada no ambiente de desenvolvimento local.Exception Handling Middleware can also provide more detailed content-negotiated output in the local development environment. Use as etapas a seguir para produzir um formato de carga consistente entre ambientes de desenvolvimento e produção:Use the following steps to produce a consistent payload format across development and production environments:

  1. Em Startup.Configure, registre as instâncias de middleware de manipulação de exceção específicas do ambiente:In Startup.Configure, register environment-specific Exception Handling Middleware instances:

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

    No código anterior, o middleware é registrado com:In the preceding code, the middleware is registered with:

    • Uma rota de /error-local-development no ambiente de desenvolvimento.A route of /error-local-development in the Development environment.
    • Uma rota de /error em ambientes que não são de desenvolvimento.A route of /error in environments that aren't Development.
  2. Aplicar roteamento de atributo a ações do controlador:Apply attribute routing to controller actions:

    [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();
    }
    
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IHostingEnvironment webHostEnvironment)
        {
            if (!webHostEnvironment.IsDevelopment())
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            var ex = feature?.Error;
    
            var problemDetails = new ProblemDetails
            {
                Status = (int)HttpStatusCode.InternalServerError,
                Instance = feature?.Path,
                Title = ex.GetType().Name,
                Detail = ex.StackTrace,
            };
    
            return StatusCode(problemDetails.Status.Value, problemDetails);
        }
    
        [Route("/error")]
        public ActionResult Error(
            [FromServices] IHostingEnvironment webHostEnvironment)
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            var ex = feature?.Error;
            var isDev = webHostEnvironment.IsDevelopment();
            var problemDetails = new ProblemDetails
            {
                Status = (int)HttpStatusCode.InternalServerError,
                Instance = feature?.Path,
                Title = isDev ? $"{ex.GetType().Name}: {ex.Message}" : "An error occurred.",
                Detail = isDev ? ex.StackTrace : null,
            };
    
            return StatusCode(problemDetails.Status.Value, problemDetails);
        }
    }
    

Usar exceções para modificar a respostaUse exceptions to modify the response

O conteúdo da resposta pode ser modificado de fora do controlador.The contents of the response can be modified from outside of the controller. Na API Web do ASP.NET 4. x, uma maneira de fazer isso era usando o tipo de HttpResponseException.In ASP.NET 4.x Web API, one way to do this was using the HttpResponseException type. ASP.NET Core não inclui um tipo equivalente.ASP.NET Core doesn't include an equivalent type. O suporte para HttpResponseException pode ser adicionado com as seguintes etapas:Support for HttpResponseException can be added with the following steps:

  1. Crie um tipo de exceção conhecido chamado HttpResponseException:Create a well-known exception type named 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:Create an action filter named HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; set; } = 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;
            }
        }
    }
    
  3. Em Startup.ConfigureServices, adicione o filtro de ação à coleção de filtros:In Startup.ConfigureServices, add the action filter to the filters collection:

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    
    services.AddMvc(options =>
            options.Filters.Add(new HttpResponseExceptionFilter()))
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
    services.AddMvc(options =>
            options.Filters.Add(new HttpResponseExceptionFilter()))
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    

Resposta de erro de falha na validaçãoValidation failure error response

Para controladores de API Web, o MVC responde com um tipo de resposta ValidationProblemDetails quando a validação do modelo falha.For web API controllers, MVC responds with a ValidationProblemDetails response type when model validation fails. O MVC usa os resultados de InvalidModelStateResponseFactory para construir a resposta de erro para uma falha de validação.MVC uses the results of InvalidModelStateResponseFactory to construct the error response for a validation failure. O exemplo a seguir usa a fábrica para alterar o tipo de resposta padrão para SerializableError em Startup.ConfigureServices:The following example uses the factory to change the default response type to SerializableError in Startup.ConfigureServices:

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

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

            return result;
        };
    });
services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

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

            return result;
        };
    });
services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = context =>
    {
        var result = new BadRequestObjectResult(context.ModelState);

        // TODO: add `using 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 clienteClient error response

Um resultado de erro é definido como resultado com um código de status HTTP de 400 ou superior.An error result is defined as a result with an HTTP status code of 400 or higher. Para controladores de API Web, o MVC transforma um resultado de erro em um resultado com ProblemDetails.For web API controllers, MVC transforms an error result to a result with ProblemDetails.

Importante

ASP.NET Core 2,1 gera uma resposta de detalhes do problema que é quase compatível com RFC 7807.ASP.NET Core 2.1 generates a problem details response that's nearly RFC 7807-compliant. Se a conformidade com o percentual de 100 for importante, atualize o projeto para ASP.NET Core 2,2 ou posterior.If 100 percent compliance is important, upgrade the project to ASP.NET Core 2.2 or later.

A resposta de erro pode ser configurada de uma das seguintes maneiras:The error response can be configured in one of the following ways:

  1. Implementar ProblemDetailsFactoryImplement ProblemDetailsFactory
  2. Usar ApiBehaviorOptions. ClientErrorMappingUse ApiBehaviorOptions.ClientErrorMapping

Implementar ProblemDetailsFactoryImplement ProblemDetailsFactory

O MVC usa Microsoft.AspNetCore.Mvc.ProblemDetailsFactory para produzir todas as instâncias de ProblemDetails e ValidationProblemDetails.MVC uses Microsoft.AspNetCore.Mvc.ProblemDetailsFactory to produce all instances of ProblemDetails and ValidationProblemDetails. Isso inclui respostas de erro de cliente, respostas de erro de falha de validação e os métodos auxiliares Microsoft.AspNetCore.Mvc.ControllerBase.Problem e ValidationProblem().This includes client error responses, validation failure error responses, and the Microsoft.AspNetCore.Mvc.ControllerBase.Problem and ValidationProblem() helper methods.

Para personalizar a resposta dos detalhes do problema, registre uma implementação personalizada do ProblemDetailsFactory no Startup.ConfigureServices:To customize the problem details response, register a custom implementation of ProblemDetailsFactory in Startup.ConfigureServices:

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

A resposta de erro pode ser configurada conforme descrito na seção usar ApiBehaviorOptions. ClientErrorMapping .The error response can be configured as outlined in the Use ApiBehaviorOptions.ClientErrorMapping section.

Usar ApiBehaviorOptions. ClientErrorMappingUse ApiBehaviorOptions.ClientErrorMapping

Use a propriedade ClientErrorMapping para configurar o conteúdo da resposta ProblemDetails.Use the ClientErrorMapping property to configure the contents of the ProblemDetails response. Por exemplo, o código a seguir no Startup.ConfigureServices atualiza a propriedade type para respostas de 404:For example, the following code in Startup.ConfigureServices updates the type property for 404 responses:

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