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

简介:

在《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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
人工智能 安全 中间件
阿里云 AI 中间件重磅发布,打通 AI 应用落地“最后一公里”
9 月 26 日,2025 云栖大会 AI 中间件:AI 时代的中间件技术演进与创新实践论坛上,阿里云智能集团资深技术专家林清山发表主题演讲《未来已来:下一代 AI 中间件重磅发布,解锁 AI 应用架构新范式》,重磅发布阿里云 AI 中间件,提供面向分布式多 Agent 架构的基座,包括:AgentScope-Java(兼容 Spring AI Alibaba 生态),AI MQ(基于Apache RocketMQ 的 AI 能力升级),AI 网关 Higress,AI 注册与配置中心 Nacos,以及覆盖模型与算力的 AI 可观测体系。
2069 94
|
中间件 关系型数据库 数据库
docker快速部署OS web中间件 数据库 编程应用
通过Docker,可以轻松地部署操作系统、Web中间件、数据库和编程应用。本文详细介绍了使用Docker部署这些组件的基本步骤和命令,展示了如何通过Docker Compose编排多容器应用。希望本文能帮助开发者更高效地使用Docker进行应用部署和管理。
553 19
|
C# Android开发 iOS开发
2025年全面的.NET跨平台应用框架推荐
2025年全面的.NET跨平台应用框架推荐
1020 23
|
监控 安全 中间件
Next.js 实战 (十):中间件的魅力,打造更快更安全的应用
这篇文章介绍了什么是Next.js中的中间件以及其应用场景。中间件可以用于处理每个传入请求,比如实现日志记录、身份验证、重定向、CORS配置等功能。文章还提供了一个身份验证中间件的示例代码,以及如何使用限流中间件来限制同一IP地址的请求次数。中间件相当于一个构建模块,能够简化HTTP请求的预处理和后处理,提高代码的可维护性,有助于创建快速、安全和用户友好的Web体验。
549 0
Next.js 实战 (十):中间件的魅力,打造更快更安全的应用
|
Web App开发 JSON JavaScript
Node.js 中的中间件机制与 Express 应用
Node.js 中的中间件机制与 Express 应用
|
开发框架 监控 .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
512 5
|
消息中间件 存储 Apache
探索 RocketMQ:企业级消息中间件的选择与应用
RocketMQ 是一个高性能、高可靠、可扩展的分布式消息中间件,它是由阿里巴巴开发并贡献给 Apache 软件基金会的一个开源项目。RocketMQ 主要用于处理大规模、高吞吐量、低延迟的消息传递,它是一个轻量级的、功能强大的消息队列系统,广泛应用于金融、电商、日志系统、数据分析等领域。
1833 0
探索 RocketMQ:企业级消息中间件的选择与应用
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
799 2
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
988 6
|
中间件 API 开发者
深入理解Python Web框架:中间件的工作原理与应用策略
在Python Web开发中,中间件位于请求处理的关键位置,提供强大的扩展能力。本文通过问答形式,探讨中间件的工作原理、应用场景及实践策略,并以Flask和Django为例展示具体实现。中间件可以在请求到达视图前或响应返回后执行代码,实现日志记录、权限验证等功能。Flask通过装饰器模拟中间件行为,而Django则提供官方中间件系统,允许在不同阶段扩展功能。合理制定中间件策略能显著提升应用的灵活性和可扩展性。
552 4

热门文章

最新文章