From 09c342062948aced0ac3f39e23f0623b96996c0b Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Sat, 2 Mar 2019 15:32:51 -0600 Subject: [PATCH] Handling errors update --- aspnetcore/fundamentals/error-handling.md | 40 ++++++++++-- .../2.x/ErrorHandlingSample/Startup.cs | 64 +++++++++++++++++-- aspnetcore/fundamentals/middleware/index.md | 2 +- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/aspnetcore/fundamentals/error-handling.md b/aspnetcore/fundamentals/error-handling.md index 13455b6519eb..8d530cdddd66 100644 --- a/aspnetcore/fundamentals/error-handling.md +++ b/aspnetcore/fundamentals/error-handling.md @@ -5,7 +5,7 @@ description: Discover how to handle errors in ASP.NET Core apps. monikerRange: '>= aspnetcore-2.1' ms.author: tdykstra ms.custom: mvc -ms.date: 03/01/2019 +ms.date: 03/02/2019 uid: fundamentals/error-handling --- # Handle errors in ASP.NET Core @@ -18,9 +18,9 @@ This article covers common approaches to handling errors in ASP.NET Core apps. ## Developer Exception Page -To configure an app to display a page that shows detailed information about exceptions, use the *Developer Exception Page*. The page is made available by the [Microsoft.AspNetCore.Diagnostics](https://www.nuget.org/packages/Microsoft.AspNetCore.Diagnostics/) package, which is available in the [Microsoft.AspNetCore.App metapackage](xref:fundamentals/metapackage-app). Add a line to the `Startup.Configure` method: +To configure an app to display a page that shows detailed information about exceptions, use the *Developer Exception Page*. The page is made available by the [Microsoft.AspNetCore.Diagnostics](https://www.nuget.org/packages/Microsoft.AspNetCore.Diagnostics/) package, which is available in the [Microsoft.AspNetCore.App metapackage](xref:fundamentals/metapackage-app). Add a line to the `Startup.Configure` method when the app is running in the Development [environment](xref:fundamentals/environments): -[!code-csharp[](error-handling/samples/2.x/ErrorHandlingSample/Startup.cs?name=snippet_DevExceptionPage&highlight=5)] +[!code-csharp[](error-handling/samples/2.x/ErrorHandlingSample/Startup.cs?name=snippet_UseDeveloperExceptionPage)] Place the call to in front of any middleware where you want to catch exceptions. @@ -42,9 +42,9 @@ When the app isn't running in the Development environment, call the adds the Exception Handling Middleware in non-Development environments. The extension method specifies an error page or controller at the `/Error` endpoint for re-executed requests after exceptions are caught and logged: +In the following example from the sample app, adds the Exception Handling Middleware in non-Development environments. The extension method specifies an error page or controller at the `/Error` endpoint for re-executed requests after exceptions are caught and logged in `Startup.Configure`: -[!code-csharp[](error-handling/samples/2.x/ErrorHandlingSample/Startup.cs?name=snippet_DevExceptionPage&highlight=9)] +[!code-csharp[](error-handling/samples/2.x/ErrorHandlingSample/Startup.cs?name=snippet_UseExceptionHandler1)] The Razor Pages app template provides an Error page (*.cshtml*) and class (`ErrorModel`) in the Pages folder. @@ -61,6 +61,34 @@ public IActionResult Error() Don't decorate 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 so that unauthenticated users are able to receive the error view. +## Access the exception + +If you need to access the exception in a controller or page, obtain from [HttpContext.Features](xref:Microsoft.AspNetCore.Http.HttpContext.Features) and read the ([IExceptionHandlerFeature.Error](xref:Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature.Error)): + +```csharp +// using Microsoft.AspNetCore.Diagnostics; + +var exceptionHandlerFeature = + HttpContext.Features.Get(); +var error = exceptionHandlerFeature?.Error; +``` + +> [!WARNING] +> Do **not** serve sensitive error information from to clients. Serving sensitive information from errors can compromise the app's security. + +## Configure custom exception handling code + +An alternative to serving an endpoint for errors with a [custom exception handling page](#configure-a-custom-exception-handling-page) is to provide a lambda to . This approach permits the app to work directly with the error before returning a response. + +The sample app demonstrates the approach in `Startup.Configure`. Set the preprocessor directive at the top of the `Startup.cs` file to `LambdaErrorHandler` and run the app in the Production [environment](xref:fundamentals/environments). After the app starts, trigger an exception with the **Throw Exception** link on the Index page. The following lambda runs: + +[!code-csharp[](error-handling/samples/2.x/ErrorHandlingSample/Startup.cs?name=snippet_UseExceptionHandler2)] + +> [!WARNING] +> Do **not** serve sensitive error information from to clients. Serving sensitive information from errors can compromise the app's security. **The sample app serves sensitive information from [IExceptionHandlerFeature.Error](xref:Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature.Error) [Exception.Message](xref:System.Exception.Message) for demonstration purposes only!** + +For more information on using preprocessor directives with documentation sample apps, see . + ## Configure status code pages 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. @@ -279,7 +307,7 @@ When running on [IIS](/iis) or [IIS Express](/iis/extensions/introduction-to-iis ### Exception filters -Exception filters can be configured globally or on a per-controller or per-action basis in an MVC app. These filters handle any unhandled exception that occurs during the execution of a controller action or another filter. These filters aren't called otherwise. To learn more, see . +Exception filters can be configured globally or on a per-controller or per-action basis in an MVC app. These filters handle any unhandled exception that occurs during the execution of a controller action or another filter. These filters aren't called otherwise. For more information, see . > [!TIP] > Exception filters are useful for trapping exceptions that occur within MVC actions, but they're not as flexible as error handling middleware. We recommend the use of middleware. Use filters only where you need to perform error handling *differently* based on which MVC action is chosen. diff --git a/aspnetcore/fundamentals/error-handling/samples/2.x/ErrorHandlingSample/Startup.cs b/aspnetcore/fundamentals/error-handling/samples/2.x/ErrorHandlingSample/Startup.cs index f4bb6eb3e6ee..a6cdbb1f7f07 100644 --- a/aspnetcore/fundamentals/error-handling/samples/2.x/ErrorHandlingSample/Startup.cs +++ b/aspnetcore/fundamentals/error-handling/samples/2.x/ErrorHandlingSample/Startup.cs @@ -1,10 +1,12 @@ -#define StatusCodePages // or StatusCodePagesWithRedirect +#define StatusCodePages // or StatusCodePagesWithRedirect +#define PageErrorHandler // or LambdaErrorHandler using System; using System.Net; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -17,18 +19,55 @@ public void ConfigureServices(IServiceCollection services) { } - #region snippet_DevExceptionPage public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { + #region snippet_UseDeveloperExceptionPage app.UseDeveloperExceptionPage(); + #endregion } else { +#if ErrorPageHandler + #region snippet_UseExceptionHandler1 app.UseExceptionHandler("/Error"); + #endregion +#endif +#if LambdaErrorHandler + #region snippet_UseExceptionHandler2 + // using Microsoft.AspNetCore.Diagnostics; + + app.UseExceptionHandler(errorApp => + { + errorApp.Run(async context => + { + context.Response.StatusCode = 500; + context.Response.ContentType = "text/html"; + + await context.Response.WriteAsync("\r\n"); + await context.Response.WriteAsync("ERROR!

