Događaj
Power BI DataViz Svetsko prvenstvo
14. feb 16 - 31. mar 16
Sa 4 šanse za ulazak, možete osvojiti konferencijski paket i stići do LIVE Grand Finale u Las Vegasu
Saznajte višeOvaj pregledač više nije podržan.
Nadogradite na Microsoft Edge biste iskoristili najnovije funkcije, bezbednosne ispravke i tehničku podršku.
Napomena
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Upozorenje
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.
Važno
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.
By Tom Dykstra
This article covers common approaches to handling errors in ASP.NET Core web apps. See also Handle errors in ASP.NET Core controller-based web APIs and Handle errors in minimal APIs.
For Blazor error handling guidance, which adds to or supersedes the guidance in this article, see Handle errors in ASP.NET Core Blazor apps.
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:
Apps created using earlier templates, that is, by using WebHost.CreateDefaultBuilder, can enable the developer exception page by calling app.UseDeveloperExceptionPage
.
Upozorenje
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:
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 configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware:
/Error
path.Upozorenje
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, UseExceptionHandler adds the exception handling middleware in non-Development environments:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The Razor Pages app template provides an Error page (.cshtml
) and 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 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:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string? ExceptionMessage { get; set; }
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}
if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}
Upozorenje
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 UseExceptionHandler. Using a lambda allows access to the error before returning the response.
The following code uses a lambda for exception handling:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
app.UseHsts();
}
Another way to use a lambda is to set the status code based on the exception type, as in the following example:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseExceptionHandler(new ExceptionHandlerOptions
{
StatusCodeSelector = ex => ex is TimeoutException
? StatusCodes.Status503ServiceUnavailable
: StatusCodes.Status500InternalServerError
});
}
Upozorenje
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.
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.
If an exception handler handles a request, 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. Different metrics and logs are emitted for handled versus unhandled exceptions.
The following example shows an IExceptionHandler
implementation:
using Microsoft.AspNetCore.Diagnostics;
namespace ErrorHandlingSample
{
public class CustomExceptionHandler : IExceptionHandler
{
private readonly ILogger<CustomExceptionHandler> logger;
public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
{
this.logger = logger;
}
public ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
var exceptionMessage = exception.Message;
logger.LogError(
"Error Message: {exceptionMessage}, Time of occurrence {time}",
exceptionMessage, DateTime.UtcNow);
// Return false to continue with the default behavior
// - or - return true to signal that this exception is handled
return ValueTask.FromResult(false);
}
}
}
The following example shows how to register an IExceptionHandler
implementation for dependency injection:
using ErrorHandlingSample;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Remaining Program.cs code omitted for brevity
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.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 UseStatusCodePages in Program.cs
:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
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.
Napomena
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 UseStatusCodePages that takes a content type and format string:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");
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 UseStatusCodePages that takes a lambda expression:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(async statusCodeContext =>
{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;
await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});
UseStatusCodePages
with a lambda isn't typically used in production because it returns a message that isn't useful to users.
The UseStatusCodePagesWithRedirects extension method:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
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 UseStatusCodePagesWithReExecute 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.
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");
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:
var app = builder.Build();
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
public int OriginalStatusCode { get; set; }
public string? OriginalPathAndQuery { get; set; }
public void OnGet(int statusCode)
{
OriginalStatusCode = statusCode;
var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature is not null)
{
OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
+ $"{statusCodeReExecuteFeature.OriginalPath}"
+ $"{statusCodeReExecuteFeature.OriginalQueryString}";
}
}
}
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 IStatusCodePagesFeature:
public void OnGet()
{
var statusCodePagesFeature =
HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature is not null)
{
statusCodePagesFeature.Enabled = false;
}
}
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 Troubleshoot ASP.NET Core on Azure App Service and IIS.
The Database developer page exception filter AddDatabaseDeveloperPageExceptionFilter 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:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
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 Filters in ASP.NET Core.
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, UseExceptionHandler. 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.
Problem Details are not the only response format to describe an HTTP API error, however, they are commonly used to report errors for HTTP APIs.
The problem details service implements the IProblemDetailsService interface, which supports creating problem details in ASP.NET Core. The AddProblemDetails(IServiceCollection) extension method on IServiceCollection registers the default IProblemDetailsService
implementation.
In ASP.NET Core apps, the following middleware generates problem details HTTP responses when AddProblemDetails
is called, except when the Accept
request HTTP header doesn't include one of the content types supported by the registered IProblemDetailsWriter (default: application/json
):
Accept
request HTTP header doesn't include text/html
.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:
builder.Services.AddProblemDetails();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
The next section shows how to customize the problem details response body.
The automatic creation of a ProblemDetails
can be customized using any of the following options:
ProblemDetailsOptions.CustomizeProblemDetails
IProblemDetailsWriter
IProblemDetailsService
in a middlewareThe generated problem details can be customized using CustomizeProblemDetails, and the customizations are applied to all auto-generated problem details.
The following code uses ProblemDetailsOptions to set CustomizeProblemDetails:
builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
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"
}
An IProblemDetailsWriter implementation can be created for advanced customizations.
public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
// Indicates that only responses with StatusCode == 400
// are handled by this writer. All others are
// handled by different registered writers if available.
public bool CanWrite(ProblemDetailsContext context)
=> context.HttpContext.Response.StatusCode == 400;
public ValueTask WriteAsync(ProblemDetailsContext context)
{
// Additional customizations.
// Write to the response.
var response = context.HttpContext.Response;
return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
}
}
Note: When using a custom IProblemDetailsWriter
, the custom IProblemDetailsWriter
must be registered before calling AddRazorPages, AddControllers, AddControllersWithViews, or AddMvc:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();
var app = builder.Build();
// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsWriter>() is
{ } problemDetailsService)
{
if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(numerator / denominator);
});
// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(Math.Sqrt(radicand));
});
app.Run();
An alternative approach to using ProblemDetailsOptions with CustomizeProblemDetails is to set the ProblemDetails in middleware. A problem details response can be written by calling IProblemDetailsService.WriteAsync
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStatusCodePages();
// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ } problemDetailsService)
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature { MathError =
MathErrorType.DivisionByZeroError };
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(numerator / denominator);
});
// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature { MathError =
MathErrorType.NegativeRadicandError };
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(Math.Sqrt(radicand));
});
app.MapControllers();
app.Run();
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 BadRequestResult, which writes to the response stream and therefore prevents the custom problem response from being returned.
[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 Values3Controller
returns ControllerBase.Problem
so the expected custom problem result is returned:
[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
// /api/values3/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 Problem(
title: "Bad Input",
detail: "Divison by zero is not defined.",
type: "https://en.wikipedia.org/wiki/Division_by_zero",
statusCode: StatusCodes.Status400BadRequest
);
}
return Ok(Numerator / Denominator);
}
// /api/values3/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Negative or complex numbers are not valid input.",
type: "https://en.wikipedia.org/wiki/Square_root",
statusCode: StatusCodes.Status400BadRequest
);
}
return Ok(Math.Sqrt(radicand));
}
}
Consider the following app:
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();
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 UseExceptionHandler. Using a lambda allows access to the error and writing a problem details response with IProblemDetailsService.WriteAsync
:
using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
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();
}
else
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = Text.Plain;
var title = "Bad Input";
var detail = "Invalid input";
var type = "https://errors.example.com/badInput";
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ } problemDetailsService)
{
var exceptionHandlerFeature =
context.Features.Get<IExceptionHandlerFeature>();
var exceptionType = exceptionHandlerFeature?.Error;
if (exceptionType != null &&
exceptionType.Message.Contains("infinity"))
{
title = "Argument exception";
detail = "Invalid input";
type = "https://errors.example.com/argumentException";
}
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = title,
Detail = detail,
Type = type
}
});
}
});
});
}
app.MapControllers();
app.Run();
Upozorenje
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.
By Tom Dykstra
This article covers common approaches to handling errors in ASP.NET Core web apps. See also Handle errors in ASP.NET Core controller-based web APIs and Handle errors in minimal APIs.
The Developer Exception Page displays detailed information about unhandled request exceptions. ASP.NET Core apps enable the developer exception page by default when both:
WebHost.CreateDefaultBuilder
must enable the developer exception page by calling app.UseDeveloperExceptionPage
in Configure
.The developer exception page runs early in the middleware pipeline, so that it can catch unhandled exceptions thrown in middleware that follows.
Detailed exception information shouldn't be displayed publicly when the app runs in the Production environment. 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:
The Developer Exception Page isn't guaranteed to provide any information. Use Logging for complete error information.
To configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware:
/Error
path.Upozorenje
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, UseExceptionHandler adds the exception handling middleware in non-Development environments:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The Razor Pages app template provides an Error page (.cshtml
) and 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 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:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string? ExceptionMessage { get; set; }
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}
if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}
Upozorenje
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 UseExceptionHandler. Using a lambda allows access to the error before returning the response.
The following code uses a lambda for exception handling:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
app.UseHsts();
}
Upozorenje
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.
IExceptionHandler
implementations are registered by calling IServiceCollection.AddExceptionHandler<T>
[IServiceCollection.AddExceptionHandler<T>
]. The lifetime of an IExceptionHandler
instance is singleton. Multiple implementations can be added, and they're called in the order registered.
If an exception handler handles a request, 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. Different metrics and logs are emitted for handled versus unhandled exceptions.
The following example shows an IExceptionHandler
implementation:
using Microsoft.AspNetCore.Diagnostics;
namespace ErrorHandlingSample
{
public class CustomExceptionHandler : IExceptionHandler
{
private readonly ILogger<CustomExceptionHandler> logger;
public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
{
this.logger = logger;
}
public ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
var exceptionMessage = exception.Message;
logger.LogError(
"Error Message: {exceptionMessage}, Time of occurrence {time}",
exceptionMessage, DateTime.UtcNow);
// Return false to continue with the default behavior
// - or - return true to signal that this exception is handled
return ValueTask.FromResult(false);
}
}
}
The following example shows how to register an IExceptionHandler
implementation for dependency injection:
using ErrorHandlingSample;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Remaining Program.cs code omitted for brevity
When the preceding code runs in the Development environment:
CustomExceptionHandler
is called first to handle an exception.TryHandleException
method returns false
, so the developer exception page is shown.In other environments:
CustomExceptionHandler
is called first to handle an exception.TryHandleException
method returns false
, so the /Error
page is shown.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 UseStatusCodePages in Program.cs
:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
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.
Napomena
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 UseStatusCodePages that takes a content type and format string:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");
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 UseStatusCodePages that takes a lambda expression:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(async statusCodeContext =>
{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;
await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});
UseStatusCodePages
with a lambda isn't typically used in production because it returns a message that isn't useful to users.
The UseStatusCodePagesWithRedirects extension method:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
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 UseStatusCodePagesWithReExecute 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.
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");
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:
var app = builder.Build();
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
public int OriginalStatusCode { get; set; }
public string? OriginalPathAndQuery { get; set; }
public void OnGet(int statusCode)
{
OriginalStatusCode = statusCode;
var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature is not null)
{
OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
+ $"{statusCodeReExecuteFeature.OriginalPath}"
+ $"{statusCodeReExecuteFeature.OriginalQueryString}";
}
}
}
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 IStatusCodePagesFeature:
public void OnGet()
{
var statusCodePagesFeature =
HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature is not null)
{
statusCodePagesFeature.Enabled = false;
}
}
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 Troubleshoot ASP.NET Core on Azure App Service and IIS.
The Database developer page exception filter AddDatabaseDeveloperPageExceptionFilter 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:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
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 Filters in ASP.NET Core.
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, UseExceptionHandler. 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.
Problem Details are not the only response format to describe an HTTP API error, however, they are commonly used to report errors for HTTP APIs.
The problem details service implements the IProblemDetailsService interface, which supports creating problem details in ASP.NET Core. The AddProblemDetails(IServiceCollection) extension method on IServiceCollection registers the default IProblemDetailsService
implementation.
In ASP.NET Core apps, the following middleware generates problem details HTTP responses when AddProblemDetails
is called, except when the Accept
request HTTP header doesn't include one of the content types supported by the registered IProblemDetailsWriter (default: application/json
):
Accept
request HTTP header doesn't include text/html
.The following code configures the app to generate a problem details response for all HTTP client and server error responses that don't have a body content yet:
builder.Services.AddProblemDetails();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
The next section shows how to customize the problem details response body.
The automatic creation of a ProblemDetails
can be customized using any of the following options:
ProblemDetailsOptions.CustomizeProblemDetails
IProblemDetailsWriter
IProblemDetailsService
in a middlewareThe generated problem details can be customized using CustomizeProblemDetails, and the customizations are applied to all auto-generated problem details.
The following code uses ProblemDetailsOptions to set CustomizeProblemDetails:
builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
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"
}
An IProblemDetailsWriter implementation can be created for advanced customizations.
public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
// Indicates that only responses with StatusCode == 400
// are handled by this writer. All others are
// handled by different registered writers if available.
public bool CanWrite(ProblemDetailsContext context)
=> context.HttpContext.Response.StatusCode == 400;
public ValueTask WriteAsync(ProblemDetailsContext context)
{
// Additional customizations.
// Write to the response.
var response = context.HttpContext.Response;
return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
}
}
Note: When using a custom IProblemDetailsWriter
, the custom IProblemDetailsWriter
must be registered before calling AddRazorPages, AddControllers, AddControllersWithViews, or AddMvc:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();
var app = builder.Build();
// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsWriter>() is
{ } problemDetailsService)
{
if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(numerator / denominator);
});
// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(Math.Sqrt(radicand));
});
app.Run();
An alternative approach to using ProblemDetailsOptions with CustomizeProblemDetails is to set the ProblemDetails in middleware. A problem details response can be written by calling IProblemDetailsService.WriteAsync
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStatusCodePages();
// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ } problemDetailsService)
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature { MathError =
MathErrorType.DivisionByZeroError };
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(numerator / denominator);
});
// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature { MathError =
MathErrorType.NegativeRadicandError };
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(Math.Sqrt(radicand));
});
app.MapControllers();
app.Run();
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 BadRequestResult, which writes to the response stream and therefore prevents the custom problem response from being returned.
[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 Values3Controller
returns ControllerBase.Problem
so the expected custom problem result is returned:
[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
// /api/values3/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 Problem(
title: "Bad Input",
detail: "Divison by zero is not defined.",
type: "https://en.wikipedia.org/wiki/Division_by_zero",
statusCode: StatusCodes.Status400BadRequest
);
}
return Ok(Numerator / Denominator);
}
// /api/values3/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Negative or complex numbers are not valid input.",
type: "https://en.wikipedia.org/wiki/Square_root",
statusCode: StatusCodes.Status400BadRequest
);
}
return Ok(Math.Sqrt(radicand));
}
}
Consider the following app:
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();
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 UseExceptionHandler. Using a lambda allows access to the error and writing a problem details response with IProblemDetailsService.WriteAsync
:
using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
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();
}
else
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = Text.Plain;
var title = "Bad Input";
var detail = "Invalid input";
var type = "https://errors.example.com/badInput";
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ } problemDetailsService)
{
var exceptionHandlerFeature =
context.Features.Get<IExceptionHandlerFeature>();
var exceptionType = exceptionHandlerFeature?.Error;
if (exceptionType != null &&
exceptionType.Message.Contains("infinity"))
{
title = "Argument exception";
detail = "Invalid input";
type = "https://errors.example.com/argumentException";
}
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = title,
Detail = detail,
Type = type
}
});
}
});
});
}
app.MapControllers();
app.Run();
Upozorenje
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.
By Tom Dykstra
This article covers common approaches to handling errors in ASP.NET Core web apps. See also Handle errors in ASP.NET Core controller-based web APIs and Handle errors in minimal APIs.
The Developer Exception Page displays detailed information about unhandled request exceptions. ASP.NET Core apps enable the developer exception page by default when both:
WebHost.CreateDefaultBuilder
must enable the developer exception page by calling app.UseDeveloperExceptionPage
in Configure
.The developer exception page runs early in the middleware pipeline, so that it can catch unhandled exceptions thrown in middleware that follows.
Detailed exception information shouldn't be displayed publicly when the app runs in the Production environment. 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:
The Developer Exception Page isn't guaranteed to provide any information. Use Logging for complete error information.
To configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware:
/Error
path.Upozorenje
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, UseExceptionHandler adds the exception handling middleware in non-Development environments:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The Razor Pages app template provides an Error page (.cshtml
) and 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 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:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string? ExceptionMessage { get; set; }
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}
if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}
Upozorenje
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 UseExceptionHandler. Using a lambda allows access to the error before returning the response.
The following code uses a lambda for exception handling:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
app.UseHsts();
}
Upozorenje
Do not serve sensitive error information to clients. Serving errors is a security risk.
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 UseStatusCodePages in Program.cs
:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
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.
Napomena
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 UseStatusCodePages that takes a content type and format string:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");
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 UseStatusCodePages that takes a lambda expression:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(async statusCodeContext =>
{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;
await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});
UseStatusCodePages
with a lambda isn't typically used in production because it returns a message that isn't useful to users.
The UseStatusCodePagesWithRedirects extension method:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
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 UseStatusCodePagesWithReExecute 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.
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");
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:
var app = builder.Build();
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
public int OriginalStatusCode { get; set; }
public string? OriginalPathAndQuery { get; set; }
public void OnGet(int statusCode)
{
OriginalStatusCode = statusCode;
var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature is not null)
{
OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
+ $"{statusCodeReExecuteFeature.OriginalPath}"
+ $"{statusCodeReExecuteFeature.OriginalQueryString}";
}
}
}
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 IStatusCodePagesFeature:
public void OnGet()
{
var statusCodePagesFeature =
HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature is not null)
{
statusCodePagesFeature.Enabled = false;
}
}
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 Troubleshoot ASP.NET Core on Azure App Service and IIS.
The Database developer page exception filter AddDatabaseDeveloperPageExceptionFilter 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:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
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 Filters in ASP.NET Core.
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, UseExceptionHandler. 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.
Problem Details are not the only response format to describe an HTTP API error, however, they are commonly used to report errors for HTTP APIs.
The problem details service implements the IProblemDetailsService interface, which supports creating problem details in ASP.NET Core. The AddProblemDetails(IServiceCollection) extension method on IServiceCollection registers the default IProblemDetailsService
implementation.
In ASP.NET Core apps, the following middleware generates problem details HTTP responses when AddProblemDetails
is called, except when the Accept
request HTTP header doesn't include one of the content types supported by the registered IProblemDetailsWriter (default: application/json
):
Accept
request HTTP header doesn't include text/html
.The following code configures the app to generate a problem details response for all HTTP client and server error responses that don't have a body content yet:
builder.Services.AddProblemDetails();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
The next section shows how to customize the problem details response body.
The automatic creation of a ProblemDetails
can be customized using any of the following options:
ProblemDetailsOptions.CustomizeProblemDetails
IProblemDetailsWriter
IProblemDetailsService
in a middlewareThe generated problem details can be customized using CustomizeProblemDetails, and the customizations are applied to all auto-generated problem details.
The following code uses ProblemDetailsOptions to set CustomizeProblemDetails:
builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
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"
}
An IProblemDetailsWriter implementation can be created for advanced customizations.
public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
// Indicates that only responses with StatusCode == 400
// are handled by this writer. All others are
// handled by different registered writers if available.
public bool CanWrite(ProblemDetailsContext context)
=> context.HttpContext.Response.StatusCode == 400;
public ValueTask WriteAsync(ProblemDetailsContext context)
{
// Additional customizations.
// Write to the response.
var response = context.HttpContext.Response;
return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
}
}
Note: When using a custom IProblemDetailsWriter
, the custom IProblemDetailsWriter
must be registered before calling AddRazorPages, AddControllers, AddControllersWithViews, or AddMvc:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();
var app = builder.Build();
// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsWriter>() is
{ } problemDetailsService)
{
if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(numerator / denominator);
});
// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(Math.Sqrt(radicand));
});
app.Run();
An alternative approach to using ProblemDetailsOptions with CustomizeProblemDetails is to set the ProblemDetails in middleware. A problem details response can be written by calling IProblemDetailsService.WriteAsync
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStatusCodePages();
// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ } problemDetailsService)
{
(string Detail, string Type) details = mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature { MathError =
MathErrorType.DivisionByZeroError };
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(numerator / denominator);
});
// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature { MathError =
MathErrorType.NegativeRadicandError };
context.Features.Set(errorType);
return Results.BadRequest();
}
return Results.Ok(Math.Sqrt(radicand));
});
app.MapControllers();
app.Run();
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 BadRequestResult, which writes to the response stream and therefore prevents the custom problem response from being returned.
[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 Values3Controller
returns ControllerBase.Problem
so the expected custom problem result is returned:
[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
// /api/values3/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 Problem(
title: "Bad Input",
detail: "Divison by zero is not defined.",
type: "https://en.wikipedia.org/wiki/Division_by_zero",
statusCode: StatusCodes.Status400BadRequest
);
}
return Ok(Numerator / Denominator);
}
// /api/values3/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Negative or complex numbers are not valid input.",
type: "https://en.wikipedia.org/wiki/Square_root",
statusCode: StatusCodes.Status400BadRequest
);
}
return Ok(Math.Sqrt(radicand));
}
}
Consider the following app:
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();
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 UseExceptionHandler. Using a lambda allows access to the error and writing a problem details response with IProblemDetailsService.WriteAsync
:
using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
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();
}
else
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = Text.Plain;
var title = "Bad Input";
var detail = "Invalid input";
var type = "https://errors.example.com/badInput";
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ } problemDetailsService)
{
var exceptionHandlerFeature =
context.Features.Get<IExceptionHandlerFeature>();
var exceptionType = exceptionHandlerFeature?.Error;
if (exceptionType != null &&
exceptionType.Message.Contains("infinity"))
{
title = "Argument exception";
detail = "Invalid input";
type = "https://errors.example.com/argumentException";
}
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = title,
Detail = detail,
Type = type
}
});
}
});
});
}
app.MapControllers();
app.Run();
Upozorenje
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.
By Tom Dykstra
This article covers common approaches to handling errors in ASP.NET Core web apps. See Handle errors in ASP.NET Core controller-based web APIs for web APIs.
The Developer Exception Page displays detailed information about unhandled request exceptions. ASP.NET Core apps enable the developer exception page by default when both:
WebHost.CreateDefaultBuilder
must enable the developer exception page by calling app.UseDeveloperExceptionPage
in Configure
.The developer exception page runs early in the middleware pipeline, so that it can catch unhandled exceptions thrown in middleware that follows.
Detailed exception information shouldn't be displayed publicly when the app runs in the Production environment. 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:
The Developer Exception Page isn't guaranteed to provide any information. Use Logging for complete error information.
To configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware:
/Error
path.Upozorenje
If the alternate pipeline throws an exception of its own, Exception Handling Middleware rethrows the original exception.
In the following example, UseExceptionHandler adds the exception handling middleware in non-Development environments:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The Razor Pages app template provides an Error page (.cshtml
) and 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 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:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string? ExceptionMessage { get; set; }
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}
if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}
Upozorenje
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 UseExceptionHandler. Using a lambda allows access to the error before returning the response.
The following code uses a lambda for exception handling:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
app.UseHsts();
}
Upozorenje
Do not serve sensitive error information to clients. Serving errors is a security risk.
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 UseStatusCodePages in Program.cs
:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
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.
Napomena
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 UseStatusCodePages that takes a content type and format string:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");
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 UseStatusCodePages that takes a lambda expression:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(async statusCodeContext =>
{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;
await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});
UseStatusCodePages
with a lambda isn't typically used in production because it returns a message that isn't useful to users.
The UseStatusCodePagesWithRedirects extension method:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
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 UseStatusCodePagesWithReExecute extension method:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");
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:
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
public int OriginalStatusCode { get; set; }
public string? OriginalPathAndQuery { get; set; }
public void OnGet(int statusCode)
{
OriginalStatusCode = statusCode;
var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature is not null)
{
OriginalPathAndQuery = string.Join(
statusCodeReExecuteFeature.OriginalPathBase,
statusCodeReExecuteFeature.OriginalPath,
statusCodeReExecuteFeature.OriginalQueryString);
}
}
}
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 IStatusCodePagesFeature:
public void OnGet()
{
var statusCodePagesFeature =
HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature is not null)
{
statusCodePagesFeature.Enabled = false;
}
}
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 Troubleshoot ASP.NET Core on Azure App Service and IIS.
The Database developer page exception filter AddDatabaseDeveloperPageExceptionFilter 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:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
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 Filters in ASP.NET Core.
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, UseExceptionHandler. 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.
By Kirk Larkin, Tom Dykstra, and Steve Smith
This article covers common approaches to handling errors in ASP.NET Core web apps. See Handle errors in ASP.NET Core controller-based web APIs for web APIs.
View or download sample code. (How to download.) The network tab on the F12 browser developer tools is useful when testing the sample app.
The Developer Exception Page displays detailed information about unhandled request exceptions. The ASP.NET Core templates generate the following code:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
The preceding highlighted code enables the developer exception page when the app is running in the Development environment.
The templates place UseDeveloperExceptionPage early in the middleware pipeline so that it can catch unhandled exceptions thrown in middleware that follows.
The preceding code enables the Developer Exception Page only when the app runs in the Development environment. Detailed exception information shouldn't be displayed publicly when the app runs in the Production environment. 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:
The Developer Exception Page isn't guaranteed to provide any information. Use Logging for complete error information.
To configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware:
/Error
path.Upozorenje
If the alternate pipeline throws an exception of its own, Exception Handling Middleware rethrows the original exception.
In the following example, UseExceptionHandler adds the exception handling middleware in non-Development environments:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The Razor Pages app template provides an Error page (.cshtml
) and 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 IExceptionHandlerPathFeature to access the exception and the original request path in an error handler. The following code adds ExceptionMessage
to the default Pages/Error.cshtml.cs
generated by the ASP.NET Core templates:
[ResponseCache(Duration=0, Location=ResponseCacheLocation.None, NoStore=true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string ExceptionMessage { get; set; }
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "File error thrown";
_logger.LogError(ExceptionMessage);
}
if (exceptionHandlerPathFeature?.Path == "/index")
{
ExceptionMessage += " from home page";
}
}
}
Upozorenje
Do not serve sensitive error information to clients. Serving errors is a security risk.
To test the exception in the sample app:
webBuilder.UseStartup<Startup>();
in Program.cs
.An alternative to a custom exception handler page is to provide a lambda to UseExceptionHandler. Using a lambda allows access to the error before returning the response.
The following code uses a lambda for exception handling:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;;
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
await context.Response.WriteAsync("ERROR!<br><br>\r\n");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(
"File error thrown!<br><br>\r\n");
}
await context.Response.WriteAsync(
"<a href=\"/\">Home</a><br>\r\n");
await context.Response.WriteAsync("</body></html>\r\n");
await context.Response.WriteAsync(new string(' ', 512));
});
});
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
Upozorenje
Do not serve sensitive error information from IExceptionHandlerFeature or IExceptionHandlerPathFeature to clients. Serving errors is a security risk.
To test the exception handling lambda in the sample app:
webBuilder.UseStartup<StartupLambda>();
in Program.cs
.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 provide status code pages, use the status code pages middleware. To enable default text-only handlers for common error status codes, call UseStatusCodePages in the Startup.Configure
method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
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. For example, navigating to Home/Privacy2
. When UseStatusCodePages
is called, the browser returns:
Status Code: 404; Not Found
UseStatusCodePages
isn't typically used in production because it returns a message that isn't useful to users.
To test UseStatusCodePages
in the sample app:
webBuilder.UseStartup<StartupUseStatusCodePages>();
in Program.cs
.Napomena
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 UseStatusCodePages that takes a content type and format string:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(
"text/plain", "Status code page, status code: {0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
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 test UseStatusCodePages
in the sample app, remove the comments from webBuilder.UseStartup<StartupFormat>();
in Program.cs
.
To specify custom error-handling and response-writing code, use the overload of UseStatusCodePages that takes a lambda expression:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages(async context =>
{
context.HttpContext.Response.ContentType = "text/plain";
await context.HttpContext.Response.WriteAsync(
"Status code page, status code: " +
context.HttpContext.Response.StatusCode);
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
UseStatusCodePages
with a lambda isn't typically used in production because it returns a message that isn't useful to users.
To test UseStatusCodePages
in the sample app, remove the comments from webBuilder.UseStartup<StartupStatusLambda>();
in Program.cs
.
The UseStatusCodePagesWithRedirects extension method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
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. For a Razor Pages example, see Pages/MyStatusCode.cshtml in the sample app.
This method is commonly used when the app:
To test UseStatusCodePages
in the sample app, remove the comments from webBuilder.UseStartup<StartupSCredirect>();
in Program.cs
.
The UseStatusCodePagesWithReExecute extension method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/MyStatusCode2", "?code={0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
If an endpoint within the app is specified, create an MVC view or Razor page for the endpoint. Ensure UseStatusCodePagesWithReExecute
is placed before UseRouting
so the request can be rerouted to the status page. For a Razor Pages example, see Pages/MyStatusCode2.cshtml in the sample app.
This method is commonly used when the app should:
The URL and query string templates may include a placeholder {0}
for the status code. The URL template must start with /
.
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class MyStatusCode2Model : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string ErrorStatusCode { get; set; }
public string OriginalURL { get; set; }
public bool ShowOriginalURL => !string.IsNullOrEmpty(OriginalURL);
public void OnGet(string code)
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
ErrorStatusCode = code;
var statusCodeReExecuteFeature = HttpContext.Features.Get<
IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
OriginalURL =
statusCodeReExecuteFeature.OriginalPathBase
+ statusCodeReExecuteFeature.OriginalPath
+ statusCodeReExecuteFeature.OriginalQueryString;
}
}
}
For a Razor Pages example, see Pages/MyStatusCode2.cshtml in the sample app.
To test UseStatusCodePages
in the sample app, remove the comments from webBuilder.UseStartup<StartupSCreX>();
in Program.cs
.
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 IStatusCodePagesFeature:
public void OnGet()
{
// using Microsoft.AspNetCore.Diagnostics;
var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
statusCodePagesFeature.Enabled = false;
}
}
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 Troubleshoot ASP.NET Core on Azure App Service and IIS.
The Database developer page exception filter AddDatabaseDeveloperPageExceptionFilter
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 was generated by the ASP.NET Core Razor Pages templates when individual user accounts were specified:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
}
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 Filters in ASP.NET Core.
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, UseExceptionHandler
. We recommend using UseExceptionHandler
, unless you need to perform error handling differently based on which MVC action is chosen.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
For information about how to handle model state errors, see Model binding and Model validation.
By Tom Dykstra, and Steve Smith
This article covers common approaches to handling errors in ASP.NET Core web apps. See Handle errors in ASP.NET Core controller-based web APIs for web APIs.
View or download sample code. (How to download.)
The Developer Exception Page displays detailed information about request exceptions. The ASP.NET Core templates generate the following code:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The preceding code enables the developer exception page when the app is running in the Development environment.
The templates place UseDeveloperExceptionPage before any middleware so exceptions are caught in the middleware that follows.
The preceding code enables the Developer Exception Page only when the app is running in the Development environment. Detailed exception information should not be displayed 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 includes the following information about the exception and the request:
To configure a custom error handling page for the Production environment, use the Exception Handling Middleware. The middleware:
/Error
.In the following example, UseExceptionHandler adds the Exception Handling Middleware in non-Development environments:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
The Razor Pages app template provides an Error page (.cshtml
) and PageModel class (ErrorModel
) in the Pages folder. For an MVC app, the project template includes an Error action method and an Error view in the Home controller.
Don't mark the error handler action method with HTTP method attributes, such as HttpGet
. Explicit verbs prevent some requests from reaching the method. Allow anonymous access to the method if unauthenticated users should see the error view.
Use IExceptionHandlerPathFeature to access the exception and the original request path in an error handler controller or page:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string ExceptionMessage { get; set; }
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "File error thrown";
}
if (exceptionHandlerPathFeature?.Path == "/index")
{
ExceptionMessage += " from home page";
}
}
}
Upozorenje
Do not serve sensitive error information to clients. Serving errors is a security risk.
To trigger the preceding exception handling page, set the environment to productions and force an exception.
An alternative to a custom exception handler page is to provide a lambda to UseExceptionHandler. Using a lambda allows access to the error before returning the response.
Here's an example of using a lambda for exception handling:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
await context.Response.WriteAsync("ERROR!<br><br>\r\n");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync("File error thrown!<br><br>\r\n");
}
await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n");
await context.Response.WriteAsync("</body></html>\r\n");
await context.Response.WriteAsync(new string(' ', 512)); // IE padding
});
});
app.UseHsts();
}
In the preceding code, await context.Response.WriteAsync(new string(' ', 512));
is added so the Internet Explorer browser displays the error message rather than an IE error message. For more information, see this GitHub issue.
Upozorenje
Do not serve sensitive error information from IExceptionHandlerFeature or IExceptionHandlerPathFeature to clients. Serving errors is a security risk.
To see the result of the exception handling lambda in the sample app, use the ProdEnvironment
and ErrorHandlerLambda
preprocessor directives, and select Trigger an exception on the home page.
By default, an ASP.NET Core app doesn't provide a status code page for HTTP status codes, such as 404 - Not Found. The app returns a status code and an empty response body. To provide status code pages, use Status Code Pages middleware.
The middleware is made available by the Microsoft.AspNetCore.Diagnostics package.
To enable default text-only handlers for common error status codes, call UseStatusCodePages in the Startup.Configure
method:
app.UseStatusCodePages();
Call UseStatusCodePages
before request handling middleware (for example, Static File Middleware and MVC 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. For example, navigating to Home/Privacy2
. When UseStatusCodePages
is called, the browser returns:
Status Code: 404; Not Found
To customize the response content type and text, use the overload of UseStatusCodePages that takes a content type and format string:
app.UseStatusCodePages(
"text/plain", "Status code page, status code: {0}");
To specify custom error-handling and response-writing code, use the overload of UseStatusCodePages that takes a lambda expression:
app.UseStatusCodePages(async context =>
{
context.HttpContext.Response.ContentType = "text/plain";
await context.HttpContext.Response.WriteAsync(
"Status code page, status code: " +
context.HttpContext.Response.StatusCode);
});
The UseStatusCodePagesWithRedirects extension method:
app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");
The URL template can include a {0}
placeholder for the status code, as shown in the example. If the URL template starts with ~
(tilde), the ~
is replaced by the app's PathBase
. If you point to an endpoint within the app, create an MVC view or Razor page for the endpoint. For a Razor Pages example, see Pages/StatusCode.cshtml
in the sample app.
This method is commonly used when the app:
The UseStatusCodePagesWithReExecute extension method:
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");
If you point to an endpoint within the app, create an MVC view or Razor page for the endpoint. Ensure UseStatusCodePagesWithReExecute
is placed before UseRouting
so the request can be rerouted to the status page. For a Razor Pages example, see Pages/StatusCode.cshtml
in the sample app.
This method is commonly used when the app should:
The URL and query string templates may include a placeholder ({0}
) for the status code. The URL template must start with a slash (/
). When using a placeholder in the path, confirm that the endpoint (page or controller) can process the path segment. For example, a Razor Page for errors should accept the optional path segment value with the @page
directive:
@page "{code?}"
The endpoint that processes the error can get the original URL that generated the error, as shown in the following example:
var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
OriginalURL =
statusCodeReExecuteFeature.OriginalPathBase
+ statusCodeReExecuteFeature.OriginalPath
+ statusCodeReExecuteFeature.OriginalQueryString;
}
Don't mark the error handler action method with HTTP method attributes, such as HttpGet
. Explicit verbs prevent some requests from reaching the method. Allow anonymous access to the method if unauthenticated users should see the error view.
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 IStatusCodePagesFeature:
var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
statusCodePagesFeature.Enabled = false;
}
Code in exception handling pages can throw exceptions. It's often a good idea for production error pages to consist of purely static content.
Once the headers for a response are sent:
In addition to the exception handling logic in your 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 your 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 Troubleshoot ASP.NET Core on Azure App Service and IIS.
Database Error Page Middleware captures database-related exceptions that can be resolved by using Entity Framework migrations. When these exceptions occur, an HTML response with details of possible actions to resolve the issue is generated. This page should be enabled only in the Development environment. Enable the page by adding code to Startup.Configure
:
if (env.IsDevelopment())
{
app.UseDatabaseErrorPage();
}
UseDatabaseErrorPage requires the Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet package.
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 exception that occurs during the execution of a controller action or another filter. For more information, see Filters in ASP.NET Core.
Napojnica
Exception filters are useful for trapping exceptions that occur within MVC actions, but they're not as flexible as the Exception Handling Middleware. We recommend using the middleware. Use filters only where 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.
Povratne informacije za ASP.NET Core
ASP.NET Core je projekat otvorenog koda. Izaberite vezu da biste pružili povratne informacije:
Događaj
Power BI DataViz Svetsko prvenstvo
14. feb 16 - 31. mar 16
Sa 4 šanse za ulazak, možete osvojiti konferencijski paket i stići do LIVE Grand Finale u Las Vegasu
Saznajte višeObuka
Modul
Customize ASP.NET Core behavior with middleware - Training
Understand and implement middleware in an ASP.NET Core app. Use included middleware like HTTP logging and authentication. Create custom middleware to handle requests and responses.