由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止。出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得到详细的出错信息,这无疑会在开发环境下增加查错纠错的难度。对于生产环境来说,我们也希望最终用户能够根据具体的错误类型得到具有针对性并且友好的错误消息。ASP.NET Core提供了相应的中间件帮助我们将定制化的错误信息呈现出来,这些中间件都定义在“Microsoft.AspNetCore.Diagnostics”这个NuGet包中。在着重介绍这些中间件之前,我们照理演示几个简单的实例让读者朋友们对这些中间件的作用有一个大概的了解。[本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、显示开发者异常页面
二、显示定制异常页面
三、针对响应状态码定制错误页面

一、显示开发者异常页面

一般情况下,如果ASP.NET Core在处理某个请求时出现异常,它一般会返回一个状态码为“500 Internal Server Error”的响应。为了避免一些敏感信息的外泄,详细的错误信息并不会随着响应发送给客户端,所以客户端只会得到一个很一般化的错误消息。以如下这个程序为例,服务端在处理每个请求时都会抛出一个类型为InvalidOperationException的异常。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         new WebHostBuilder()

   6:             .UseKestrel()

   7:             .Configure(app => app.Run(context => Task.FromException(new InvalidOperationException("Manually thrown exception..."))))

   8:             .Build()

   9:             .Run();

  10:     }

  11: }

当我们利用浏览器访问这个应用的时候,总是会得到如下图所示的这个错误页面。可以看出这个页面仅仅告诉我们目标应用当前无法正常处理本次请求,除了提供的响应状态码(“HTTP ERROR 500”)之外,它并没有提供任何有益于差错纠错的错误信息。

那么有人可能会觉得虽然浏览器上没有显示出任何详细的错误信息,也许它会隐藏在接收到的HTTP响应报文中。针对通过浏览器放出的这个请求,得到的响应内容如下所示,我们会发现响应报文根本没有主体部分,有限的几个报头也并没有承载任何与错误有关的信息。

   1: HTTP/1.1 500 Internal Server Error

   2: Date: Fri, 09 Dec 2016 23:42:18 GMT

   3: Content-Length: 0

   4: Server: Kestrel

由于应用并没有中断,浏览器上也并没有显示任何具有针对性的错误信息,开发人员在进行查错纠错的时候如何准确定位到作为错误根源的那一行代码呢?具体来说,我们又两种解决方案,一种就是利用日志,因为ASP.NET Core在进行请求处理时出现的任何错误都会被写入日志,所以我们可以通过注册相应的LoggerProvider(比如注册一个ConsoleLoggerProvider将日志直接写入宿主应用的控制台)到来获取写入的错误日志。

至于另一种解决方案,就是直接显示一个包含错误相应信息的错误页面,由于这个页面是在开发环境给开发者看的,所以我们将这个页面称为“开发者异常页面(Developer Exception Page)”。针对页面的自动呈现是利用一个名为DeveloperExceptionPageMiddleware的中间件来完成的,我们可以调用ApplicationBuilder的扩展方法UseDeveloperExceptionPage来注册这个中间件。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         new WebHostBuilder()

   6:             .UseKestrel()

   7:             .Configure(app => app

   8:                 .UseDeveloperExceptionPage()

   9:                 .Run(context => Task.FromException(new InvalidOperationException("Manually thrown exception..."))))

  10:             .Build()

  11:             .Run();

  12: }

  13: }

一旦注册了这个DeveloperExceptionPageMiddleware中间件,ASP.NET Core应用在处理请求出现的异常信息就会以下图的形式直接出现在浏览器上,我们可以在这个页面中看到几乎所有的错误信息,包括异常的类型、消息和堆栈信息等。

开发者异常页面除了显示与抛出的异常相关的信息之外,还会以如下图所示的形式显示与当前请求上下文相关的信息,其中包括当前请求URL携带的所有查询字符串、所有请求报头以及Cookie的内容。如此详尽的信息无疑会极大地帮助开发人员尽快地找出错误的根源。

通过DeveloperExceptionPageMiddleware中间件呈现的错误页面仅仅是供开发人员使用的,详细的错误信息往往会携带一些敏感的信息,所以务必记住只有在开发环境才能注册这个中间件,如下所示的代码片段体现了针对DeveloperExceptionPageMiddleware中间件正确的注册方式。

   1: new WebHostBuilder()

   2:     .UseStartup<Startup>()

   3:     …

   4:  

   5: public class Startup

   6: {

   7:     public void Configure(IApplicationBuilder app, IHostingEnvironment env)

   8:     {

   9:         if (env.IsDevelopment())

  10:         {

  11:             app.UseDeveloperExceptionPage();

  12:         }

  13:     }

  14: }

二、显示定制异常页面

