接下来我们查看一下UseStatusCodePages扩展方法相关实现
public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options) { return app.UseMiddleware<StatusCodePagesMiddleware>(Options.Create(options)); } public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app) { return app.UseMiddleware<StatusCodePagesMiddleware>(); } public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Func<StatusCodeContext, Task> handler) { return app.UseStatusCodePages(new StatusCodePagesOptions { HandleAsync = handler }); } public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, string contentType, string bodyFormat) { return app.UseStatusCodePages(context => { var body = string.Format(CultureInfo.InvariantCulture, bodyFormat, context.HttpContext.Response.StatusCode); context.HttpContext.Response.ContentType = contentType; return context.HttpContext.Response.WriteAsync(body); }); }虽然扩展方法比较多,但是本质都是组装StatusCodePagesOptions,所以我们直接查看源码
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<StatusCodeContext, Task> HandleAsync { get; set; } }看着代码不少,其实都是吓唬人的,就是给HandleAsync一个默认值,这个默认值里有默认的输出模板。接下来我们查看一下StatusCodePagesMiddleware中间件源码
public class StatusCodePagesMiddleware { private readonly RequestDelegate _next; private readonly StatusCodePagesOptions _options; public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options) { _next = next; _options = options.Value; } public async Task Invoke(HttpContext context) { //初始化StatusCodePagesFeature var statusCodeFeature = new StatusCodePagesFeature(); context.Features.Set<IStatusCodePagesFeature>(statusCodeFeature); await _next(context); if (!statusCodeFeature.Enabled) { return; } //这个范围外的Http状态码直接忽略,不受程序处理只处理值为400-600之间的状态码 if (context.Response.HasStarted || context.Response.StatusCode < 400 || context.Response.StatusCode >= 600 || context.Response.ContentLength.HasValue || !string.IsNullOrEmpty(context.Response.ContentType)) { return; } //将状态信息包装到StatusCodeContext,传递给自定义处理终结点 var statusCodeContext = new StatusCodeContext(context, _options, _next); await _options.HandleAsync(statusCodeContext); } }这个中间件的实现思路更为简单,主要就是拦截请求判断Http状态码,判断是否是400-600,也就是4xx 5xx相关的状态码,如果符合则包装成StatusCodeContext,交由自定义的终结点去处理。
总结关于常用异常处理中间件我们介绍到这里就差不多了,接下来我们总结对比一下三种中间件的异同和大致实现的方式
UseDeveloperExceptionPage中间件主要工作方式就是捕获后续中间件执行异常,如果存在异常则将异常信息包装成ErrorPageModel视图模型,然后通过这个模型去渲染开发者异常界面。
UseExceptionHandler中间件核心思路和UseDeveloperExceptionPage类似都是通过捕获后续中间件执行异常,不同之处在于UseExceptionHandler将捕获的异常信息包装到ExceptionHandlerFeature然后将其放入Http上下文中,后续的异常处理终结点通过Http上下文获取到异常信息进行处理。
UseStatusCodePages中间件相对于前两种中间件最为简单,其核心思路就是获取执行完成后的Http状态码判断是否是4xx 5xx相关,如果是则执行自定义的状态码拦截终结点。这个中间件核心是围绕StatusCode其实并不包含处理异常相关的逻辑,所以整体实现相对简单。
最后我们再来总结下使用中间件的方式和使用IExceptionFilter的方式的区别中间件的方式是对整个请求执行管道进行异常捕获,主要是负责整个请求过程中的异常捕获,其生命周期更靠前,捕获异常的范围更广泛。毕竟MVC只是Asp.Net Core终结点的一种实现方式,目前Asp.Net Core还可以处理GRPC Signalr等其它类型的终结点信息。
IExceptionFilter主要是针对Action执行过程中的异常,毕竟终结点只是中间件的一种形式,所以可处理范围比较有限,主要适用于MVC程序。对于其它终结点类型有点无能为力。
以上就是文章的全部内容,由于能力有限,如果存在理解不周之处请多多谅解。我觉得学习一个东西,如果你能了解到它的工作方式或者实现原理,肯定会对你的编程思路有所提升,看过的代码用过的东西可能会忘,但是思路一旦形成,将会改变你以后的思维方式。