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

栏目: IT技术 · 发布时间: 4年前

内容简介:您看此文用·秒,转发只需1秒呦~

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

在本文中,我将讲解如何通过自定义 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 中看到如下的中间件配置:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }

    // .. other middleware not shown
}

Development 环境中运行时,应用程序将捕获处理请求时引发的所有异常,并使用一个非常有用的 DeveloperExceptionMiddleware 方法将其以网页的形式进行显示:

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

开发人员例外页面

这在本地开发期间非常有用,因为它使您可以快速检查堆栈跟踪,请求标头,路由详细信息以及其他内容。

当然,这些都是您不想在生产中公开的敏感信息。因此,当不在开发阶段时,我们将使用其他异常处理程序 ExceptionHandlerMiddleware 。此中间件允许您提供一个请求路径,默认情况下是 "/Error" ,并使用它“重新执行”中间件管道,以生成最终响应:

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

使用以下命令重新执行管道

Razor Pages应用程序的最终结果是,每当生产中发生异常时,就会返回这个 Error.cshtml 的Razor 页面:

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

生产中的例外页面

这涵盖了razor 页面的异常处理,但是Web API呢?

Web API的异常处理

Web API模板( dotnet new webapi )中的默认异常处理类似于Razor Pages使用的异常处理,但有一个重要的区别:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // .. other middleware not shown
}

如您所见 DeveloperExceptionMiddleware ,在 Development 环境中仍会添加,但是在生产中根本没有添加错误处理!这没有听起来那么糟糕:即使没有异常处理中间件,ASP.NET Core也会在其底层架构中捕获该异常,将其记录下来,并向客户端返回一个空白的 500 响应:

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

一个例外

如果您正在使用该 [ApiController] 属性(你可能应该这样使用),并且该错误来自您的Web API控制器,那么 ProblemDetails 默认情况下会得到一个结果,或者您可以进一步对其进行自定义。

对于Web API客户端来说,这实际上还不错。您的API使用者应能够处理错误响应,因此最终用户将不会看到上面的“中断”页面。但是,它通常不是那么简单。

例如,也许您使用的是错误的标准格式,例如ProblemDetails格式。如果您的客户期望所有错误都具有该格式,那么在某些情况下生成的空响应很可能导致客户端中断。同样,在 Development 环境中,当客户端期望返回JSON时而你返回一个HTML开发人员异常页面,这可能会导致问题!

官方文档中描述了一种解决方案,建议您创建 ErrorController 并具有两个终结点的:

[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应用程序中使用的相同“重新执行”功能来生成响应:

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 处理所有这些情况,所以重新写你自己的版本不是一条要走的路。幸运的是,尽管通常显示的方法是为中间件提供重新执行的路径,但还有另一种选择-直接提供处理函数。

ExceptionHandlerMiddleware 中有一个 ExceptionHandlerOptions 参数。该选项对象具有两个属性:

public class ExceptionHandlerOptions
{
    public PathString ExceptionHandlingPath { get; set; }
    public RequestDelegate ExceptionHandler { get; set; }
}

当你向 UseExceptionHandler(path) 方法提供重新执行的路径时,实际上是在options对象上设置 ExceptionHandlingPath 。同样的,如果需要的话,您可以设置 ExceptionHandler 属性,并使用 UseExceptionHandler()ExceptionHandlerOptions 的实例直接传递给中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        ExceptionHandler = // .. to implement
    });

    // .. othe middleware
}

另外,您可以使用 UseExceptionHandler() 的另一个重载方法并配置一个迷你中间件管道来生成响应:

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响应中错误的机器可读详细信息的行业标准方法。这是从ASP.NET Core 3.x(在某种程度上在2.2版中)的Web API返回错误消息的普遍支持的方法。

我们将从在静态帮助器类中定义 UseCustomErrors 函数开始。该帮助类将一个生成响应的中间件添加到 IApplicationBuilder 方法扩展中。在开发环境中,它最终会调用 WriteResponse 方法,并且设置includeDetails: true 。在其他环境中, includeDetails`设置为false。

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<Task> next)
        => WriteResponse(httpContext, includeDetails: true);

    private static Task WriteProductionResponse(HttpContext httpContext, Func<Task> next)
        => WriteResponse(httpContext, includeDetails: false);

    private static async Task WriteResponse(HttpContext httpContext, bool includeDetails)
    {
        // .. to implement
    }
}

剩下的就是实现 WriteResponse 方法来生成我们的响应的功能。这将从 ExceptionHandlerMiddleware (通过 IExceptionHandlerFeature )中检索异常,并构建一个包含要显示的详细信息的 ProblemDetails 对象。然后,它使用 System.Text.Json 序列化程序将对象写入Response流。

private static async Task WriteResponse(HttpContext httpContext, bool includeDetails)
{
    // Try and retrieve the error from the ExceptionHandler middleware
    var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>();
    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 会 清除路由值,以使这些值不可用。

如果您的应用程序现在在 Development 环境中引发异常,则您将在响应中获取作为JSON返回的完整异常:

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

开发中的ProblemDetails响应

在生产环境中,您仍然会得到ProblemDetails响应,但是省略了详细信息:

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

生产中的ProblemDetails响应

与MVC /重新执行路径方法相比,此方法显然具有一些局限性,即您不容易获得模型绑定,内容协商,简单的序列化或本地化(取决于您的方法)。

如果您需要其中任何一个(例如,也许您使用PascalCase而不是camelCase从MVC进行序列化),那么使用此方法可能比其价值更麻烦。如果是这样,那么所描述的Controller方法可能是明智的选择。

如果您不关心这些,那么本文中显示的简单处理程序方法可能是更好的选择。无论哪种方式,都不要尝试实现自己的版本 ExceptionHandlerMiddleware -使用可用的扩展点!

总结

在这篇文章中,我描述了Razor Pages和Web API的默认异常处理中间件方法。我着重指出了默认Web API模板配置的问题,尤其是在客户端期望有效JSON的情况下,即使出现错误也是如此。

然后,我从官方文档中展示了建议的方法,该方法使用MVC控制器为API 生成 ProblemDetails 响应。这种方法效果很好,除非问题出在您的MVC配置本身上,否则尝试执行 ErrorController 将会失败。

作为替代方案,我展示了如何使用 ExceptionHandlerMiddleware 为生成响应提供定制的异常处理功能。我最后展示了一个示例处理程序,该处理程序将 ProblemDetails 对象序列化为JSON,包括 Development 环境中的详细信息,并在其他环境中将其排除在外。

往期 精彩 回顾

【推荐】.NET Core开发实战视频课程   ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了

给我好看

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

您看此文用

·

秒,转发只需1秒呦~

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

好看你就

点点


以上所述就是小编给大家介绍的《如何创建一个自定义的`ErrorHandlerMiddleware`方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

圈圈教你玩USB

圈圈教你玩USB

刘荣 / 2013-4 / 59.00元

通过U盘、USB鼠标、15SB键盘、USBMIDI键盘、USB转串口、自定义的USBHID设备和自定义的USB设备等几个具体的USB例子,一步步讲解USB设备及驱动程序和应用程序开发的详细过程和步骤。第9和10章介绍USBWDM驱动开发,并给出一个简单的USB驱动和USB上层过滤驱动的实例。第2版中新增4章内容,包括USB触摸屏设备、移植到AVR单片机和ARM微控制器上以及更多的USB设备的实现。......一起来看看 《圈圈教你玩USB》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具