深入探究ASP.NET Core异常处理中间件
2021-01-28 20:13
标签:请求过程 简单 his 屏幕 通过 解释 factor append 用处 ????全局异常处理是我们编程过程中不可或缺的重要环节。有了全局异常处理机制给我们带来了很多便捷,首先我们不用满屏幕处理程序可能出现的异常,其次我们可以对异常进行统一的处理,比如收集异常信息或者返回统一的格式等等。ASP.NET Core为我们提供了两种机制去处理全局异常,一是基于中间件的方式,二是基于Filter过滤器的方式。Filter过滤器的方式相对来说比较简单,就是捕获Action执行过程中出现的异常,然后调用注册的Filter去执行处理异常信息,在这里就不过多介绍这种方式了,接下来我们主要介绍中间件的方式。 ????ASP.NET Core为我们提供了几种不同处理异常方式的中间件分别是UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePages、UseStatusCodePagesWithRedirects、UseStatusCodePagesWithReExecute。这几种方式处理的思路是一致的都是通过捕获该管道后续的管道执行过程中出现的异常,只是处理的方式不一样。一般推荐全局异常处理相关中间件写到所有管道的最开始,这样可以捕获到整个执行管道过程中的异常信息。接下来我们介绍一下最常用的三个异常处理中间件UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePage。 UseDeveloperExceptionPage的使用场景大部分是开发阶段,通过名称我们就可以看出,通过它捕获的异常会返回一个异常界面,它的使用方式很简单 如果程序出现异常,出现的效果是这个样子的 我们看到有两个扩展方法一个是无参的,另一个是可以传递DeveloperExceptionPageOptions的扩展方法,因为平时使用无参的方法所以我们看下DeveloperExceptionPageOptions包含了哪些信息,找到DeveloperExceptionPageOptions源码 接下来我们就看核心的DeveloperExceptionPageMiddleware中间件大致是如何工作的 通过上面代码我们可以了解到我们可以通过自定义IDeveloperPageExceptionFilter的方式拦截到异常信息做处理 自定义的MyDeveloperPageExceptionFilter是需要注入的 同时还可以通过诊断日志的方式处理异常信息,我使用了Microsoft.Extensions.DiagnosticAdapter扩展包,所以可以定义强类型类 通过这里可以看出,异常处理扩展性还是非常强的,这仅仅是.Net Core设计方式的冰山一角。刚才我们提到_exceptionHandler才是处理的核心,通过构造函数可知这个委托是通过DisplayException方法初始化的,接下来我们看这里的相关实现 关于DisplayCompilationException我们这里就不做过多解释了,在Asp.Net Core中cshtml文件可以动态编译,有兴趣的同学可以自行了解。我们重点看下DisplayRuntimeException处理 其整体实现思路就是捕获后续执行过程中出现的异常,如果出现异常则包装异常信息以及Http上下文和路由相关信息到ErrorPageModel模型中,然后这个模型作为异常展示界面的数据模型进行展示。 UseExceptionHandler可能是我们在实际开发过程中使用最多的方式。UseDeveloperExceptionPage固然强大,但是返回的终究还是供开发者使用的界面,通过UseExceptionHandler我们可以通过自己的方式处理异常信息,这里就需要我自己编码 通过上面的实现方式我们大概可以猜出扩展方法的几种类型找到源码位置ExceptionHandlerExtensions扩展类 通过UseExceptionHandler扩展方法我们可以知道这么多扩展方法其实本质都是在构建ExceptionHandlerOptions我们来看一下大致实现 这个类非常简单,要么指定处理异常信息的具体终结点路径,要么自定义终结点委托处理异常信息。通过上面的使用示例可以很清楚的看到这一点,接下来我们查看一下ExceptionHandlerMiddleware中间件的大致实现 通过这段处理我们可以看出所有的异常处理都指向当前类的HandleException方法 最后还有一段清除上下文和清除输出缓存的方法,因为程序一旦发生了异常,可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算?。?而且异常信息保留输出缓存是没有意义的。? 从上面的代码我们可以看出UseExceptionHandler要比UseDeveloperExceptionPage实现方式简单很多。其大致思路就是捕获后续管道执行异常,如果存在异常则将异常包装成ExceptionHandlerFeature,放入到Http上下文中。之所以相对简单主要原因还是UseExceptionHandler最终处理异常由我们自定义的终结点去处理,所以它只是负责包装异常相关信息,并将它交于我们定义的异常处理终结点。 无论是UseDeveloperExceptionPage还是UseExceptionHandler都是通过捕获异常的方式去处理异常信息,UseStatusCodePages则是通过Http状态码去判断是否为成功的返回并进行处理,使用方式如下 接下来我们查看一下UseStatusCodePages扩展方法相关实现 虽然扩展方法比较多,但是本质都是组装StatusCodePagesOptions,所以我们直接查看源码 看着代码不少,其实都是吓唬人的,就是给HandleAsync一个默认值,这个默认值里有默认的输出模板。接下来我们查看一下StatusCodePagesMiddleware中间件源码 这个中间件的实现思路更为简单,主要就是拦截请求判断Http状态码,判断是否是400-600,也就是4xx 5xx相关的状态码,如果符合则包装成StatusCodeContext,交由自定义的终结点去处理。 关于常用异常处理中间件我们介绍到这里就差不多了,接下来我们总结对比一下三种中间件的异同和大致实现的方式
深入探究ASP.NET Core异常处理中间件 标签:请求过程 简单 his 屏幕 通过 解释 factor append 用处 原文地址:https://www.cnblogs.com/wucy/p/13203443.html前言
异常处理中间件
UseDeveloperExceptionPage
//这个判断不是必须的,但是在正式环境中给用户展示代码错误信息,终究不是合理的
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
这里包含了非常详细的异常堆栈信息、请求参数、Cookie信息、Header信息、和路由终结点相关的信息。接下来我们找到UseDeveloperExceptionPage所在的扩展类public static class DeveloperExceptionPageExtensions
{
public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app)
{
return app.UseMiddleware
public class DeveloperExceptionPageOptions
{
public DeveloperExceptionPageOptions()
{
SourceCodeLineCount = 6;
}
///
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
public class MyDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
private readonly ILogger
services.AddSingleton
public class DiagnosticCollector
{
private readonly ILogger
private Task DisplayException(ErrorContext errorContext)
{
var httpContext = errorContext.HttpContext;
var headers = httpContext.Request.GetTypedHeaders();
var acceptHeader = headers.Accept;
//如果acceptHeader不存在或者类型不是text/plain,将以字符串的形式输出异常,比如通过代码或者Postman的方式调用并未设置头信息
if (acceptHeader == null || !acceptHeader.Any(h => h.IsSubsetOf(_textHtmlMediaType)))
{
httpContext.Response.ContentType = "text/plain";
var sb = new StringBuilder();
sb.AppendLine(errorContext.Exception.ToString());
sb.AppendLine();
sb.AppendLine("HEADERS");
sb.AppendLine("=======");
foreach (var pair in httpContext.Request.Headers)
{
sb.AppendLine($"{pair.Key}: {pair.Value}");
}
return httpContext.Response.WriteAsync(sb.ToString());
}
//判断是否为编译时异常,比如视图文件可以动态编译
if (errorContext.Exception is ICompilationException compilationException)
{
return DisplayCompilationException(httpContext, compilationException);
}
//处理运行时异常
return DisplayRuntimeException(httpContext, errorContext.Exception);
}
private Task DisplayRuntimeException(HttpContext context, Exception ex)
{
//获取终结点信息
var endpoint = context.Features.Get
UseExceptionHandler
app.UseExceptionHandler(configure =>
{
configure.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get
public static class ExceptionHandlerExtensions
{
public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app)
{
return app.UseMiddleware
public class ExceptionHandlerOptions
{
///
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ExceptionHandlerOptions _options;
private readonly ILogger _logger;
private readonly Func
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
private static void ClearHttpContext(HttpContext context)
{
context.Response.Clear();
//因为可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get
UseStatusCodePages
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}");
});
});
public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options)
{
return app.UseMiddleware
public class StatusCodePagesOptions
{
public StatusCodePagesOptions()
{
//初始化
HandleAsync = context =>
{
var statusCode = context.HttpContext.Response.StatusCode;
var body = BuildResponseBody(statusCode);
context.HttpContext.Response.ContentType = "text/plain";
return context.HttpContext.Response.WriteAsync(body);
};
}
private string BuildResponseBody(int httpStatusCode)
{
//组装默认消息模板
var internetExplorerWorkaround = new string(‘ ‘, 500);
var reasonPhrase = ReasonPhrases.GetReasonPhrase(httpStatusCode);
return string.Format(CultureInfo.InvariantCulture, "Status Code: {0}{1}{2}{3}",
httpStatusCode,
string.IsNullOrWhiteSpace(reasonPhrase) ? "" : "; ",
reasonPhrase,
internetExplorerWorkaround);
}
public Func
public class StatusCodePagesMiddleware
{
private readonly RequestDelegate _next;
private readonly StatusCodePagesOptions _options;
public StatusCodePagesMiddleware(RequestDelegate next, IOptions
总结
最后我们再来总结下使用中间件的方式和使用IExceptionFilter的方式的区别
以上就是文章的全部内容,由于能力有限,如果存在理解不周之处请多多谅解。我觉得学习一个东西,如果你能了解到它的工作方式或者实现原理,肯定会对你的编程思路有所提升,看过的代码用过的东西可能会忘,但是思路一旦形成,将会改变你以后的思维方式。
下一篇:css_float