\r\n"); + + var error = context.Features.Get(); + + if (error != null) + { + // FOR DEMONSTRATION PURPOSES ONLY! + // Do NOT expose an error message to the client + // with the following code. + await context.Response.WriteAsync("Error: " + + HtmlEncoder.Default.Encode(error.Error.Message) + + "

\r\n"); + } + + await context.Response.WriteAsync("Home
\r\n"); + await context.Response.WriteAsync("\r\n"); + await context.Response.WriteAsync(new string(' ', 512)); // IE padding + }); + }); + #endregion +#endif } - #endregion #if StatusCodePages #region snippet_StatusCodePages @@ -58,23 +97,31 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) error.Run(async context => { var builder = new StringBuilder(); - builder.AppendLine(""); - builder.AppendLine("An error occurred.
"); + + builder.AppendLine(""); + builder.AppendLine("An error occurred.
"); + var path = context.Request.Path.ToString(); + if (path.Length > 1) { builder.AppendLine("Status Code: " + - HtmlEncoder.Default.Encode(path.Substring(1)) + "
"); + HtmlEncoder.Default.Encode(path.Substring(1)) + "
"); } + var referrer = context.Request.Headers["referer"]; + if (!string.IsNullOrEmpty(referrer)) { builder.AppendLine("Return to " + - WebUtility.HtmlEncode(referrer) + "
"); + WebUtility.HtmlEncode(referrer) + "
"); } + builder.AppendLine(""); + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(builder.ToString()); }); }); @@ -85,7 +132,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { throw new Exception("Exception triggered!"); } + var builder = new StringBuilder(); + builder.AppendLine("Hello World!"); builder.AppendLine("
    "); builder.AppendLine("
  • Throw Exception
  • "); @@ -94,6 +143,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) builder.AppendLine(""); context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(builder.ToString()); }); } diff --git a/aspnetcore/fundamentals/middleware/index.md b/aspnetcore/fundamentals/middleware/index.md index 9a86e08b9650..b4259723ae9a 100644 --- a/aspnetcore/fundamentals/middleware/index.md +++ b/aspnetcore/fundamentals/middleware/index.md @@ -231,7 +231,7 @@ ASP.NET Core ships with the following middleware components. The *Order* column | [Authentication](xref:security/authentication/identity) | Provides authentication support. | Before `HttpContext.User` is needed. Terminal for OAuth callbacks. | | [Cookie Policy](xref:security/gdpr) | Tracks consent from users for storing personal information and enforces minimum standards for cookie fields, such as `secure` and `SameSite`. | Before middleware that issues cookies. Examples: Authentication, Session, MVC (TempData). | | [CORS](xref:security/cors) | Configures Cross-Origin Resource Sharing. | Before components that use CORS. | -| [Diagnostics](xref:fundamentals/error-handling) | Configures diagnostics. | Before components that generate errors. | +| [Exception Handling](xref:fundamentals/error-handling) | Handles exceptions. | Before components that generate errors. | | [Forwarded Headers](/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersextensions) | Forwards proxied headers onto the current request. | Before components that consume the updated fields. Examples: scheme, host, client IP, method. | | [Health Check](xref:host-and-deploy/health-checks) | Checks the health of an ASP.NET Core app and its dependencies, such as checking database availability. | Terminal if a request matches a health check endpoint. | | [HTTP Method Override](/dotnet/api/microsoft.aspnetcore.builder.httpmethodoverrideextensions) | Allows an incoming POST request to override the method. | Before components that consume the updated method. |