Handle errors in ASP.NET Core controller-based web APIs
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
This article describes how to handle errors and customize error handling in controller-based ASP.NET Core web APIs. For information about error handling in minimal APIs, see Handle errors in ASP.NET Core and Handle errors in minimal APIs.
Developer Exception Page
The Developer Exception Page displays detailed information about unhandled request exceptions. It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses. The developer exception page runs early in the middleware pipeline, so that it can catch unhandled exceptions thrown in middleware that follows.
ASP.NET Core apps enable the developer exception page by default when both:
- Running in the Development environment.
- The app was created with the current templates, that is, by using WebApplication.CreateBuilder.
Apps created using earlier templates, that is, by using WebHost.CreateDefaultBuilder, can enable the developer exception page by calling app.UseDeveloperExceptionPage
.
Warning
Don't enable the Developer Exception Page unless the app is running in the Development environment. Don't share detailed exception information publicly when the app runs in production. For more information on configuring environments, see Use multiple environments in ASP.NET Core.
The Developer Exception Page can include the following information about the exception and the request:
- Stack trace
- Query string parameters, if any
- Cookies, if any
- Headers
- Endpoint metadata, if any
The Developer Exception Page isn't guaranteed to provide any information. Use Logging for complete error information.
The following image shows a sample developer exception page with animation to show the tabs and the information displayed:
In response to a request with an Accept: text/plain
header, the Developer Exception Page returns plain text instead of HTML. For example:
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
To see the Developer Exception Page:
Add the following controller action to a controller-based API. The action throws an exception when the endpoint is requested.
[HttpGet("Throw")] public IActionResult Throw() => throw new Exception("Sample exception.");
Run the app in the development environment.
Go to the endpoint defined by the controller action.
Exception handler
In non-development environments, use Exception Handling Middleware to produce an error payload:
In
Program.cs
, call UseExceptionHandler to add the Exception Handling Middleware:var app = builder.Build(); app.UseHttpsRedirection(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error"); } app.UseAuthorization(); app.MapControllers(); app.Run();
Configure a controller action to respond to the
/error
route:[Route("/error")] public IActionResult HandleError() => Problem();
The preceding HandleError
action sends an RFC 7807-compliant payload to the client.
Warning
Don't mark the error handler action method with HTTP method attributes, such as HttpGet
. Explicit verbs prevent some requests from reaching the action method.
For web APIs that use Swagger / OpenAPI, mark the error handler action with the [ApiExplorerSettings] attribute and set its IgnoreApi property to true
. This attribute configuration excludes the error handler action from the app's OpenAPI specification:
[ApiExplorerSettings(IgnoreApi = true)]
Allow anonymous access to the method if unauthenticated users should see the error.
Exception Handling Middleware can also be used in the Development environment to produce a consistent payload format across all environments:
In
Program.cs
, register environment-specific Exception Handling Middleware instances:if (app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error-development"); } else { app.UseExceptionHandler("/error"); }
In the preceding code, the middleware is registered with:
- A route of
/error-development
in the Development environment. - A route of
/error
in non-Development environments.
- A route of
Add controller actions for both the Development and non-Development routes:
[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();
Use exceptions to modify the response
The contents of the response can be modified from outside of the controller using a custom exception and an action filter:
Create a well-known exception type named
HttpResponseException
:public class HttpResponseException : Exception { public HttpResponseException(int statusCode, object? value = null) => (StatusCode, Value) = (statusCode, value); public int StatusCode { get; } public object? Value { get; } }
Create an action filter named
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; } } }
The preceding filter specifies an
Order
of the maximum integer value minus 10. ThisOrder
allows other filters to run at the end of the pipeline.In
Program.cs
, add the action filter to the filters collection:builder.Services.AddControllers(options => { options.Filters.Add<HttpResponseExceptionFilter>(); });
Validation failure error response
For web API controllers, MVC responds with a ValidationProblemDetails response type when model validation fails. MVC uses the results of InvalidModelStateResponseFactory to construct the error response for a validation failure. The following example replaces the default factory with an implementation that also supports formatting responses as XML, in 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();
Client error response
An error result is defined as a result with an HTTP status code of 400 or higher. For web API controllers, MVC transforms an error result to produce a ProblemDetails.
The automatic creation of a ProblemDetails
for error status codes is enabled by default, but error responses can be configured in one of the following ways:
- Use the problem details service
- Implement ProblemDetailsFactory
- Use ApiBehaviorOptions.ClientErrorMapping
Default problem details response
The following Program.cs
file was generated by the web application templates for API controllers:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Consider the following controller, which returns BadRequest when the input is invalid:
[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));
}
}
A problem details response is generated with the preceding code when any of the following conditions apply:
- The
/api/values2/divide
endpoint is called with a zero denominator. - The
/api/values2/squareroot
endpoint is called with a radicand less than zero.
The default problem details response body has the following type
, title
, and status
values:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"status": 400,
"traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}
Problem details service
ASP.NET Core supports creating Problem Details for HTTP APIs using the IProblemDetailsService. For more information, see the Problem details service.
The following code configures the app to generate a problem details response for all HTTP client and server error responses that don't have body content yet:
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();
Consider the API controller from the preceding section, which returns BadRequest when the input is invalid:
[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));
}
}
A problem details response is generated with the preceding code when any of the following conditions apply:
- An invalid input is supplied.
- The URI has no matching endpoint.
- An unhandled exception occurs.
The automatic creation of a ProblemDetails
for error status codes is disabled when the SuppressMapClientErrors property is set to 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();
Using the preceding code, when an API controller returns BadRequest
, an HTTP 400 response status is returned with no response body. SuppressMapClientErrors
prevents a ProblemDetails
response from being created, even when calling WriteAsync
for an API Controller endpoint. WriteAsync
is explained later in this article.
The next section shows how to customize the problem details response body, using CustomizeProblemDetails, to return a more helpful response. For more customization options, see Customizing problem details.
Customize problem details with CustomizeProblemDetails
The following code uses ProblemDetailsOptions to set 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();
The updated API controller:
[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));
}
}
The following code contains the MathErrorFeature
and MathErrorType
, which are used with the preceding sample:
// Custom Http Request Feature
class MathErrorFeature
{
public MathErrorType MathError { get; set; }
}
// Custom math errors
enum MathErrorType
{
DivisionByZeroError,
NegativeRadicandError
}
A problem details response is generated with the preceding code when any of the following conditions apply:
- The
/divide
endpoint is called with a zero denominator. - The
/squareroot
endpoint is called with a radicand less than zero. - The URI has no matching endpoint.
The problem details response body contains the following when either squareroot
endpoint is called with a radicand less than zero:
{
"type": "https://en.wikipedia.org/wiki/Square_root",
"title": "Bad Input",
"status": 400,
"detail": "Negative or complex numbers are not allowed."
}
Implement ProblemDetailsFactory
MVC uses Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory to produce all instances of ProblemDetails and ValidationProblemDetails. This factory is used for:
- Client error responses
- Validation failure error responses
- ControllerBase.Problem and ControllerBase.ValidationProblem
To customize the problem details response, register a custom implementation of ProblemDetailsFactory in Program.cs
:
builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();
Use ApiBehaviorOptions.ClientErrorMapping
Use the ClientErrorMapping property to configure the contents of the ProblemDetails
response. For example, the following code in Program.cs
updates the Link property for 404 responses:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
Additional resources
This article describes how to handle errors and customize error handling with ASP.NET Core web APIs.
Developer Exception Page
The Developer Exception Page shows detailed stack traces for server errors. It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses. For example, consider the following controller action, which throws an exception:
[HttpGet("Throw")]
public IActionResult Throw() =>
throw new Exception("Sample exception.");
When the Developer Exception Page detects an unhandled exception, it generates a default plain-text response similar to the following example:
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()
...
If the client requests an HTML-formatted response, the Developer Exception Page generates a response similar to the following example:
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;
}
...
To request an HTML-formatted response, set the Accept
HTTP request header to text/html
.
Warning
Don't enable the Developer Exception Page unless the app is running in the Development environment. Don't share detailed exception information publicly when the app runs in production. For more information on configuring environments, see Use multiple environments in ASP.NET Core.
Exception handler
In non-development environments, use Exception Handling Middleware to produce an error payload:
In
Program.cs
, call UseExceptionHandler to add the Exception Handling Middleware:var app = builder.Build(); app.UseHttpsRedirection(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error"); } app.UseAuthorization(); app.MapControllers(); app.Run();
Configure a controller action to respond to the
/error
route:[Route("/error")] public IActionResult HandleError() => Problem();
The preceding HandleError
action sends an RFC 7807-compliant payload to the client.
Warning
Don't mark the error handler action method with HTTP method attributes, such as HttpGet
. Explicit verbs prevent some requests from reaching the action method.
For web APIs that use Swagger / OpenAPI, mark the error handler action with the [ApiExplorerSettings] attribute and set its IgnoreApi property to true
. This attribute configuration excludes the error handler action from the app's OpenAPI specification:
[ApiExplorerSettings(IgnoreApi = true)]
Allow anonymous access to the method if unauthenticated users should see the error.
Exception Handling Middleware can also be used in the Development environment to produce a consistent payload format across all environments:
In
Program.cs
, register environment-specific Exception Handling Middleware instances:if (app.Environment.IsDevelopment()) { app.UseExceptionHandler("/error-development"); } else { app.UseExceptionHandler("/error"); }
In the preceding code, the middleware is registered with:
- A route of
/error-development
in the Development environment. - A route of
/error
in non-Development environments.
- A route of
Add controller actions for both the Development and non-Development routes:
[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();
Use exceptions to modify the response
The contents of the response can be modified from outside of the controller using a custom exception and an action filter:
Create a well-known exception type named
HttpResponseException
:public class HttpResponseException : Exception { public HttpResponseException(int statusCode, object? value = null) => (StatusCode, Value) = (statusCode, value); public int StatusCode { get; } public object? Value { get; } }
Create an action filter named
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; } } }
The preceding filter specifies an
Order
of the maximum integer value minus 10. ThisOrder
allows other filters to run at the end of the pipeline.In
Program.cs
, add the action filter to the filters collection:builder.Services.AddControllers(options => { options.Filters.Add<HttpResponseExceptionFilter>(); });
Validation failure error response
For web API controllers, MVC responds with a ValidationProblemDetails response type when model validation fails. MVC uses the results of InvalidModelStateResponseFactory to construct the error response for a validation failure. The following example replaces the default factory with an implementation that also supports formatting responses as XML, in 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();
Client error response
An error result is defined as a result with an HTTP status code of 400 or higher. For web API controllers, MVC transforms an error result to produce a ProblemDetails.
The error response can be configured in one of the following ways:
Implement ProblemDetailsFactory
MVC uses Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory to produce all instances of ProblemDetails and ValidationProblemDetails. This factory is used for:
- Client error responses
- Validation failure error responses
- ControllerBase.Problem and ControllerBase.ValidationProblem
To customize the problem details response, register a custom implementation of ProblemDetailsFactory in Program.cs
:
builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();
Use ApiBehaviorOptions.ClientErrorMapping
Use the ClientErrorMapping property to configure the contents of the ProblemDetails
response. For example, the following code in Program.cs
updates the Link property for 404 responses:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
Custom Middleware to handle exceptions
The defaults in the exception handling middleware work well for most apps. For apps that require specialized exception handling, consider customizing the exception handling middleware.
Produce a ProblemDetails payload for exceptions
ASP.NET Core doesn't produce a standardized error payload when an unhandled exception occurs. For scenarios where it's desirable to return a standardized ProblemDetails response to the client, the ProblemDetails middleware can be used to map exceptions and 404 responses to a ProblemDetails payload. The exception handling middleware can also be used to return a ProblemDetails payload for unhandled exceptions.
Additional resources
This article describes how to handle and customize error handling with ASP.NET Core web APIs.
View or download sample code (How to download)
Developer Exception Page
The Developer Exception Page is a useful tool to get detailed stack traces for server errors. It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses. 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();
}
Run the following curl
command to test the preceding action:
curl -i https://localhost:5001/weatherforecast/chicago
The Developer Exception Page displays a plain-text response if the client doesn't request HTML-formatted output. 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
To display an HTML-formatted response instead, set the Accept
HTTP request header to the text/html
media type. For example:
curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago
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;
}
The HTML-formatted response becomes useful when testing via tools like curl.
Warning
Enable the Developer Exception Page only when the app is running in the Development environment. Don't share detailed exception information publicly when the app runs in production. For more information on configuring environments, see Use multiple environments in ASP.NET Core.
Don't mark the error handler action method with HTTP method attributes, such as HttpGet
. Explicit verbs prevent some requests from reaching the action method. Allow anonymous access to the method if unauthenticated users should see the error.
Exception handler
In non-development environments, Exception Handling Middleware can be used to produce an error payload:
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(); }); }
Configure a controller action to respond to the
/error
route:[ApiController] public class ErrorController : ControllerBase { [Route("/error")] public IActionResult Error() => Problem(); }
The preceding Error
action sends an RFC 7807-compliant payload to the client.
Exception Handling Middleware can also provide more detailed content-negotiated output in the local development environment. Use the following steps to produce a consistent payload format across development and production environments:
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"); } }
In the preceding code, the middleware is registered with:
- A route of
/error-local-development
in the Development environment. - A route of
/error
in environments that aren't Development.
- A route of
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(); }
The preceding code calls ControllerBase.Problem to create a ProblemDetails response.
Use exceptions to modify the response
The contents of the response can be modified from outside of the controller. In ASP.NET 4.x Web API, one way to do this was using the HttpResponseException type. ASP.NET Core doesn't include an equivalent type. Support for HttpResponseException
can be added with the following steps:
Create a well-known exception type named
HttpResponseException
:public class HttpResponseException : Exception { public int Status { get; set; } = 500; public object Value { get; set; } }
Create an action filter named
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; } } }
The preceding filter specifies an
Order
of the maximum integer value minus 10. ThisOrder
allows other filters to run at the end of the pipeline.In
Startup.ConfigureServices
, add the action filter to the filters collection:services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter()));
Validation failure error response
For web API controllers, MVC responds with a ValidationProblemDetails response type when model validation fails. MVC uses the results of InvalidModelStateResponseFactory to construct the error response for a validation failure. 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 System.Net.Mime;` to resolve MediaTypeNames
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
});
Client error response
An error result is defined as a result with an HTTP status code of 400 or higher. For web API controllers, MVC transforms an error result to a result with ProblemDetails.
The error response can be configured in one of the following ways:
Implement ProblemDetailsFactory
MVC uses Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory to produce all instances of ProblemDetails and ValidationProblemDetails. This factory is used for:
- Client error responses
- Validation failure error responses
- ControllerBase.Problem and ControllerBase.ValidationProblem
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>();
}
Use ApiBehaviorOptions.ClientErrorMapping
Use the ClientErrorMapping property to configure the contents of the ProblemDetails
response. 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[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
options.DisableImplicitFromServicesParameters = true;
});
Custom Middleware to handle exceptions
The defaults in the exception handling middleware work well for most apps. For apps that require specialized exception handling, consider customizing the exception handling middleware.
Producing a ProblemDetails payload for exceptions
ASP.NET Core doesn't produce a standardized error payload when an unhandled exception occurs. For scenarios where it's desirable to return a standardized ProblemDetails response to the client, the ProblemDetails middleware can be used to map exceptions and 404 responses to a ProblemDetails payload. The exception handling middleware can also be used to return a ProblemDetails payload for unhandled exceptions.