全局异常处理是我们编程过程中不可或缺的重要环节。有了全局异常处理机制给我们带来了很多便捷,首先我们不用满屏幕处理程序可能出现的异常,其次我们可以对异常进行统一的处理,比如收集异常信息或者返回统一的格式等等。ASP.NET Core为我们提供了两种机制去处理全局异常,一是基于中间件的方式,二是基于Filter过滤器的方式。Filter过滤器的方式相对来说比较简单,就是捕获Action执行过程中出现的异常,然后调用注册的Filter去执行处理异常信息,在这里就不过多介绍这种方式了,接下来我们主要介绍中间件的方式。
异常处理中间件ASP.NET Core为我们提供了几种不同处理异常方式的中间件分别是UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePages、UseStatusCodePagesWithRedirects、UseStatusCodePagesWithReExecute。这几种方式处理的思路是一致的都是通过捕获该管道后续的管道执行过程中出现的异常,只是处理的方式不一样。一般推荐全局异常处理相关中间件写到所有管道的最开始,这样可以捕获到整个执行管道过程中的异常信息。接下来我们介绍一下最常用的三个异常处理中间件UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePage。
UseDeveloperExceptionPageUseDeveloperExceptionPage的使用场景大部分是开发阶段,通过名称我们就可以看出,通过它捕获的异常会返回一个异常界面,它的使用方式很简单
//这个判断不是必须的,但是在正式环境中给用户展示代码错误信息,终究不是合理的 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }如果程序出现异常,出现的效果是这个样子的
这里包含了非常详细的异常堆栈信息、请求参数、Cookie信息、Header信息、和路由终结点相关的信息。接下来我们找到UseDeveloperExceptionPage所在的扩展类 public static class DeveloperExceptionPageExtensions { public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app) { return app.UseMiddleware<DeveloperExceptionPageMiddleware>(); } public static IApplicationBuilder UseDeveloperExceptionPage( this IApplicationBuilder app, DeveloperExceptionPageOptions options) { return app.UseMiddleware<DeveloperExceptionPageMiddleware>(Options.Create(options)); } }
我们看到有两个扩展方法一个是无参的,另一个是可以传递DeveloperExceptionPageOptions的扩展方法,因为平时使用无参的方法所以我们看下DeveloperExceptionPageOptions包含了哪些信息,找到DeveloperExceptionPageOptions源码
public class DeveloperExceptionPageOptions { public DeveloperExceptionPageOptions() { SourceCodeLineCount = 6; } /// <summary> /// 展示出现异常代码的地方上下展示多少行的代码信息,默认是6行 /// </summary> public int SourceCodeLineCount { get; set; } /// <summary> /// 通过这个文件提供程序我们可以猜测到,我们可以自定义异常错误界面 /// </summary> public IFileProvider FileProvider { get; set; } }接下来我们就看核心的DeveloperExceptionPageMiddleware中间件大致是如何工作的
public class DeveloperExceptionPageMiddleware { private readonly RequestDelegate _next; private readonly DeveloperExceptionPageOptions _options; private readonly ILogger _logger; private readonly IFileProvider _fileProvider; private readonly DiagnosticSource _diagnosticSource; private readonly ExceptionDetailsProvider _exceptionDetailsProvider; private readonly Func<ErrorContext, Task> _exceptionHandler; private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html"); public DeveloperExceptionPageMiddleware( RequestDelegate next, IOptions<DeveloperExceptionPageOptions> options, ILoggerFactory loggerFactory, IWebHostEnvironment hostingEnvironment, DiagnosticSource diagnosticSource, IEnumerable<IDeveloperPageExceptionFilter> filters) { _next = next; _options = options.Value; _logger = loggerFactory.CreateLogger<DeveloperExceptionPageMiddleware>(); //默认使用ContentRootFileProvider _fileProvider = _options.FileProvider ?? hostingEnvironment.ContentRootFileProvider; //可以发送诊断日志 _diagnosticSource = diagnosticSource; _exceptionDetailsProvider = new ExceptionDetailsProvider(_fileProvider, _options.SourceCodeLineCount); _exceptionHandler = DisplayException; //构建IDeveloperPageExceptionFilter执行管道,说明我们同时还可以通过程序的方式捕获异常信息 foreach (var filter in filters.Reverse()) { var nextFilter = _exceptionHandler; _exceptionHandler = errorContext => filter.HandleExceptionAsync(errorContext, nextFilter); } } public async Task Invoke(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.UnhandledException(ex); if (context.Response.HasStarted) { _logger.ResponseStartedErrorPageMiddleware(); throw; } try { //清除输出相关信息,将状态码设为500 context.Response.Clear(); context.Response.StatusCode = 500; //核心处理 await _exceptionHandler(new ErrorContext(context, ex)); //发送名称为Microsoft.AspNetCore.Diagnostics.UnhandledException诊断日志,我们可以自定义订阅者处理异常 if (_diagnosticSource.IsEnabled("Microsoft.AspNetCore.Diagnostics.UnhandledException")) { _diagnosticSource.Write("Microsoft.AspNetCore.Diagnostics.UnhandledException", new { httpContext = context, exception = ex }); } return; } catch (Exception ex2) { _logger.DisplayErrorPageException(ex2); } throw; } } }