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#代码)。毫无疑问,这个如此详尽的错误页面对于相信开发人员的纠错针对是非常有价值的。

一般来说,动态编译的整个过程由两个步骤组成,它先是将源代码(类似于.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。

二、处理运行时异常
对于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属性来控制的。

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中间件
ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”的更多相关文章
- ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得 ...
- ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”
DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMi ...
- ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的 ...
- ASP.NET Core错误处理中间件[2]: 开发者异常页面
<呈现错误信息>通过几个简单的实例演示了如何呈现一个错误页面,该过程由3个对应的中间件来完成.下面先介绍用来呈现开发者异常页面的DeveloperExceptionPageMiddlewa ...
- 本地化ASP.NET core模型绑定错误消息
默认错误消息: MissingBindRequiredValueAccessor A value for the '{0}' property was not provided. MissingKey ...
- (7)ASP.NET Core 中的错误处理
1.前言 ASP.NET Core处理错误环境区分为两种:开发环境和非开发环境.●开发环境:开发人员异常页.●非开发环境:异常处理程序页.状态代码页.在Startup.Configure方法里面我们会 ...
- asp.net core系列 37 WebAPI 使用OpenAPI (swagger)中间件
一.概述 在使用Web API时,对于开发人员来说,了解其各种方法可能是一项挑战.在ASP.NET Core上,Web api 辅助工具介绍二个中间件,包括:Swashbuckle和NSwag .NE ...
- 15.ASP.NET Core 应用程序中的静态文件中间件
在这篇文章中,我将向大家介绍,如何使用中间件组件来处理静态文件.这篇文章中,我们讨论下面几个问题: 在ASP.NET Core中,我们需要把静态文件存放在哪里? 在ASP.NET Core中 wwwr ...
- asp.net core 系列 14 错误处理
一.概述 本文介绍处理 ASP.NET Core 应用中常见错误的一些方法.主要是关于:开发环境异常页:非开发环境配置自定义异常处理页:配置状态代码页(没有正文响应,http状态400~599的). ...
随机推荐
- 在SQL2008查找某数据库中的列是否存在某个值
在SQL2008查找某数据库中的列是否存在某个值 --SQL2008查找某数据库中的列是否存在某个值 create proc spFind_Column_In_DB ( @type int,--类型: ...
- Visual Studio Code 代理设置
Visual Studio Code (简称 VS Code)是由微软研发的一款免费.开源的跨平台文本(代码)编辑器,在十多年的编程经历中,我使用过非常多的的代码编辑器(包括 IDE),例如 Fron ...
- 再谈CAAnimation动画
CAAnimaton动画分为CABasicAnimation & CAKeyframeAnimation CABasicAnimation动画, 顾名思义就是最基本的动画, 老规矩先上代码: ...
- C# DateTime日期格式化
在C#中DateTime是一个包含日期.时间的类型,此类型通过ToString()转换为字符串时,可根据传入给Tostring()的参数转换为多种字符串格式. 目录 1. 分类 2. 制式类型 3. ...
- PowerDesigner-VBSrcipt-自动设置主键,外键名等(SQL Server)
在PowerDesigner中的设计SQL Server 数据表时,要求通过vbScript脚本实现下面的功能: 主键:pk_TableName 外键:fk_TableName_ForeignKeyC ...
- 利用Oracle RUEI+EM12c进行应用的“端到端”性能诊断
概述 我们知道,影响一个B/S应用性能的因素,粗略地说,有以下几个大的环节: 1. 客户端环节 2. 网络环节(可能包括WAN和LAN) 3. 应用及中间层环节 4. 数据库层环节 能够对各个环节的问 ...
- [C#] string 与 String,大 S 与小 S 之间没有什么不可言说的秘密
string 与 String,大 S 与小 S 之间没有什么不可言说的秘密 目录 小写 string 与大写 String 声明与初始化 string string 的不可变性 正则 string ...
- seaJs学习笔记2 – seaJs组建库的使用
原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
- 后缀数组的倍增算法(Prefix Doubling)
后缀数组的倍增算法(Prefix Doubling) 文本内容除特殊注明外,均在知识共享署名-非商业性使用-相同方式共享 3.0协议下提供,附加条款亦可能应用. 最近在自学习BWT算法(Burrows ...
- 微服务与Docker介绍
什么是微服务 微服务应用的一个最大的优点是,它们往往比传统的应用程序更有效地利用计算资源.这是因为它们通过扩展组件来处理功能瓶颈问题.这样一来,开发人员只需要为额外的组件部署计算资源,而不需要部署一个 ...