如何创建一个自定义的`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 middlewarevar 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 typehttpContext.Response.ContentType = "application/problem+json";// Get the details to display, depending on whether we want to expose the raw exceptionvar 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 requestvar 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响应

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

生产中的ProblemDetails响应

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

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

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

总结

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql or中有空查询慢_MySQL 慢查询日志

1.定义2.相关参数2.开启3.原因4.慢查询日志工具mysqldumpslow1.定义作用:用来记录在MySQL中响应时间超过阀值的语句。2.相关参数mysql> show variables like %slow_query%;------------------------------------------------------------| Variable_name | Value …

从业务需求抽象成模型解决方案

从业务需求调研&#xff0c;通过抽象转换成模型技术方案&#xff0c;本文将对这个过程做个拆解&#xff0c;供大家参考。以下我所说的可能都是错的&#xff0c;只是一家之见&#xff0c;欢迎大家在留言区多提意见和看法&#xff0c;互相共勉。一、订单对象-信息需求公司的运营都…

《C++ Primer》7.1.4节练习

练习7.11: #include <iostream> #include <cstring> using namespace std;class Sales_data {public:Sales_data() default;Sales_data(const std::string &book): bookNo(book) {}Sales_data(const std::string &book, const unsigned num, const doubl…

DotNetCore Web应用程序中的Session管理

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。限于译者的能力有限&#xff0c;个别语句翻译略显生硬&#xff0c;还请见谅。作者简介&#xff1a;Jon&#xff08;Jonathan&#x…

css3是什么 ptml_CSS3

CSS3HTMLCSSJavaScript结构表项交互如何学习&#xff1f;CSS是什么CSS怎么用(快速入门)CSS选择器(重点难点)美化网页(文字、阴影、超链接、列表、渐变...)盒子模型浮动定位网页动画(特效效果)1、初识CSS1.1、什么是CSSCascading Style Sheet(层叠样式表)CSS&#xff1a;表现(美…

python docx runs_别再问我Python怎么操作Word了!

安装docx是一个非标准库&#xff0c;需要在命令行(终端)中使用pip即可安装pip install python-docx一定要注意&#xff0c;安装的时候是python-docx而实际调用时均为docx!前置知识Word中一般可以结构化成三个部分&#xff1a;文档Document段落Paragraph文字块Run也就是Document…

【要闻】如何基于K8s管理1600个微服务?某数字化银行秘诀公开

Cloud Foundry Foundation宣布KubeCF为新孵化项目Cloud Foundry Foundation是开放源代码项目的聚集地&#xff0c;简化了开发人员的体验&#xff0c;近日其宣布&#xff0c;KubeCF已成为该基金会的孵化项目&#xff0c;并已发布版本1.0.1。KubeCF是Cloud Foundry应用程序运行时…

如何用 Blazor 实现 Ant Design 组件库?

本文主要分享我创建 Ant Design of Blazor 项目的心路历程&#xff0c;已经文末有一个 Blazor 线上分享预告。Blazor WebAssembly 来了&#xff01;Blazor 这个新推出的前端 Web 框架&#xff0c;想必是去年 .NET Core 3.0 发布时才进入 .NET 开发者的视线的。但其实&#xff0…

.NET Core开发实战(第28课:工作单元模式(UnitOfWork):管理好你的事务)--学习笔记...

28 | 工作单元模式&#xff08;UnitOfWork&#xff09;&#xff1a;管理好你的事务工作单元模式有如下几个特性&#xff1a;1、使用同一上下文2、跟踪实体的状态3、保障事务一致性我们对实体的操作&#xff0c;最终的状态都是应该如实保存到我们的存储中&#xff0c;进行持久化…

《C++ Primer》7.3.2节练习

练习7.27: #include <iostream> #include <cstring> using namespace std;class Screen {private:unsigned height 0, width 0;unsigned cursor 0;string contents;public:Screen() default;Screen(unsigned ht, unsigned wd): height(ht), width(wd), conten…

【实战 Ids4】║ 控制台密码模式搭配Ocelot网关

&#xff08;此岁只能云赏樱了&#xff09;书接上文&#xff0c;这些天一直在研究IdentityServer4&#xff08;下文简称Ids4&#xff09;框架&#xff0c;发现有很多有意思&#xff0c;或者说比我想象中的知识点&#xff0c;可扩展的多&#xff0c;所以比较开心能钻研进去&…

《C++ Primer》7.3.3节练习

练习7.31: 满足题意的程序如下所示&#xff1a; class X;//声明类型X class Y//定义类型Y {X* x; }; class X//定义类型X {Y y; };类X的声明称为前向声明&#xff0c;它向程序中引入了名字X并且指明X是一种类类型。对于类型X来说&#xff0c;此时我们已知它是一个类类型&#…

.NET Core 如何生成信用卡卡号

点击上方蓝字关注“汪宇杰博客”导语上个月我写了《.NET Core 如何验证信用卡卡号》&#xff0c;不少朋友表示挺有兴趣。在金融科技行业的实际工作中&#xff0c;通常还需要生成信用卡卡号用来测试&#xff0c;今天我就来教大家如何生成信用卡卡号。上回的改进上篇文章写完后&a…

python怎么爬虎牙_使用python爬虫框架scrapy抓取虎牙主播数据

前言本文利用python的scrapy框架对虎牙web端的主播、主播订阅数、主播当前观看人数等基本数据进行抓取&#xff0c;并将抓取到的数据以csv格数输出&#xff0c;以及存储到mongodb中思路观察虎牙网站后确认所有频道url都在www.huya.com/g中的&#xff0c;而主播房间数据则是ajax…

《C++ Primer》7.3.4节练习

练习7.32: 要想让clear函数作为Screen的友元&#xff0c;只需要在Screen类中做出友元声明即可。本题的真正关键之处是程序的组织结构&#xff0c;我们必须首先定义Window_mgr类&#xff0c;其中声明clear函数&#xff0c;但是不能定义它&#xff1b;接下来定义Screen类&#xf…

.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记

29 | 定义仓储&#xff1a;使用EF Core实现仓储层首先定义仓储层的接口&#xff0c;以及仓储层实现的基类&#xff0c;抽象类仓储层的接口namespace GeekTime.Infrastructure.Core {/// <summary>/// 包含普通实体的仓储/// 约束 TEntity 必须是继承 Entity 的基类&#…

ueditor单图上传iframe跨域_UEditor单图上传(simpleupload)跨域问题解决方案

代码实现首先我们需要在ueditor.all.js文件中找到原本的单图上传部分的代码搜索关键字 simpleupload&#xff0c;如下图所示&#xff1a;然后找到上传图片的代码片段&#xff0c;如下图所示&#xff1a;然后把 domUtils.on的 input 绑定的事件注释掉或删除掉替换成以下代码:inp…

StringBuilder内存碎片对性能的影响

TL;DR:StringBuilder内部是由多段 char[]组成的半自动链表&#xff0c;因此频繁从中间修改 StringBuilder&#xff0c;会将原本连续的内存分隔为多段&#xff0c;从而影响读取/遍历性能。连续内存与不连续内存的性能差&#xff0c;可能高达 1600倍。背景用 StringBuilder的用户…

java 双击_利用java开发一个双击执行的小程序

之前我们利用java写了很多东西&#xff0c;但是好像都没有什么实际意义。因为有意义桌面小程序怎么都得有个界面&#xff0c;可是界面又不太好搞。或者 了解到这一层的人就少之又少了。呀&#xff0c;是不是还得开辟一些版面来介绍awt和 swing。。。算了 先把这个 双击执行的小…

开发人员如何学习 Kubernetes

虽然“容器编排平台”还没有被整个行业大范围采用&#xff0c;但在这一领域 Kubernetes 已经战胜其他选手&#xff0c;成为了事实标准。近两年的 Web 开发技术社区&#xff0c;随便打开一两个群&#xff0c;你都能看到人们在谈 Kubernetes。很多开发人员&#xff0c;包括曾经的…