如何创建一个自定义的`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
方法将其以网页的形式进行显示:
这在本地开发期间非常有用,因为它使您可以快速检查堆栈跟踪,请求标头,路由详细信息以及其他内容。
当然,这些都是您不想在生产中公开的敏感信息。因此,当不在开发阶段时,我们将使用其他异常处理程序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`方法的更多相关文章
- 如何创建一个自定义jQuery插件
简介 jQuery 库是专为加快 JavaScript 开发速度而设计的.通过简化编写 JavaScript 的方式,减少代码量.使用 jQuery 库时,您可能会发现您经常为一些常用函数重写相同的代 ...
- springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑. 1.1 异常处理思路 系统中异常包括两类:预期异常和运行时异常RuntimeEx ...
- 怎么在java中创建一个自定义的collector
目录 简介 Collector介绍 自定义Collector 总结 怎么在java中创建一个自定义的collector 简介 在之前的java collectors文章里面,我们讲到了stream的c ...
- 使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)
译文来源 欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScrip ...
- 【Unity Shaders】Diffuse Shading——创建一个自定义的diffuse lighting model(漫反射光照模型)
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 创建一个自定义的Application类
由于每个应用程序必须创建一个Application对象,vs为开发人员提供了模板来减轻开发人员的重复工作.当使用vs创建一个WPF应用程序是,vs会自动创建一个app.xaml文件, <Appl ...
- [原创]java WEB学习笔记40:简单标签概述(背景,使用一个标签,标签库的API,SimpleTag接口,创建一个自定义的标签的步骤 和简单实践)
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- 创建一个自定义名称的Ceph集群
前言 这里有个条件,系统环境是Centos 7 ,Ceph 的版本为Jewel版本,因为这个组合下是由systemctl来进行服务控制的,所以需要做稍微的改动即可实现 准备工作 部署mon的时候需要修 ...
- OC动态创建的问题变量数组.有数组,在阵列13要素,第一个数据包阵列,每3元素为一组,分成若干组,这些数据包的统一管理。最后,一个数组.(要动态地创建一个数组).两种方法
<span style="font-size:24px;">//////第一种方法 // NSMutableArray *arr = [NSMutable ...
随机推荐
- 调参、最优化、ml算法(未完成)
最优化方法 调参方法 ml算法 梯度下降gd grid search lr 梯度上升 随机梯度下降 pca 随机梯度下降sgd 贝叶斯调参 lda 牛顿算法 knn 拟牛顿算法 kmeans ...
- python库之-------Pandas
包括两个数据结构:DataFrame和Series 官方文档地址: pandas https://pandas.pydata.org/pandas-docs/stable/index.html ser ...
- OpenCV 实现自己的线性滤波器
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #i ...
- PHP--foreach的问题
<?php echo "<pre>"; $data = ['a', 'b', 'c']; foreach($data as $key => $val){ $ ...
- java异常分析;剖析printStackTrace和fillInStackTrace
Java异常的栈轨迹(Stack Trace) 捕获到异常时,往往需要进行一些处理.比较简单直接的方式就是打印异常栈轨迹Stack Trace.说起栈轨迹,可能很多人和我一样,第一反应就是printS ...
- 测试误区《二》 python逻辑运算和关系运算优先级
关系运算 关系运算就是对2个对象进行比较,通过比较符判断进行比较,有6种方式. x > y 大于 x >= y 大于等于 x < y 小于 x <= y 小于等于 x = y ...
- IT男频繁猝死背后的心理探秘
"深圳36岁IT男猝死酒店马桶上"这条新闻再次成为人们眼球的焦点,每每发生这样的事情,难免让人扼腕唏嘘,他们本该是风华正茂的年纪,家有老母贤妻爱子,甚至房子车子票子都不缺,该是一边 ...
- from PIL import image报错
python中import PIL可以,但是from PIL import Image就报错? ’‘ 大家在安装pillow的时候,可能会安装成功,但是当运行from pIL import image ...
- Leetcode 946. Validate Stack Sequences 验证栈序列
946. Validate Stack Sequences 题目描述 Given two sequences pushed and popped with distinct values, retur ...
- 18岁,赚到了人生中的第一个10W!
大家好,我是九歌 今年我18岁,赚到了我人生中的第一个10W 截至2019年10月14日,我已经做了43天的公众号啦,粉丝也悄然增长到了1W8,感谢各位读者朋友给我的支持和鼓励. 相信大部分读者都是从 ...