ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”

本文涉及的产品
云原生网关 MSE Higress,422元/月
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
MSE Nacos/ZooKeeper 企业版试用,1600元额度,限量50份
简介:

在《ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式》中,我们通过几个简单的实例演示了如何呈现一个错误页面,这些错误页面的呈现分别由三个对应的中间件来完成,接下来我们将对这三个中间件进行详细介绍。在开发环境呈现的异常页面是通过一个类型为DeveloperExceptionPageMiddleware中间件实现的。[本文已经同步到《ASP.NET Core框架揭秘》之中]

   1: public class DeveloperExceptionPageMiddleware
   2: {
   3:     public DeveloperExceptionPageMiddleware(RequestDelegate next, IOptions<DeveloperExceptionPageOptions> options, 
   4:         ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, DiagnosticSource diagnosticSource);
   5:     public Task Invoke(HttpContext context);
   6: }

如上面的代码片段所示,当我们创建一个DeveloperExceptionPageMiddleware对象的时候需要以参数的形式提供一个IOptions<DeveloperExceptionPageOptions>对象,而DeveloperExceptionPageOptions对象携带我们为这个中间件指定的配置选项,具体的配置选项体现在如下另个属性(FileProvider和SourceCodeLineCount)。

   1: public class DeveloperExceptionPageOptions
   2: {
   3:     public IFileProvider     FileProvider { get; set; }
   4:     public int               SourceCodeLineCount { get; set; }
   5: }

一般来说我们总是通过调用ApplicationBuilder的扩展方法UseDeveloperExceptionPage方法来注册这个DeveloperExceptionPageMiddleware中间件,这两个扩展方法重载采用如下的方式创建并注册这个DeveloperExceptionPageMiddleware中间件。

   1: public static class DeveloperExceptionPageExtensions
   2: {    
   3:     public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app)
   4:     {
   5:         return app.UseMiddleware<DeveloperExceptionPageMiddleware>();
   6:     }    
   7:     public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app,DeveloperExceptionPageOptions options)
   8:     {
   9:         return app.UseMiddleware<DeveloperExceptionPageMiddleware>(Options.Create(options));
  10:     }
  11: }

在《ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式》实例演示中,我们并不曾使用过DeveloperExceptionPageOptions这个对象,对于定义在这个类型中的这两个属性,我想很多人都不知道它们究竟可以用作哪方面的配置。要很清楚地解答这个问题,就需要从 DeveloperExceptionPageMiddleware中间件处理的两种异常类型说起。总的来说,该中间件处理的异常大体上可以分为两类,它们分别是“运行时异常”和“编译异常”,后者类型实现了ICompilationException接口,如下的代码片段基本上体现了异常处理在DeveloperExceptionPageMiddleware中间件中的实现。

   1: public class DeveloperExceptionPageMiddleware
   2: {
   3:     private RequestDelegate _next;
   4:     public async Task Invoke(HttpContext context)
   5:     {
   6:         try
   7:         {
   8:             await _next(context);
   9:         }
  10:         catch(Exception ex)
  11:         {
  12:             context.Response.Clear();
  13:             context.Response.StatusCode = 500;
  14:  
  15:             ICompilationException compilationException = ex as ICompilationException;
  16:             if (null != compilationException)
  17:             {
  18:                 await DisplayCompilationException(context, compilationException);
  19:             }
  20:             else
  21:             {
  22:                 await DisplayRuntimeException(context, ex);
  23:             }
  24:         }
  25:     }
  26:  
  27:     private Task DisplayRuntimeException(HttpContext context, Exception ex);
  28:      private Task DisplayCompilationException(HttpContext context,ICompilationException compilationException) ;
  29: }

一、 处理编译异常

我想很多人会很疑惑:我们编写一个ASP.NET Core应用应该是先编译成程序集,然后再部署并启动执行,为什么运行过程中还会出现“编译异常”呢?从ASP.NET Core应用层面来说,我们采用的是“预编译”,也就说我们部署的不是源代码而是编译好的程序集,所以运行过程中根本就不存在“编译异常”一说。但是不要忘了在一个ASP.NET Core MVC应用中,视图文件(.cshtml)是支持“动态编译”的。也就是说我们可以直接部署视图源文件,应用在执行过程中是可以动态地编译它们的。换句话说,由于视图文件支持动态编译,我们是可以在部署环境直接修改视图文件的。

对于DeveloperExceptionPageMiddleware中间件来说,对于普通的运行时异常,它会采用HTML文档的形式将异常自身的详细信息和当前请求的信息以HTML文档的形式呈现出来,我们前面演示的实例已经很好的说明了这一点。如果应用在动态编译视图文件中出现了编译异常,最终呈现出来的错误页面将具有不同的结构和内容,我们不防也通过一个简单的实例来演示一下DeveloperExceptionPageMiddleware中间件针对编译异常的处理。