DeveloperExceptionPageMiddleware中间件通过将异常详细信息和基于当前请求的内容直接呈现在错误页面中,这为开发人员的纠错诊断提供了极大的便利。但是在生产环境下,我们倾向于为最终的用户呈现一个定制的错误页面,而这可以通过注册另一个名为ExceptionHandlerMiddleware的中间件来实现。顾名思义,这个中间件旨在提供一个异常处理器(Exception Handler)来处理抛出的异常。实际上这个所谓的异常处理器就是一个类型为RequestDelegate的委托对象,ExceptionHandlerMiddleware中间件捕捉到抛出的异常后利用它来响应当前的请求。

还是以上面创建的这个总是会抛出一个 InvalidOperationException异常的应用为例。我们按照如下的形式调用ApplicationBuilder的扩展方法UseExceptionHandler注册了上述的这个ExceptionHandlerMiddleware中间件。这个扩展方法具有一个ExceptionHandlerOptions类型的参数,它的ExceptionHandler属性返回的就是这个作为异常处理器的RequestDelegate对象。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         RequestDelegate handler = async context => await context.Response.WriteAsync("Unhandled exception occurred!");

   6:  

   7:         new WebHostBuilder()

   8:             .UseKestrel()

   9:             .Configure(app => app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = handler})

  10:             .Run(context => Task.FromException(new InvalidOperationException("Manually thrown exception..."))))

  11:             .Build()

  12:             .Run();

  13:     }

  14: }

如上面的代码片段所示,这个作为异常处理器的RequestDelegate仅仅是将一个简单的错误消息(“Unhandled exception occurred!”)作为响应的内容。当我们利用浏览器访问该应用的时候,这个定制的错误消息将会以如图4所示的形式直接呈现在浏览器上。

最终作为异常处理器的是一个类型为RequestDelegate的委托对象,而ApplicationBuilder具有创建这个委托对象的能力。具体来说,我们可以根据异常处理的需要将相应的中间件注册到某个ApplicationBuilder对象上,并最终利用这个ApplicationBuilder根据注册的中间件创建出作为异常处理器的RequestDelegate对象。 如果异常处理需要通过一个或者多个中间件来完成,我们可以按照如下的形式调用另一个UseExceptionHandler方法重载。这个方法的参数类型为Action<IApplicationBuilder>,我们调用它的Run方法注册了一个中间件来响应一个简单的错误消息。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {        

   5:         new WebHostBuilder()

   6:             .UseKestrel()

   7:             .Configure(app => app.UseExceptionHandler(builder=>builder.Run(async context => await context.Response.WriteAsync("Unhandled exception occurred!")))

   8:             .Run(context => Task.FromException(new InvalidOperationException("Manually thrown exception..."))))

   9:         .Build()

  10:         .Run();

  11:     }

  12: }

上面这两种异常处理的形式都体现在提供一个RequestDelegate的委托对象来处理抛出的异常并完成最终的响应。如果应用已经设置了一个错误页面,并且这个错误页面具有一个固定的路径,那么我们在进行异常处理的时候就没有必要提供这个RequestDelegate对象,而只需要重定向到错误页面指向的路径即可。这种采用服务端重定向的异常处理方式可以采用如下的形式调用另一个UseExceptionHandler方法重载来完成,这个方法的参数表示的就是重定向的目标路径(“/error”),我们针对这个路径注册了一个路由来响应定制的错误消息。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         new WebHostBuilder()

   6:             .UseKestrel()

   7:             .ConfigureServices(svcs=>svcs.AddRouting())

   8:             .Configure(app => app

   9:                 .UseExceptionHandler("/error")

  10:                 .UseRouter(builder=>builder.MapRoute("error", async context => await context.Response.WriteAsync("Unhandled exception occurred!")))

  11:                 .Run(context => Task.FromException(new InvalidOperationException("Manually thrown exception..."))))

  12:         .Build()

  13:         .Run();

  14:     }

  15: }

三、针对响应状态码定制错误页面

由于Web应用采用HTTP通信协议,所以我们应该尽可能低迎合HTTP标准并将定义在协议规范中的语义应用到应用中。对于异常或者错误的语义表达在HTTP协议层面主要体现在响应报文的状态码上,具体来说HTTP通信的错误大体分为如下两种类型:

  • 客户端错误:表示因客户端提供不正确的请求信息而导致服务器不能正常处理请求,响应状态码范围在400~499之间。

  • 服务端错误:表示服务器在处理请求过程中因自身的问题而发生错误,响应状态码在500~509之间。

正是因为响应状态码是对错误或者异常语义最重要的表达,所以在很多情况下我们需要针对不同的响应状态码来定制显示的错误信息。针对响应状态码对错误页面的定制可以借助一个类型为StatusCodePagesMiddleware的中间件来实现,我们可以调用ApplicationBuilder相应的扩展方法来注册这个中间件。

