aspnetcore/fundamentals/error-handling.md
:::moniker range=">= aspnetcore-10.0"
This article covers common approaches to handling errors in ASP.NET Core web apps. See also xref:fundamentals/error-handling-api.
For Blazor error handling guidance, which adds to or supersedes the guidance in this article, see xref:blazor/fundamentals/handle-errors.
To configure a custom error handling page for the Production environment, call xref:Microsoft.AspNetCore.Builder.ExceptionHandlerExtensions.UseExceptionHandler%2A. This exception handling middleware:
/Error path.[!WARNING] If the alternate pipeline throws an exception of its own, Exception Handling Middleware rethrows the original exception.
Since this middleware can re-execute the request pipeline:
_next or caching their processing on the HttpContext to avoid redoing it. When dealing with the request body, this either means buffering or caching the results like the Form reader.In the following example, xref:Microsoft.AspNetCore.Builder.ExceptionHandlerExtensions.UseExceptionHandler%2A adds the exception handling middleware in non-Development environments:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Program.cs" id="snippet_UseExceptionHandler" highlight="3,5":::
The Razor Pages app template provides an Error page (.cshtml) and xref:Microsoft.AspNetCore.Mvc.RazorPages.PageModel class (ErrorModel) in the Pages folder. For an MVC app, the project template includes an Error action method and an Error view for the Home controller.
The exception handling middleware re-executes the request using the original HTTP method. If an error handler endpoint is restricted to a specific set of HTTP methods, it runs only for those HTTP methods. For example, an MVC controller action that uses the [HttpGet] attribute runs only for GET requests. To ensure that all requests reach the custom error handling page, don't restrict them to a specific set of HTTP methods.
To handle exceptions differently based on the original HTTP method:
OnGet to handle GET exceptions and use OnPost to handle POST exceptions.[HttpGet] to handle GET exceptions and use [HttpPost] to handle POST exceptions.To allow unauthenticated users to view the custom error handling page, ensure that it supports anonymous access.
Use xref:Microsoft.AspNetCore.Diagnostics.IExceptionHandlerPathFeature to access the exception and the original request path in an error handler. The following example uses IExceptionHandlerPathFeature to get more information about the exception that was thrown:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Pages/Error.cshtml.cs" id="snippet_Class" highlight="15-27":::
[!WARNING] Do not serve sensitive error information to clients. Serving errors is a security risk.
An alternative to a custom exception handler page is to provide a lambda to xref:Microsoft.AspNetCore.Builder.ExceptionHandlerExtensions.UseExceptionHandler%2A. Using a lambda allows access to the error before returning the response.
The following code uses a lambda for exception handling:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseExceptionHandlerInline" highlight="5-29":::
Another way to use a lambda is to set the status code based on the exception type, as in the following example:
:::code language="csharp" source="~/fundamentals/error-handling/samples/9.x/ErrorHandlingSample/Program.cs" id="snippet_lambda" highlight="2,6-11":::
[!WARNING] Do not serve sensitive error information to clients. Serving errors is a security risk.
IExceptionHandler is an interface that gives the developer a callback for handling known exceptions in a central location. The interface contains a single method, TryHandleAsync, which receives an HttpContext and an Exception parameter.
IExceptionHandler implementations are registered by calling IServiceCollection.AddExceptionHandler<T>. The lifetime of an IExceptionHandler instance is singleton. Multiple implementations can be added, and they're called in the order registered.
Exception handling middleware iterates through registered exception handlers in order until one returns true from TryHandleAsync, indicating that the exception has been handled. If an exception handler handles an exception, it can return true to stop processing. If an exception isn't handled by any exception handler, then control falls back to the default behavior and options from the middleware.
Starting in .NET 10, the default behavior is to suppress emission of diagnostics such as logs and metrics for handled exceptions (when TryHandleAsync returns true). This differs from earlier versions (.NET 8 and 9) where diagnostics were always emitted regardless of whether the exception was handled. The default behavior can be changed by setting SuppressDiagnosticsCallback.
The following example shows an IExceptionHandler implementation:
:::code language="csharp" source="~/fundamentals/error-handling/samples/8.x/ErrorHandlingSample/CustomExceptionHandler.cs":::
The following example shows how to register an IExceptionHandler implementation for dependency injection:
:::code language="csharp" source="~/fundamentals/error-handling/samples/8.x/ErrorHandlingSample/Program.cs" id="snippet_RegisterIExceptionHandler" highlight="7":::
When the preceding code runs in the Development environment:
CustomExceptionHandler is called first to handle an exception.TryHandleAsync method returns false, so the developer exception page is shown.In other environments:
CustomExceptionHandler is called first to handle an exception.TryHandleAsync method returns false, so the /Error page is shown.Starting in .NET 10, you can control whether the exception handling middleware writes diagnostics for handled exceptions by configuring the SuppressDiagnosticsCallback property on ExceptionHandlerOptions. This callback receives the exception context and allows you to determine whether diagnostics should be suppressed based on the specific exception or request.
To revert to the .NET 8 and 9 behavior where diagnostics are always emitted for handled exceptions, set the callback to always return false:
app.UseExceptionHandler(new ExceptionHandlerOptions
{
SuppressDiagnosticsCallback = context => false
});
You can also conditionally suppress diagnostics based on the exception type or other context:
app.UseExceptionHandler(new ExceptionHandlerOptions
{
SuppressDiagnosticsCallback = context => context.Exception is ArgumentException
});
When an exception isn't handled by any IExceptionHandler implementation (all handlers return false from TryHandleAsync), control falls back to the default behavior and options from the middleware, and diagnostics are emitted according to the middleware's standard behavior.
<a name="sestatuscodepages"></a>
By default, an ASP.NET Core app doesn't provide a status code page for HTTP error status codes, such as 404 - Not Found. When the app sets an HTTP 400-599 error status code that doesn't have a body, it returns the status code and an empty response body. To enable default text-only handlers for common error status codes, call xref:Microsoft.AspNetCore.Builder.StatusCodePagesExtensions.UseStatusCodePages%2A in Program.cs:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseStatusCodePages" highlight="9":::
Call UseStatusCodePages before request handling middleware. For example, call UseStatusCodePages before the Static File Middleware and the Endpoints Middleware.
When UseStatusCodePages isn't used, navigating to a URL without an endpoint returns a browser-dependent error message indicating the endpoint can't be found. When UseStatusCodePages is called, the browser returns the following response:
Status Code: 404; Not Found
UseStatusCodePages isn't typically used in production because it returns a message that isn't useful to users.
[!NOTE] The status code pages middleware does not catch exceptions. To provide a custom error handling page, use the exception handler page.
To customize the response content type and text, use the overload of xref:Microsoft.AspNetCore.Builder.StatusCodePagesExtensions.UseStatusCodePages%2A that takes a content type and format string:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseStatusCodePagesContent" highlight="10":::
In the preceding code, {0} is a placeholder for the error code.
UseStatusCodePages with a format string isn't typically used in production because it returns a message that isn't useful to users.
To specify custom error-handling and response-writing code, use the overload of xref:Microsoft.AspNetCore.Builder.StatusCodePagesExtensions.UseStatusCodePages%2A that takes a lambda expression:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseStatusCodePagesInline" highlight="9-16":::
UseStatusCodePages with a lambda isn't typically used in production because it returns a message that isn't useful to users.
The xref:Microsoft.AspNetCore.Builder.StatusCodePagesExtensions.UseStatusCodePagesWithRedirects%2A extension method:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseStatusCodePagesRedirect" highlight="9":::
The URL template can include a {0} placeholder for the status code, as shown in the preceding code. If the URL template starts with ~ (tilde), the ~ is replaced by the app's PathBase. When specifying an endpoint in the app, create an MVC view or Razor page for the endpoint.
This method is commonly used when the app:
The xref:Microsoft.AspNetCore.Builder.StatusCodePagesExtensions.UseStatusCodePagesWithReExecute%2A extension method:
The new pipeline execution may alter the response's status code, as the new pipeline has full control of the status code. If the new pipeline does not alter the status code, the original status code will be sent to the client.
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseStatusCodePagesReExecute" highlight="9":::
If an endpoint within the app is specified, create an MVC view or Razor page for the endpoint.
This method is commonly used when the app should:
The URL template must start with / and may include a placeholder {0} for the status code. To pass the status code as a query-string parameter, pass a second argument into UseStatusCodePagesWithReExecute. For example:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_UseStatusCodePagesReExecuteQueryString":::
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Pages/StatusCode.cshtml.cs" id="snippet_Class" highlight="12-21":::
Since this middleware can re-execute the request pipeline:
_next or caching their processing on the HttpContext to avoid redoing it. When dealing with the request body, this either means buffering or caching the results like the Form reader.To disable status code pages for an MVC controller or action method, use the [SkipStatusCodePages] attribute.
To disable status code pages for specific requests in a Razor Pages handler method or in an MVC controller, use xref:Microsoft.AspNetCore.Diagnostics.IStatusCodePagesFeature:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Pages/Index.cshtml.cs" id="snippet_OnGet":::
Code in exception handling pages can also throw exceptions. Production error pages should be tested thoroughly and take extra care to avoid throwing exceptions of their own.
Once the headers for a response are sent:
In addition to the exception handling logic in an app, the HTTP server implementation can handle some exceptions. If the server catches an exception before response headers are sent, the server sends a 500 - Internal Server Error response without a response body. If the server catches an exception after response headers are sent, the server closes the connection. Requests that aren't handled by the app are handled by the server. Any exception that occurs when the server is handling the request is handled by the server's exception handling. The app's custom error pages, exception handling middleware, and filters don't affect this behavior.
Only the hosting layer can handle exceptions that take place during app startup. The host can be configured to capture startup errors and capture detailed errors.
The hosting layer can show an error page for a captured startup error only if the error occurs after host address/port binding. If binding fails:
When running on IIS (or Azure App Service) or IIS Express, a 502.5 - Process Failure is returned by the ASP.NET Core Module if the process can't start. For more information, see xref:test/troubleshoot-azure-iis.
The Database developer page exception filter xref:Microsoft.Extensions.DependencyInjection.DatabaseDeveloperPageExceptionFilterServiceExtensions.AddDatabaseDeveloperPageExceptionFilter%2A captures database-related exceptions that can be resolved by using Entity Framework Core migrations. When these exceptions occur, an HTML response is generated with details of possible actions to resolve the issue. This page is enabled only in the Development environment. The following code adds the Database developer page exception filter:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Program.cs" id="snippet_AddDatabaseDeveloperPageExceptionFilter" highlight="3":::
In MVC apps, exception filters can be configured globally or on a per-controller or per-action basis. In Razor Pages apps, they can be configured globally or per page model. These filters handle any unhandled exceptions that occur during the execution of a controller action or another filter. For more information, see xref:mvc/controllers/filters#exception-filters.
Exception filters are useful for trapping exceptions that occur within MVC actions, but they're not as flexible as the built-in exception handling middleware, xref:Microsoft.AspNetCore.Builder.ExceptionHandlerExtensions.UseExceptionHandler%2A. We recommend using UseExceptionHandler, unless you need to perform error handling differently based on which MVC action is chosen.
For information about how to handle model state errors, see Model binding and Model validation.
<a name="pds7"></a>
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:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_AddProblemDetails" highlight="1":::
The next section shows how to customize the problem details response body.
<a name="cpd7"></a>
The automatic creation of a ProblemDetails can be customized using any of the following options:
ProblemDetailsOptions.CustomizeProblemDetailsIProblemDetailsWriterIProblemDetailsService in a middlewareCustomizeProblemDetails operationThe generated problem details can be customized using xref:Microsoft.AspNetCore.Http.ProblemDetailsOptions.CustomizeProblemDetails, and the customizations are applied to all auto-generated problem details.
The following code uses xref:Microsoft.AspNetCore.Http.ProblemDetailsOptions to set xref:Microsoft.AspNetCore.Http.ProblemDetailsOptions.CustomizeProblemDetails:
:::code language="csharp" source="~/fundamentals/error-handling/samples/7.x/ErrorHandlingSample/Snippets/Program.cs" id="snippet_CustomizeProblemDetails" highlight="1-3":::
For example, an HTTP Status 400 Bad Request endpoint result produces the following problem details response body:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"nodeId": "my-machine-name"
}
IProblemDetailsWriterAn xref:Microsoft.AspNetCore.Http.IProblemDetailsWriter implementation can be created for advanced customizations.
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/SampleProblemDetailsWriter.cs" :::
Note: When using a custom IProblemDetailsWriter, the custom IProblemDetailsWriter must be registered before calling xref:Microsoft.Extensions.DependencyInjection.MvcServiceCollectionExtensions.AddRazorPages%2A, xref:Microsoft.Extensions.DependencyInjection.MvcServiceCollectionExtensions.AddControllers%2A, xref:Microsoft.Extensions.DependencyInjection.MvcServiceCollectionExtensions.AddControllersWithViews%2A, or xref:Microsoft.Extensions.DependencyInjection.MvcServiceCollectionExtensions.AddMvc%2A:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/Program.cs" id="snippet_sampleproblemdetailswriter" :::
An alternative approach to using xref:Microsoft.AspNetCore.Http.ProblemDetailsOptions with xref:Microsoft.AspNetCore.Http.ProblemDetailsOptions.CustomizeProblemDetails is to set the xref:Microsoft.AspNetCore.Http.ProblemDetailsContext.ProblemDetails in middleware. A problem details response can be written by calling IProblemDetailsService.WriteAsync:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/Program.cs" id="snippet_middleware" highlight="5,19-40":::
In the preceding code, the Minimal API endpoints /divide and /squareroot return the expected custom problem response on error input.
The API controller endpoints return the default problem response on error input, not the custom problem response. The default problem response is returned because the API controller has written to the response stream, Problem details for error status codes, before IProblemDetailsService.WriteAsync is called and the response is not written again.
The following ValuesController returns xref:Microsoft.AspNetCore.Mvc.BadRequestResult, which writes to the response stream and therefore prevents the custom problem response from being returned.
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/Controllers/ValuesController.cs" id="snippet" highlight="9-17,27-35":::
The following Values3Controller returns ControllerBase.Problem so the expected custom problem result is returned:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/Controllers/ValuesController.cs" id="snippet3" highlight="16-21":::
Consider the following app:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/Program.cs" id="snippet_apishort" highlight="4,8":::
In non-development environments, when an exception occurs, the following is a standard ProblemDetails response that is returned to the client:
{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}
For most apps, the preceding code is all that's needed for exceptions. However, the following section shows how to get more detailed problem responses.
An alternative to a custom exception handler page is to provide a lambda to xref:Microsoft.AspNetCore.Builder.ExceptionHandlerExtensions.UseExceptionHandler%2A. Using a lambda allows access to the error and writing a problem details response with IProblemDetailsService.WriteAsync:
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/problem-details-service/Program.cs" id="snippet_lambda" :::
[!WARNING] Do not serve sensitive error information to clients. Serving errors is a security risk.
An alternative approach to generate problem details is to use the third-party NuGet package Hellang.Middleware.ProblemDetails that can be used to map exceptions and client errors to problem details.
IExceptionHandler.TryHandleAsync returns true:::moniker-end