在本文中,我将讲解如何通过自定义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方法将其以网页的形式进行显示:

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

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

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

这涵盖了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响应:

如果您正在使用该[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返回的完整异常:

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

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

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

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

如何创建一个自定义的`ErrorHandlerMiddleware`方法的更多相关文章

  1. 如何创建一个自定义jQuery插件

    简介 jQuery 库是专为加快 JavaScript 开发速度而设计的.通过简化编写 JavaScript 的方式,减少代码量.使用 jQuery 库时,您可能会发现您经常为一些常用函数重写相同的代 ...

  2. springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。

    springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑. 1.1 异常处理思路 系统中异常包括两类:预期异常和运行时异常RuntimeEx ...

  3. 怎么在java中创建一个自定义的collector

    目录 简介 Collector介绍 自定义Collector 总结 怎么在java中创建一个自定义的collector 简介 在之前的java collectors文章里面,我们讲到了stream的c ...

  4. 使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)

    译文来源 欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScrip ...

  5. 【Unity Shaders】Diffuse Shading——创建一个自定义的diffuse lighting model(漫反射光照模型)

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  6. 创建一个自定义的Application类

    由于每个应用程序必须创建一个Application对象,vs为开发人员提供了模板来减轻开发人员的重复工作.当使用vs创建一个WPF应用程序是,vs会自动创建一个app.xaml文件, <Appl ...

  7. [原创]java WEB学习笔记40:简单标签概述(背景,使用一个标签,标签库的API,SimpleTag接口,创建一个自定义的标签的步骤 和简单实践)

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  8. 创建一个自定义名称的Ceph集群

    前言 这里有个条件,系统环境是Centos 7 ,Ceph 的版本为Jewel版本,因为这个组合下是由systemctl来进行服务控制的,所以需要做稍微的改动即可实现 准备工作 部署mon的时候需要修 ...

  9. OC动态创建的问题变量数组.有数组,在阵列13要素,第一个数据包阵列,每3元素为一组,分成若干组,这些数据包的统一管理。最后,一个数组.(要动态地创建一个数组).两种方法

    <span style="font-size:24px;">//////第一种方法 //        NSMutableArray *arr = [NSMutable ...

随机推荐

  1. 调参、最优化、ml算法(未完成)

    最优化方法 调参方法 ml算法 梯度下降gd grid search lr 梯度上升 随机梯度下降 pca 随机梯度下降sgd  贝叶斯调参 lda 牛顿算法   knn 拟牛顿算法   kmeans ...

  2. python库之-------Pandas

    包括两个数据结构:DataFrame和Series 官方文档地址: pandas https://pandas.pydata.org/pandas-docs/stable/index.html ser ...

  3. OpenCV 实现自己的线性滤波器

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #i ...

  4. PHP--foreach的问题

    <?php echo "<pre>"; $data = ['a', 'b', 'c']; foreach($data as $key => $val){ $ ...

  5. java异常分析;剖析printStackTrace和fillInStackTrace

    Java异常的栈轨迹(Stack Trace) 捕获到异常时,往往需要进行一些处理.比较简单直接的方式就是打印异常栈轨迹Stack Trace.说起栈轨迹,可能很多人和我一样,第一反应就是printS ...

  6. 测试误区《二》 python逻辑运算和关系运算优先级

    关系运算 关系运算就是对2个对象进行比较,通过比较符判断进行比较,有6种方式. x > y 大于 x >= y 大于等于 x < y 小于 x <= y 小于等于 x = y ...

  7. IT男频繁猝死背后的心理探秘

    "深圳36岁IT男猝死酒店马桶上"这条新闻再次成为人们眼球的焦点,每每发生这样的事情,难免让人扼腕唏嘘,他们本该是风华正茂的年纪,家有老母贤妻爱子,甚至房子车子票子都不缺,该是一边 ...

  8. from PIL import image报错

    python中import PIL可以,但是from PIL import Image就报错? ’‘ 大家在安装pillow的时候,可能会安装成功,但是当运行from pIL import image ...

  9. Leetcode 946. Validate Stack Sequences 验证栈序列

    946. Validate Stack Sequences 题目描述 Given two sequences pushed and popped with distinct values, retur ...

  10. 18岁,赚到了人生中的第一个10W!

    大家好,我是九歌 今年我18岁,赚到了我人生中的第一个10W 截至2019年10月14日,我已经做了43天的公众号啦,粉丝也悄然增长到了1W8,感谢各位读者朋友给我的支持和鼓励. 相信大部分读者都是从 ...