DeveloperExceptionPageMiddleware和ExceptionHandlerMiddleware中间件都是在后续请求处理过程中抛出异常的情况下才会被调用,而StatusCodePagesMiddleware被调用的前提是后续请求助理过程中产生一个错误响应状态码(范围在400~599之间)。如果仅仅希望显示一个统一的错误页面,我们可以按照如下的形式调用扩展方法UseStatusCodePages注册这个中间件,传入该方法的两个参数分别表示响应采用的媒体类型和主体内容。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {        

   5:         new WebHostBuilder()

   6:             .UseKestrel()

   7:             .Configure(app=>app

   8:                 .UseStatusCodePages("text/plain", "Error occurred ({0})")

   9:                 .Run(context=> Task.Run(()=>context.Response.StatusCode = 500)))

  10:         .Build()

  11:         .Run();

  12:     }

  13: }

如上面的代码片段所示,应用在处理请求的时候总是会将响应状态码设置为500,所以最终的响应内容将由注册的StatusCodePagesMiddleware中间件来提供。我们调用UseStatusCodePages方法的时候将响应的媒体类型设置为“text/plain”,并将一段简单的错误消息作为了响应的主体内容。值得一提的时候,作为响应内容的字符串可以包含一个占位符({0}),StatusCodePagesMiddleware中间件最终会采用当前响应状态码来替换它。如果我们利用浏览器来访问这个应用,将会得到如下图所示的错误页面。

如果我们希望针对不同的错误状态码显示不同的错误页面,那么我们就需要将具体的请求处理逻辑实现在一个的状态码错误处理器中,并最终提供给StatusCodePagesMiddleware中间件。这个所谓的状态码错误处理器体现为一个类型为Func<StatusCodeContext, Task>的委托对象,作为输入的StatusCodeContext对象是对当前HttpContext的封装,同时承载着其他一些与错误处理相关的选项设置,我们将在本系列后续部分对这个类型进行详细介绍。

对于如下这个应用来说,它在处理任意一个请求是总是会随机地选择一个400~599之间的整数作为响应的状态码,所以客户端返回的响应内容总是通过注册的StatusCodePagesMiddleware中间件来提供。我们在调用另一个UseStatusCodePages方法重载的时候,为注册的中间件指定了一个Func<StatusCodeContext, Task>对象作为状态码错误处理器。

   1: public class Program

   2: {

   3: private static Random _random = new Random();

   4:  

   5:     public static void Main()

   6:     {

   7:         Func<StatusCodeContext, Task> handler = async context => {

   8:             var response = context.HttpContext.Response;

   9:             if (response.StatusCode < 500)

  10:             {

  11:                 await response.WriteAsync($"Client error ({response.StatusCode})");

  12:             }

  13:             else

  14:             {

  15:                 await response.WriteAsync($"Server error ({response.StatusCode})");

  16:             }

  17:         };

  18:         new WebHostBuilder()

  19:             .UseKestrel()

  20:             .Configure(app => app

  21:                 .UseStatusCodePages(handler)

  22:                 .Run(context => Task.Run(() => context.Response.StatusCode = _random.Next(400,599))))

  23:             .Build()

  24:             .Run();

  25:     }

  26: }

我们指定的状态码错误处理器在处理请求的时候,根据响应状态码将错误分成客户端错误和服务端错误两种类型,并选择针对性的错误消息作为响应内容。当我们利用浏览器访问这个应用的时候,显示的错误消息将由响应状态码来决定。