我们通过如下所示的代码启动了一个ASP.NET Core MVC应用,并通过调用ApplicationBuilder的扩展方法UseDeveloperExceptionPage注册了DeveloperExceptionPageMiddleware中间件。对应定义在HomeController中的Action方法Index来说,它会负责将对应的视图呈现出来。

   1: public class Program
   2: {
   3:     public static void Main()
   4:     {
   5:         new WebHostBuilder()
   6:             .UseKestrel()
   7:             .UseContentRoot(Directory.GetCurrentDirectory())
   8:             .ConfigureServices(svcs => svcs.AddMvc())
   9:             .Configure(app => app
  10:                 .UseDeveloperExceptionPage()
  11:                 .UseMvc())
  12:             .Build()
  13:             .Run();
  14:     }
  15: }
  16:  
  17: public class HomeController : Controller
  18: {
  19:     [HttpGet("/")]
  20:     public IActionResult Index()
  21:     {
  22:         return View();
  23:     }
  24: }

根据约定,Action方法Index呈现出来的视图文件对应的路径应该是“~/views/home/index.cshtml”,我们为此在这个路径下创建这个视图文件。为了能够在动态编译过程中出现编译异常,我们在这个视图文件中编写了如下三行代码,Foobar是一个尚未被创建的类型

   1: @{ 
   2:     var value = new Foobar();
   3: }

