如何创建一个自定义的`ErrorHandlerMiddleware`方法

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

如何创建一个自定义的`ErrorHandlerMiddleware`方法

依乐祝   2020-03-16 我要评论
在本文中,我将讲解如何通过自定义`ExceptionHandlerMiddleware`,以便在中间件管道中发生错误时创建自定义响应,而不是提供一个“重新执行”管道的路径。 > 作者:依乐祝 > 译文:https://www.cnblogs.com/yilezhu/p/12497937.html > 原文:https://andrewlock.net/creating-a-custom-error-handler-middleware-function/ ## Razor页面中的异常处理 所有的.NET应用程序都有可能会产生错误,并且不幸地引发异常,因此在ASP.NET中间件管道中处理这些异常显得非常重要。服务器端呈现的应用程序(如Razor Pages)通常希望捕获这些异常并重定向到一个错误页面。 例如,如果您创建一个使用Razor Pages(`dotnet new webapp`)的新Web应用程序,您将在`Startup.Configure`中看到如下的中间件配置: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } // .. other middleware not shown } ``` 在`Development`环境中运行时,应用程序将捕获处理请求时引发的所有异常,并使用一个非常有用的`DeveloperExceptionMiddleware`方法将其以网页的形式进行显示: ![开发人员例外页面](https://img2018.cnblogs.com/blog/1377250/202003/1377250-20200315154017012-868453515.png) 这在本地开发期间非常有用,因为它使您可以快速检查堆栈跟踪,请求标头,路由详细信息以及其他内容。 当然,这些都是您不想在生产中公开的敏感信息。因此,当不在开发阶段时,我们将使用其他异常处理程序`ExceptionHandlerMiddleware`。此中间件允许您提供一个请求路径,默认情况下是`"/Error"`,并使用它“重新执行”中间件管道,以生成最终响应: ![使用以下命令重新执行管道 ](https://img2018.cnblogs.com/blog/1377250/202003/1377250-20200315154016727-1816582208.png) Razor Pages应用程序的最终结果是,每当生产中发生异常时,就会返回这个*Error.cshtml* 的Razor 页面: ![生产中的例外页面](https://img2018.cnblogs.com/blog/1377250/202003/1377250-20200315154016480-1304527591.png) 这涵盖了razor 页面的异常处理,但是Web API呢? ## Web API的异常处理 Web API模板(`dotnet new webapi`)中的默认异常处理类似于Razor Pages使用的异常处理,但有一个重要的区别: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // .. other middleware not shown } ``` 如您所见`DeveloperExceptionMiddleware`,在`Development`环境中仍会添加,但是在生产中根本没有添加错误处理!这没有听起来那么糟糕:即使没有异常处理中间件,ASP.NET Core也会在其底层架构中捕获该异常,将其记录下来,并向客户端返回一个空白的`500`响应: ![一个例外](https://img2018.cnblogs.com/blog/1377250/202003/1377250-20200315154016245-827862740.png) 如果您正在使用该`[ApiController]`属性(你可能应该这样使用),并且该错误来自您的Web API控制器,那么`ProblemDetails`默认情况下会得到一个结果,或者您可以进一步对其进行自定义。 对于Web API客户端来说,这实际上还不错。您的API使用者应能够处理错误响应,因此最终用户将不会看到上面的“中断”页面。但是,它通常不是那么简单。 例如,也许您使用的是错误的标准格式,例如[ProblemDetails](https:/https://img.qb5200.com/download-x/docs.microsoft.com/en-ushttps://img.qb5200.com/download-x/dotnet/api/microsoft.aspnetcore.mvc.problemdetails)格式。如果您的客户期望所有错误都具有该格式,那么在某些情况下生成的空响应很可能导致客户端中断。同样,在`Development`环境中,当客户端期望返回JSON时而你返回一个HTML开发人员异常页面,这可能会导致问题! [官方文档中](https:/https://img.qb5200.com/download-x/docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-3.1#exception-handler)描述了一种解决方案,建议您创建`ErrorController`并具有两个终结点的: ```csharp [ApiController] public class ErrorController : ControllerBase { [Route("/error-local-development")] public IActionResult ErrorLocalDevelopment() => Problem(); // Add extra details here [Route("/error")] public IActionResult Error() => Problem(); } ``` 然后使用Razor Pages应用程序中使用的相同“重新执行”功能来生成响应: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseExceptionHandler("/error-local-development"); } else { app.UseExceptionHandler("/error"); } // .. other middleware } ``` 这可以正常工作,但是对于使用生成异常的同一基础结构(例如Razor Pages或MVC)来生成异常消息,总有一些困扰我。由于被第二次抛出异常,我多次被失败的*错误*响应所困扰!因此,我喜欢采取稍微不同的方法。 ## 使用ExceptionHandler代替ExceptionHandlingPath 当我第一次开始使用ASP.NET Core时,解决此问题的方法是编写自己的自定义ExceptionHandler中间件来直接生成响应。“处理异常不是那么难,对吧”? 事实证明,这要复杂得多(我知道,令人震惊)。您需要处理各种边缘情况,例如: - 如果在发生异常时响应已经开始发送,则您将无法拦截它。 - 如果在`EndpointMiddleware`发生异常时已执行,则需要对选定的端点进行一些处理 - 您不想缓存错误响应 [`ExceptionHandlerMiddleware`处理所有这些情况](https://github.comhttps://img.qb5200.com/download-x/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs),所以重新写你自己的版本不是一条要走的路。幸运的是,尽管通常显示的方法是为中间件提供重新执行的路径,但还有另一种选择-直接提供处理函数。 在`ExceptionHandlerMiddleware`中有一个`ExceptionHandlerOptions`参数。[该选项对象具有两个属性](https://github.comhttps://img.qb5200.com/download-x/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs): ```csharp public class ExceptionHandlerOptions { public PathString ExceptionHandlingPath { get; set; } public RequestDelegate ExceptionHandler { get; set; } } ``` 当你向`UseExceptionHandler(path)`方法提供重新执行的路径时,实际上是在options对象上设置`ExceptionHandlingPath`。同样的,如果需要的话,您可以设置`ExceptionHandler`属性,并使用`UseExceptionHandler()`将`ExceptionHandlerOptions`的实例直接传递给中间件: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = // .. to implement }); // .. othe middleware } ``` 另外,您可以使用`UseExceptionHandler()`的另一个重载方法并配置一个迷你中间件管道来生成响应: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseExceptionHandler(err => err.UseCustomErrors(env)); // .. to implement // .. othe middleware } ``` 两种方法都是等效的,因此更多是关于喜好的问题。在本文中,我将使用第二种方法并实现该`UseCustomErrors()`功能。 ## 创建自定义异常处理函数 对于此示例,我将假设我们在中间件管道中遇到异常时需要生成一个`ProblemDetails`的对象。我还要假设我们的API仅支持JSON。这就避免了我们不必担心XML内容协商等问题。在开发环境中,`ProblemDetails`响应将包含完整的异常堆栈跟踪,而在生产环境中,它将仅显示一般错误消息。 > `ProblemDetails`是返回HTTP响应中错误的机器可读详细信息[的行业标准](https://tools.ietf.org/html/rfc7807)方法。这是从ASP.NET Core 3.x(在某种程度上在2.2版中)的Web API返回错误消息的普遍支持的方法。 我们将从在静态帮助器类中定义`UseCustomErrors`函数开始。该帮助类将一个生成响应的中间件添加到`IApplicationBuilder`方法扩展中。在开发环境中,它最终会调用`WriteResponse`方法,并且设置includeDetails: true`。在其他环境中,`includeDetails`设置为false。 ```csharp using System; using System.Diagnostics; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; public static class CustomErrorHandlerHelper { public static void UseCustomErrors(this IApplicationBuilder app, IHostEnvironment environment) { if (environment.IsDevelopment()) { app.Use(WriteDevelopmentResponse); } else { app.Use(WriteProductionResponse); } } private static Task WriteDevelopmentResponse(HttpContext httpContext, Func next) => WriteResponse(httpContext, includeDetails: true); private static Task WriteProductionResponse(HttpContext httpContext, Func next) => WriteResponse(httpContext, includeDetails: false); private static async Task WriteResponse(HttpContext httpContext, bool includeDetails) { // .. to implement } } ``` 剩下的就是实现`WriteResponse`方法来生成我们的响应的功能。这将从`ExceptionHandlerMiddleware`(通过`IExceptionHandlerFeature`)中检索异常,并构建一个包含要显示的详细信息的`ProblemDetails`对象。然后,它使用`System.Text.Json`序列化程序将对象写入Response流。 ```csharp private static async Task WriteResponse(HttpContext httpContext, bool includeDetails) { // Try and retrieve the error from the ExceptionHandler middleware var exceptionDetails = httpContext.Features.Get(); var ex = exceptionDetails?.Error; // Should always exist, but best to be safe! if (ex != null) { // ProblemDetails has it's own content type httpContext.Response.ContentType = "application/problem+json"; // Get the details to display, depending on whether we want to expose the raw exception var title = includeDetails ? "An error occured: " + ex.Message : "An error occured"; var details = includeDetails ? ex.ToString() : null; var problem = new ProblemDetails { Status = 500, Title = title, Detail = details }; // This is often very handy information for tracing the specific request var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier; if (traceId != null) { problem.Extensions["traceId"] = traceId; } //Serialize the problem details object to the Response as JSON (using System.Text.Json) var stream = httpContext.Response.Body; await JsonSerializer.SerializeAsync(stream, problem); } } ``` 您可以在序列化`ProblemDetails`之前记录从`HttpContext`中检索的自己喜欢的任何其他值。 > 请注意,在调用异常处理程序方法之前,`ExceptionHandlerMiddleware`会 [清除路由值](https://github.comhttps://img.qb5200.com/download-x/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs#L107),以使这些值不可用。 如果您的应用程序现在在`Development`环境中引发异常,则您将在响应中获取作为JSON返回的完整异常: ![开发中的ProblemDetails响应](https://img2018.cnblogs.com/blog/1377250/202003/1377250-20200315154015954-1557303213.png) 在生产环境中,您仍然会得到ProblemDetails响应,但是省略了详细信息: ![生产中的ProblemDetails响应](https://img2018.cnblogs.com/blog/1377250/202003/1377250-20200315154015122-1465272789.png) 与MVC /重新执行路径方法相比,此方法显然具有一些局限性,即您不容易获得模型绑定,内容协商,简单的序列化或本地化(取决于您的方法)。 如果您需要其中任何一个(例如,也许您使用PascalCase而不是camelCase从MVC进行序列化),那么使用此方法可能比其价值更麻烦。如果是这样,那么[所描述的Controller方法](https:/https://img.qb5200.com/download-x/docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-3.1#exception-handler)可能是明智的选择。 如果您不关心这些,那么本文中显示的简单处理程序方法可能是更好的选择。无论哪种方式,都不要尝试实现自己的版本`ExceptionHandlerMiddleware`-使用可用的扩展点!

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们