通过这段处理我们可以看出所有的异常处理都指向当前类的HandleException方法
private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi) { _logger.UnhandledException(edi.SourceException); // 如果输出已经开始执行了,后续的代码就没必要执行了,直接重新抛出 if (context.Response.HasStarted) { _logger.ResponseStartedErrorHandler(); edi.Throw(); } PathString originalPath = context.Request.Path; //如果指定处理异常的终结点,将异常处理交给指定的终结点去处理 if (_options.ExceptionHandlingPath.HasValue) { //将处理路径指向,异常处理终结点路径 context.Request.Path = _options.ExceptionHandlingPath; } try { //清除原有HTTP上下文信息,为了明确指定程序出现异常,防止异常未被处理而后续当做正常操作执行 ClearHttpContext(context); //将异常信息包装成ExceptionHandlerFeature,后续处理程序获取异常信息都是通过ExceptionHandlerFeature var exceptionHandlerFeature = new ExceptionHandlerFeature() { //异常信息 Error = edi.SourceException, //原始路径 Path = originalPath.Value, }; //将包装的ExceptionHandlerFeature放入到上下文中,后续处理程序可通过HttpContext获取异常信息 context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature); context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature); //设置状态码 context.Response.StatusCode = 500; context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response); //调用给定的异常处理终结点处理异常信息 await _options.ExceptionHandler(context); //同样也可以发送诊断日志,可以利用处理程序返回输出,诊断日志记录异常将职责分离 if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException")) { _diagnosticListener.Write("Microsoft.AspNetCore.Diagnostics.HandledException", new { httpContext = context, exception = edi.SourceException }); } return; } catch (Exception ex2) { _logger.ErrorHandlerException(ex2); } finally { //异常处理结束后,恢复原始的请求路径,供后续执行程序能拿到原始的请求信息 context.Request.Path = originalPath; } //如果异常没被处理则重新抛出 edi.Throw(); }最后还有一段清除上下文和清除输出缓存的方法,因为程序一旦发生了异常,可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算。而且异常信息保留输出缓存是没有意义的。
private static void ClearHttpContext(HttpContext context) { context.Response.Clear(); //因为可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算 context.SetEndpoint(endpoint: null); var routeValuesFeature = context.Features.Get<IRouteValuesFeature>(); routeValuesFeature?.RouteValues?.Clear(); } private static Task ClearCacheHeaders(object state) { //清除输出缓存相关 var headers = ((HttpResponse)state).Headers; headers[HeaderNames.CacheControl] = "no-cache"; headers[HeaderNames.Pragma] = "no-cache"; headers[HeaderNames.Expires] = "-1"; headers.Remove(HeaderNames.ETag); return Task.CompletedTask; }从上面的代码我们可以看出UseExceptionHandler要比UseDeveloperExceptionPage实现方式简单很多。其大致思路就是捕获后续管道执行异常,如果存在异常则将异常包装成ExceptionHandlerFeature,放入到Http上下文中。之所以相对简单主要原因还是UseExceptionHandler最终处理异常由我们自定义的终结点去处理,所以它只是负责包装异常相关信息,并将它交于我们定义的异常处理终结点。
UseStatusCodePages无论是UseDeveloperExceptionPage还是UseExceptionHandler都是通过捕获异常的方式去处理异常信息,UseStatusCodePages则是通过Http状态码去判断是否为成功的返回并进行处理,使用方式如下
app.UseStatusCodePages(); //或 app.UseStatusCodePages("text/plain;charset=utf-8", "状态码:{0}"); //或 app.UseStatusCodePages(async context => { context.HttpContext.Response.ContentType = "text/plain;charset=utf-8"; await context.HttpContext.Response.WriteAsync($"状态码:{context.HttpContext.Response.StatusCode}"); }); //或 app.UseStatusCodePages(new StatusCodePagesOptions { HandleAsync = async context=> { context.HttpContext.Response.ContentType = "text/plain;charset=utf-8"; await context.HttpContext.Response.WriteAsync($"状态码:{context.HttpContext.Response.StatusCode}"); }}); //或 app.UseStatusCodePages(configure => { configure.Run(async context => { await context.Response.WriteAsync($"状态码:{context.Response.StatusCode}"); }); });