在ASP.NET Core的世界里,针对请求的处理总是体现为一个RequestDelegate对象。如果请求的处理需要借助一个或者多个中间件来完成,我们可以将它们注册到ApplicationBuilder对象上并利用它将中间件管道转换成一个RequestDelegate对象。用于注册StatusCodePagesMiddleware中间件的UseStatusCodePage方法还具有另一个重载,它允许我们采用这种方式来创建一个RequestDelegate对象来完成最终的请求处理工作,所以上面演示的这个应用完全可以改写成如下的形式。

   1: public class Program

   2: {

   3:     private static Random _random = new Random();

   4:     public static void Main()

   5:     {

   6:         RequestDelegate handler = async context =>

   7:         {

   8:             var response = context.Response;

   9:             if (response.StatusCode < 500)

  10:             {

  11:                 await response.WriteAsync($"Client error ({response.StatusCode})");

  12:             }

  13:             else

  14:             {

  15:                 await response.WriteAsync($"Server error ({response.StatusCode})");

  16:             }

  17:         };

  18:         new WebHostBuilder()

  19:             .UseKestrel()

  20:             .Configure(app => app

  21:                 .UseStatusCodePages(builder=>builder.Run(handler))

  22:                 .Run(context => Task.Run(() => context.Response.StatusCode = _random.Next(400, 599))))

  23:             .Build()

  24:             .Run();

  25:     }

  26: }


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

ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式的更多相关文章

  1. 从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

    标题:从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/112 ...

  2. 用VSCode开发一个asp.net core 2.0+angular 5项目(4): Angular5全局错误处理

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三 ...

  3. 基于 ASP.NET Core 2.1 的 Razor Class Library 实现自定义错误页面的公用类库

    注意:文中使用的是 razor pages ,建议使用 razor views ,使用 razor pages 有一个小坑,razor pages 会用到 {page} 路由参数,如果应用中也用到了这 ...

  4. 记一次asp.net core 在iis上运行抛出502.5错误

    asp.net core 在iis上运行抛出502.5异常的部分原因以及解决方案 环境说明 已安装 .net core runtime 2.1.401 已安装 .net core windows ho ...

  5. 运行ABP(asp.net core 3.X+Vue)提示'OFFSET' 附近有语法错误。 在 FETCH 语句中选项 NEXT 的用法无效。

    创建ASP.NET Boilerplate,还原数据库和启动客户端 这里就略过,具体参考 ABP框架(asp.net core 2.X+Vue)模板项目学习之路(一) ASP.NET Boilerpl ...

  6. 【ASP.NET Core分布式项目实战】(三)整理IdentityServer4 MVC授权、Consent功能实现

    本博客根据http://video.jessetalk.cn/my/course/5视频整理(内容可能会有部分,推荐看源视频学习) 前言 由于之前的博客都是基于其他的博客进行开发,现在重新整理一下方便 ...

  7. 干货分享:ASP.NET CORE(C#)与Spring Boot MVC(JAVA)异曲同工的编程方式总结

    目录 C# VS JAVA 基础语法类比篇: 一.匿名类 二.类型初始化 三.委托(方法引用) 四.Lambda表达式 五.泛型 六.自动释放 七.重写(override) ASP.NET CORE ...

  8. 使用 ASP.NET Core MVC 创建 Web API(三)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 十 ...

  9. ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)

    在上一章中主要和大家分享了在ASP.NET Core中如何使用Autofac替换自带DI进行构造函数的批量依赖注入,本章将和大家继续分享如何使之能够同时支持属性的批量依赖注入. 约定: 1.仓储层接口 ...

随机推荐

  1. Angular企业级开发(5)-项目框架搭建

    1.AngularJS Seed项目目录结构 AngularJS官方网站提供了一个angular-phonecat项目,另外一个就是Angular-Seed项目.所以大多数团队会基于Angular-S ...

  2. 【Machine Learning】KNN算法虹膜图片识别

    K-近邻算法虹膜图片识别实战 作者:白宁超 2017年1月3日18:26:33 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...

  3. 采用MiniProfiler监控EF与.NET MVC项目(Entity Framework 延伸系列1)

    前言 Entity Framework 延伸系列目录 今天来说说EF与MVC项目的性能检测和监控 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC Mi ...

  4. redux-undo

    简介 通过包装reducer,创建一个state History,保留历史state,可以做退一步,进一步操作 1.install npm install --save redux-undo@beta ...

  5. Could not create SSL connection through proxy serve-svn

    RA layer request failedsvn: Unable to connect to a repository at URL xxxxxx 最后:Could not create SSL ...

  6. Angular企业级开发(4)-ngResource和REST介绍

    一.RESTful介绍 RESTful维基百科 REST(表征性状态传输,Representational State Transfer)是Roy Fielding博士在2000年他的博士论文中提出来 ...

  7. iOS开发之Alamofire源码深度解析

    今天博客中的Alamofire源码的版本是以现在最新的3.4版本为例.上篇博客系统的对NSURLSession相关的东西进行了详细的解析,详情请看<详解NSURLSession>,为了就是 ...

  8. 解读发布:.NET Core RC2 and .NET Core SDK Preview 1

    先看一下 .NET Core(包含 ASP.NET Core)的路线图: Beta6: 2015年7月27日 Beta7: 2015年9月2日 Beta8: 2015年10月15日 RC1: 2015 ...

  9. 原生javascript 固定表头原理与源码

    我在工作中需要固定表头这个功能,我不想去找,没意思.于是就写了一个,我写的是angularjs 自定义指令 起了个 "fix-header" ,有人叫  "freeze- ...

  10. Solr高级查询Facet

    一.什么是facet solr种以导航为目的的查询结果成为facet,在用户查询的结果上根据分类增加了count信息,然后用户根据count信息做进一步搜索. facet主要用于导航实现渐进式精确搜索 ...