当我们利用浏览器访问HomeController的Action方法Index的时候,应用会动态编译目标视图,由于视图文件中使用了一个不曾不定义的类型,动态编译会失败,响应的错误信息会以如图7所示的形式出现在浏览器上。可以看出错误页面显示的内容和结构与前面演示的实例是完全不一样的,我们不仅可以从这个错误页面中得到导致编译失败的视图文件的路径(“Views/Home/Index.cshtml”),还可以直接看到导致编译失败的那一行代码。不仅如此,这个错误页面还直接将参与编译的源代码(不是定义在.cshtml文件中的原始代码,而是经过转换处理生成的C#代码)。毫无疑问,这个如此详尽的错误页面对于相信开发人员的纠错针对是非常有价值的。

7

一般来说,动态编译的整个过程由两个步骤组成,它先是将源代码(类似于.cshtml这样的模板文件)转换成针对某种.NET语言(比如C#)的代码,然后进一步地编译成IL代码。动态编译过程中抛出的异常类型一般会实现ICompilationException接口。如下面的代码片段所示,该接口值具有一个唯一的属性CompilationFailures,它返回一个元素类型为CompilationFailure的集合。编译失败的相关信息被封装在一个CompilationFailure对象之中,我们可以利用它得到源文件的路径(SourceFilePath)和内容(SourceFileContent),以及源代码转换后交付编译的内容。如果在内容转换过程就已经发生错误,那么SourceFileContent属性可能返回Null。

   1: public interface ICompilationException
   2: {
   3:     IEnumerable<CompilationFailure> CompilationFailures { get; }
   4: }
   5:  
   6: public class CompilationFailure
   7: {
   8:     public string                             SourceFileContent {  get; }
   9:     public string                             SourceFilePath {  get; }
  10:     public string                             CompiledContent {  get; }
  11:     public IEnumerable<DiagnosticMessage>     Messages {  get; }
  12:
  13: }

CompilationFailure类型还具有一个名为Messages的只读属性,它返回一个元素类型为DiagnosticMessage的集合,一个DiagnosticMessage对象承载着一些描述编译错误的诊断信息。我们不仅可以借助DiagnosticMessage对象的相关属性得到描述编译错误的消息(Message和FormattedMessage),还可以得到发生编译错误所在源文件的路径(SourceFilePath)以及范围,StartLine、StartColumn、EndLine和EndColumn属性分别表示导致编译错误的源代码在源文件中开始和结束的行与列(行数和列数分别从1和0开始计数)。

   1: public class DiagnosticMessage
   2: {
   3:     public string     SourceFilePath {  get; }
   4:     public int        StartLine {  get; }
   5:     public int        StartColumn {  get; } 
   6:     public int        EndLine {  get; }
   7:     public int        EndColumn {  get; }
   8:  
   9:     public string     Message {  get; }      
  10:     public string     FormattedMessage {  get; } 
  11:
  12: }

从上图可以看出,错误页面会直接将导致编译失败的相关源代码显示出来。具体来说,它不仅仅会将直接导致失败的源代码实现出来,还会同时显示前后相邻的源代码。至于相邻源代码应该显示多少行,实际上是通过DeveloperExceptionPageOptions的SourceCodeLineCount属性控制的。

   1: public class Program
   2: {
   3:     public static void Main()
   4:     {
   5:         new WebHostBuilder()
   6:             .UseKestrel()
   7:             .UseContentRoot(Directory.GetCurrentDirectory())
   8:             .ConfigureServices(svcs => svcs.AddMvc())
   9:             .Configure(app => app
  10:                 .UseDeveloperExceptionPage(new DeveloperExceptionPageOptions { SourceCodeLineCount = 3 })
  11:                 .UseMvc())
  12:             .Build()
  13:             .Run();
  14:     }
  15: }

对于前面演示的这个实例来说,如果我们希望前后相邻的三行代码被显示在错误页面上,我们可以采用如上的方式为注册的DeveloperExceptionPageMiddleware中间件指定一个DeveloperExceptionPageOptions对象,并将它的SourceCodeLineCount属性设置为3。与此同时,我们将视图文件(index.cshtml)改写成如下的形式,即在导致编译失败的那一行代码前后分别添加了4行代码。

   1: 1:
   2: 2:
   3: 3:
   4: 4:
   5: 5:@{ var value = new Foobar();}
   6: 6:
   7: 7:
   8: 8:
   9: 9:

对于定义在视图文件中的共计9行代码,根据在注册DeveloperExceptionPageMiddleware中间件时指定的规则,最终显示在错误页面上的应该是第2行到第8行。如果利用浏览器访问相同的地址,我们会看到这7行代码会以下图的形式出现在错误页面上。值得一提的是,如果我们没有对SourceCodeLineCount属性作显式设置,它的默认值为6。

8

二、处理运行时异常

对于DeveloperExceptionPageMiddleware中间件来说,任何类型没有实现ICompilationException接口的异常都被视为“运行时异常”。通过ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式》演示的实例我们已经知道,DeveloperExceptionPageMiddleware中间件在处理运行时异常时不仅仅会将异常的详细信息显示在错误页面中,该页面中还会包含于当前请求相关的信息,包括查询字符串Cookie请求报头集合。现在我们关心的是另一个问题,我们利用DeveloperExceptionPageOptions的供的这个FileProvider对象就是出于什么目的呢?

对于错误页面呈现的描述异常的详细信息,除了类型和消息这些基本的信息之外,异常的堆栈追踪(Stack Trace)也会出现在该页面中。不仅如此,如果堆栈追踪包含源代码的信息(比如源文件路径以及对应源代码所在的行和列),DeveloperExceptionPageMiddleware中间件还会试着加载源文件,并将导致异常的源代码原封不动的显示出来。

   1: public class Program
   2: {
   3:     public static void Main()
   4:     {
   5:         new WebHostBuilder()
   6:             .UseKestrel()
   7:             .Configure(app => app
   8:                 .UseDeveloperExceptionPage()
   9:                 .Run(Invoke))
  10:             .Build()
  11:             .Run();
  12:     }
  13:  
  14:     private static Task Invoke(HttpContext context)
  15:     {
  16:         throw new InvalidOperationException("Manually thrown exception");
  17:     }
  18: }

我们将前面演示的代码改写成如上的形式,并在本地以Debug模式运行该程序,将会得到如下图所示的错误页面。我们会看到由于异常的堆栈追踪信息中包含源代码的相关信息(源文件路径和行号),所以导致异常的那一行代码可以原封不动地显示出来。与编译异常处理方式一样,一并显示出来的还包括与之相邻的代码,至于具体会显示多少行相邻代码,自然也是通过DeveloperExceptionPageOptions的SourceCodeLineCount属性来控制的。

9

DeveloperExceptionPageOptions的FileProvider提供FileProvider对象的目的就是帮助读取源文件的内容,或者说它为我们的纠错调试提供源文件。如果我们在创建DeveloperExceptionPageMiddleware中间件的时候没有显式提供这么一个FileProvider,那么默认情况下会使用指向ContentRoot目录的这个PhysicalFileProvider。值得一提的是,如果异常的追踪堆栈中出现了源文件的路径,DeveloperExceptionPageMiddleware中间件总是会试图先从本地文件系统去加载这个文件,只有在本地文件加载失败的情况下它才会利用指定的FileProvider去读取文件。


ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件
ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件
ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
541 10
|
8月前
|
C# Android开发 iOS开发
2025年全面的.NET跨平台应用框架推荐
2025年全面的.NET跨平台应用框架推荐
333 23
|
8月前
|
开发框架 安全 .NET
【Azure Developer】.NET Aspire 项目本地调试遇 Grpc.Core.RpcException 异常( Error starting gRPC call ... )
Error starting gRPC call. HttpRequestException: The SSL connection could not be established, see inner exception. AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
173 12
|
9月前
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
182 1
|
10月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
167 5
|
10月前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
387 2
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
466 6
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
305 0
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
267 0
|
Java Spring 自然语言处理
Spring 框架里竟藏着神秘魔法?国际化与本地化的奇妙之旅等你来揭开谜底!
【8月更文挑战第31天】在软件开发中,国际化(I18N)与本地化(L10N)对于满足不同地区用户需求至关重要。Spring框架提供了强大支持,利用资源文件和`MessageSource`实现多语言文本管理。通过配置日期格式和货币符号,进一步完善本地化功能。合理应用这些特性,可显著提升应用的多地区适应性和用户体验。
132 0

热门文章

最新文章