在《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中间件如何呈现“开发者异常页面”的更多相关文章

  1. ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式

    由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得 ...

  2. ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”

    DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMi ...

  3. ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面

    StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的 ...

  4. ASP.NET Core错误处理中间件[2]: 开发者异常页面

    <呈现错误信息>通过几个简单的实例演示了如何呈现一个错误页面,该过程由3个对应的中间件来完成.下面先介绍用来呈现开发者异常页面的DeveloperExceptionPageMiddlewa ...

  5. 本地化ASP.NET core模型绑定错误消息

    默认错误消息: MissingBindRequiredValueAccessor A value for the '{0}' property was not provided. MissingKey ...

  6. (7)ASP.NET Core 中的错误处理

    1.前言 ASP.NET Core处理错误环境区分为两种:开发环境和非开发环境.●开发环境:开发人员异常页.●非开发环境:异常处理程序页.状态代码页.在Startup.Configure方法里面我们会 ...

  7. asp.net core系列 37 WebAPI 使用OpenAPI (swagger)中间件

    一.概述 在使用Web API时,对于开发人员来说,了解其各种方法可能是一项挑战.在ASP.NET Core上,Web api 辅助工具介绍二个中间件,包括:Swashbuckle和NSwag .NE ...

  8. 15.ASP.NET Core 应用程序中的静态文件中间件

    在这篇文章中,我将向大家介绍,如何使用中间件组件来处理静态文件.这篇文章中,我们讨论下面几个问题: 在ASP.NET Core中,我们需要把静态文件存放在哪里? 在ASP.NET Core中 wwwr ...

  9. asp.net core 系列 14 错误处理

    一.概述 本文介绍处理 ASP.NET Core 应用中常见错误的一些方法.主要是关于:开发环境异常页:非开发环境配置自定义异常处理页:配置状态代码页(没有正文响应,http状态400~599的). ...

随机推荐

  1. 【流量劫持】躲避 HSTS 的 HTTPS 劫持

    前言 HSTS 的出现,对 HTTPS 劫持带来莫大的挑战. 不过,HSTS 也不是万能的,它只能解决 SSLStrip 这类劫持方式.但仔细想想,SSLStrip 这种算劫持吗? 劫持 vs 钓鱼 ...

  2. C++ 应用程序性能优化

    C++ 应用程序性能优化 eryar@163.com 1. Introduction 对于几何造型内核OpenCASCADE,由于会涉及到大量的数值算法,如矩阵相关计算,微积分,Newton迭代法解方 ...

  3. 来吧,HTML5之基础标签(上)

    什么是html5 HTML 5 是下一代的 HTML.HTML5 仍处于完善之中.然而,大部分现代浏览器已经具备了某些 HTML5 支持. 学习过程中标签的理解 <a>标签  定义超链接, ...

  4. Javascript实现HashTable类

    散列算法可以尽快在数据结构中找出指定的一个值,因为可以通过Hash算法求出值的所在位置,存储和插入的时候都按照Hash算法放到指定位置. <script> function HashTab ...

  5. ASP.Net MVC4+Memcached+CodeFirst实现分布式缓存

    ASP.Net MVC4+Memcached+CodeFirst实现分布式缓存 part 1:给我点时间,允许我感慨一下2016年 正好有时间,总结一下最近使用的一些技术,也算是为2016年画上一个完 ...

  6. Android实现TCP断点上传,后台C#服务实现接收

    终端实现大文件上传一直都是比较难的技术,其中涉及到后端与前端的交互,稳定性和流量大小,而且实现原理每个人都有自己的想法,后端主流用的比较多的是Http来实现,因为大多实现过断点下载.但稳定性不能保证, ...

  7. “老坛泡新菜”:SOD MVVM框架,让WinForms焕发新春

    火热的MVVM框架 最近几年最热门的技术之一就是前端技术了,各种前端框架,前端标准和前端设计风格层出不穷,而在众多前端框架中具有MVC,MVVM功能的框架成为耀眼新星,比如GitHub关注度很高的Vu ...

  8. 【干货分享】流程DEMO-补打卡

    流程名: 补打卡申请 业务描述: 当员工在该出勤的工作日出勤但漏打卡时,于一周内填写补打卡申请. 流程相关文件: 流程包.xml 流程说明: 直接导入流程包文件,即可使用本流程 表单:  流程: 图片 ...

  9. CRM 数据密钥 忘记 解决方案

    UPDATE EmailServerProfile SET IncomingPassword=nullUPDATE EmailServerProfile SET OutgoingPassword=nu ...

  10. Linux实战教学笔记07:Linux系统目录结构介绍

    第七节 Linux系统目录结构介绍 标签(空格分隔):Linux实战教学笔记 第1章 前言 windows目录结构 C:\windows D:\Program Files E:\你懂的\精品 F:\你 ...