深入探究ASP.NET Core异常处理中间件
前言
????全局異常處理是我們編程過程中不可或缺的重要環(huán)節(jié)。有了全局異常處理機制給我們帶來了很多便捷,首先我們不用滿屏幕處理程序可能出現(xiàn)的異常,其次我們可以對異常進行統(tǒng)一的處理,比如收集異常信息或者返回統(tǒng)一的格式等等。ASP.NET Core為我們提供了兩種機制去處理全局異常,一是基于中間件的方式,二是基于Filter過濾器的方式。Filter過濾器的方式相對來說比較簡單,就是捕獲Action執(zhí)行過程中出現(xiàn)的異常,然后調(diào)用注冊的Filter去執(zhí)行處理異常信息,在這里就不過多介紹這種方式了,接下來我們主要介紹中間件的方式。
異常處理中間件
??? ASP.NET Core為我們提供了幾種不同處理異常方式的中間件分別是UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePages、UseStatusCodePagesWithRedirects、UseStatusCodePagesWithReExecute。這幾種方式處理的思路是一致的都是通過捕獲該管道后續(xù)的管道執(zhí)行過程中出現(xiàn)的異常,只是處理的方式不一樣。一般推薦全局異常處理相關(guān)中間件寫到所有管道的最開始,這樣可以捕獲到整個執(zhí)行管道過程中的異常信息。接下來我們介紹一下最常用的三個異常處理中間件UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePage。
UseDeveloperExceptionPage
UseDeveloperExceptionPage的使用場景大部分是開發(fā)階段,通過名稱我們就可以看出,通過它捕獲的異常會返回一個異常界面,它的使用方式很簡單
//這個判斷不是必須的,但是在正式環(huán)境中給用戶展示代碼錯誤信息,終究不是合理的 if (env.IsDevelopment()) {app.UseDeveloperExceptionPage(); }如果程序出現(xiàn)異常,出現(xiàn)的效果是這個樣子的
這里包含了非常詳細的異常堆棧信息、請求參數(shù)、Cookie信息、Header信息、和路由終結(jié)點相關(guān)的信息。接下來我們找到UseDeveloperExceptionPage所在的擴展類
我們看到有兩個擴展方法一個是無參的,另一個是可以傳遞DeveloperExceptionPageOptions的擴展方法,因為平時使用無參的方法所以我們看下DeveloperExceptionPageOptions包含了哪些信息,找到DeveloperExceptionPageOptions源碼
public class DeveloperExceptionPageOptions {public DeveloperExceptionPageOptions(){SourceCodeLineCount = 6;}/// <summary>/// 展示出現(xiàn)異常代碼的地方上下展示多少行的代碼信息,默認是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;//可以發(fā)送診斷日志_diagnosticSource = diagnosticSource;_exceptionDetailsProvider = new ExceptionDetailsProvider(_fileProvider, _options.SourceCodeLineCount);_exceptionHandler = DisplayException;//構(gòu)建IDeveloperPageExceptionFilter執(zhí)行管道,說明我們同時還可以通過程序的方式捕獲異常信息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{//清除輸出相關(guān)信息,將狀態(tài)碼設(shè)為500context.Response.Clear();context.Response.StatusCode = 500;//核心處理await _exceptionHandler(new ErrorContext(context, ex));//發(fā)送名稱為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;}} }通過上面代碼我們可以了解到我們可以通過自定義IDeveloperPageExceptionFilter的方式攔截到異常信息做處理
public class MyDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter {private readonly ILogger<MyDeveloperPageExceptionFilter> _logger;public MyDeveloperPageExceptionFilter(ILogger<MyDeveloperPageExceptionFilter> logger){_logger = logger;}public async Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next){_logger.LogInformation($"狀態(tài)碼:{errorContext.HttpContext.Response.StatusCode},異常信息:{errorContext.Exception.Message}");await next(errorContext);} }自定義的MyDeveloperPageExceptionFilter是需要注入的
services.AddSingleton<IDeveloperPageExceptionFilter,MyDeveloperPageExceptionFilter>();同時還可以通過診斷日志的方式處理異常信息,我使用了Microsoft.Extensions.DiagnosticAdapter擴展包,所以可以定義強類型類
public class DiagnosticCollector {private readonly ILogger<DiagnosticCollector> _logger;public DiagnosticCollector(ILogger<DiagnosticCollector> logger){_logger = logger;}[DiagnosticName("Microsoft.AspNetCore.Diagnostics.UnhandledException")]public void OnRequestStart(HttpContext httpContext, Exception exception){_logger.LogInformation($"診斷日志收集到異常,狀態(tài)碼:{httpContext.Response.StatusCode},異常信息:{exception.Message}");} }通過這里可以看出,異常處理擴展性還是非常強的,這僅僅是.Net Core設(shè)計方式的冰山一角。剛才我們提到_exceptionHandler才是處理的核心,通過構(gòu)造函數(shù)可知這個委托是通過DisplayException方法初始化的,接下來我們看這里的相關(guān)實現(xiàn)
private Task DisplayException(ErrorContext errorContext) {var httpContext = errorContext.HttpContext;var headers = httpContext.Request.GetTypedHeaders();var acceptHeader = headers.Accept;//如果acceptHeader不存在或者類型不是text/plain,將以字符串的形式輸出異常,比如通過代碼或者Postman的方式調(diào)用并未設(shè)置頭信息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());}//判斷是否為編譯時異常,比如視圖文件可以動態(tài)編譯if (errorContext.Exception is ICompilationException compilationException){return DisplayCompilationException(httpContext, compilationException);}//處理運行時異常return DisplayRuntimeException(httpContext, errorContext.Exception); }關(guān)于DisplayCompilationException我們這里就不做過多解釋了,在Asp.Net Core中cshtml文件可以動態(tài)編譯,有興趣的同學(xué)可以自行了解。我們重點看下DisplayRuntimeException處理
private Task DisplayRuntimeException(HttpContext context, Exception ex) {//獲取終結(jié)點信息var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;EndpointModel endpointModel = null;if (endpoint != null){endpointModel = new EndpointModel();endpointModel.DisplayName = endpoint.DisplayName;if (endpoint is RouteEndpoint routeEndpoint){endpointModel.RoutePattern = routeEndpoint.RoutePattern.RawText;endpointModel.Order = routeEndpoint.Order;var httpMethods = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods;if (httpMethods != null){endpointModel.HttpMethods = string.Join(", ", httpMethods);}}}var request = context.Request;//往視圖還是個輸出的模型,對于我們上面截圖展示的信息對應(yīng)的數(shù)據(jù)var model = new ErrorPageModel{Options = _options,//異常詳情ErrorDetails = _exceptionDetailsProvider.GetDetails(ex),//查詢參數(shù)相關(guān)Query = request.Query,//Cookie信息Cookies = request.Cookies,//頭信息Headers = request.Headers,//路由信息RouteValues = request.RouteValues,//終結(jié)點信息Endpoint = endpointModel};var errorPage = new ErrorPage(model);//執(zhí)行輸出視圖頁面,也就是我們看到的開發(fā)者頁面return errorPage.ExecuteAsync(context); }其整體實現(xiàn)思路就是捕獲后續(xù)執(zhí)行過程中出現(xiàn)的異常,如果出現(xiàn)異常則包裝異常信息以及Http上下文和路由相關(guān)信息到ErrorPageModel模型中,然后這個模型作為異常展示界面的數(shù)據(jù)模型進行展示。
UseExceptionHandler
UseExceptionHandler可能是我們在實際開發(fā)過程中使用最多的方式。UseDeveloperExceptionPage固然強大,但是返回的終究還是供開發(fā)者使用的界面,通過UseExceptionHandler我們可以通過自己的方式處理異常信息,這里就需要我自己編碼
app.UseExceptionHandler(configure => {configure.Run(async context =>{var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();var ex = exceptionHandlerPathFeature?.Error;if (ex != null){context.Response.ContentType = "text/plain;charset=utf-8";await context.Response.WriteAsync(ex.ToString());}}); }); //或 app.UseExceptionHandler(new ExceptionHandlerOptions {ExceptionHandler = async context =>{var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();var ex = exceptionHandlerPathFeature?.Error;if (ex != null){context.Response.ContentType = "text/plain;charset=utf-8";await context.Response.WriteAsync(ex.ToString());}} }); //或 app.UseExceptionHandler(new ExceptionHandlerOptions {ExceptionHandlingPath = new PathString("/exception") });通過上面的實現(xiàn)方式我們大概可以猜出擴展方法的幾種類型找到源碼位置ExceptionHandlerExtensions擴展類
public static class ExceptionHandlerExtensions {public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app){return app.UseMiddleware<ExceptionHandlerMiddleware>();}public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, string errorHandlingPath){return app.UseExceptionHandler(new ExceptionHandlerOptions{ExceptionHandlingPath = new PathString(errorHandlingPath)});}public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, Action<IApplicationBuilder> configure){//創(chuàng)建新的執(zhí)行管道var subAppBuilder = app.New();configure(subAppBuilder);var exceptionHandlerPipeline = subAppBuilder.Build();return app.UseExceptionHandler(new ExceptionHandlerOptions{ExceptionHandler = exceptionHandlerPipeline});}public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options){return app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options));} }通過UseExceptionHandler擴展方法我們可以知道這么多擴展方法其實本質(zhì)都是在構(gòu)建ExceptionHandlerOptions我們來看一下大致實現(xiàn)
public class ExceptionHandlerOptions {/// <summary>/// 指定處理異常的終結(jié)點路徑/// </summary>public PathString ExceptionHandlingPath { get; set; }/// <summary>/// 指定處理異常的終結(jié)點委托/// </summary>public RequestDelegate ExceptionHandler { get; set; } }這個類非常簡單,要么指定處理異常信息的具體終結(jié)點路徑,要么自定義終結(jié)點委托處理異常信息。通過上面的使用示例可以很清楚的看到這一點,接下來我們查看一下ExceptionHandlerMiddleware中間件的大致實現(xiàn)
public class ExceptionHandlerMiddleware {private readonly RequestDelegate _next;private readonly ExceptionHandlerOptions _options;private readonly ILogger _logger;private readonly Func<object, Task> _clearCacheHeadersDelegate;private readonly DiagnosticListener _diagnosticListener;public ExceptionHandlerMiddleware(RequestDelegate next,ILoggerFactory loggerFactory,IOptions<ExceptionHandlerOptions> options,DiagnosticListener diagnosticListener){_next = next;_options = options.Value;_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();_clearCacheHeadersDelegate = ClearCacheHeaders;_diagnosticListener = diagnosticListener;//ExceptionHandler和ExceptionHandlingPath不同同時不存在if (_options.ExceptionHandler == null){if (_options.ExceptionHandlingPath == null){throw new InvalidOperationException(Resources.ExceptionHandlerOptions_NotConfiguredCorrectly);}else{_options.ExceptionHandler = _next;}}}public Task Invoke(HttpContext context){ExceptionDispatchInfo edi;try{var task = _next(context);//task未完成情況if (!task.IsCompletedSuccessfully){return Awaited(this, context, task);}return Task.CompletedTask;}catch (Exception exception){edi = ExceptionDispatchInfo.Capture(exception);}return HandleException(context, edi);//處理未完成taskstatic async Task Awaited(ExceptionHandlerMiddleware middleware, HttpContext context, Task task){ExceptionDispatchInfo edi = null;try{//等待完成await task;}catch (Exception exception){//收集異常信息edi = ExceptionDispatchInfo.Capture(exception);}if (edi != null){await middleware.HandleException(context, edi);}}} }通過這段處理我們可以看出所有的異常處理都指向當前類的HandleException方法
private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi) {_logger.UnhandledException(edi.SourceException);// 如果輸出已經(jīng)開始執(zhí)行了,后續(xù)的代碼就沒必要執(zhí)行了,直接重新拋出if (context.Response.HasStarted){_logger.ResponseStartedErrorHandler();edi.Throw();}PathString originalPath = context.Request.Path;//如果指定處理異常的終結(jié)點,將異常處理交給指定的終結(jié)點去處理if (_options.ExceptionHandlingPath.HasValue){//將處理路徑指向,異常處理終結(jié)點路徑context.Request.Path = _options.ExceptionHandlingPath;}try{//清除原有HTTP上下文信息,為了明確指定程序出現(xiàn)異常,防止異常未被處理而后續(xù)當做正常操作執(zhí)行ClearHttpContext(context);//將異常信息包裝成ExceptionHandlerFeature,后續(xù)處理程序獲取異常信息都是通過ExceptionHandlerFeaturevar exceptionHandlerFeature = new ExceptionHandlerFeature(){//異常信息Error = edi.SourceException,//原始路徑Path = originalPath.Value,};//將包裝的ExceptionHandlerFeature放入到上下文中,后續(xù)處理程序可通過HttpContext獲取異常信息context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);//設(shè)置狀態(tài)碼context.Response.StatusCode = 500;context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);//調(diào)用給定的異常處理終結(jié)點處理異常信息await _options.ExceptionHandler(context);//同樣也可以發(fā)送診斷日志,可以利用處理程序返回輸出,診斷日志記錄異常將職責(zé)分離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{//異常處理結(jié)束后,恢復(fù)原始的請求路徑,供后續(xù)執(zhí)行程序能拿到原始的請求信息context.Request.Path = originalPath;}//如果異常沒被處理則重新拋出edi.Throw(); }最后還有一段清除上下文和清除輸出緩存的方法,因為程序一旦發(fā)生了異常,可能創(chuàng)建了新的終結(jié)點,所以執(zhí)行管道會有所調(diào)整,所以需要重新計算。而且異常信息保留輸出緩存是沒有意義的。
private static void ClearHttpContext(HttpContext context) {context.Response.Clear();//因為可能創(chuàng)建了新的終結(jié)點,所以執(zhí)行管道會有所調(diào)整,所以需要重新計算context.SetEndpoint(endpoint: null);var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();routeValuesFeature?.RouteValues?.Clear(); }private static Task ClearCacheHeaders(object state) {//清除輸出緩存相關(guān)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實現(xiàn)方式簡單很多。其大致思路就是捕獲后續(xù)管道執(zhí)行異常,如果存在異常則將異常包裝成ExceptionHandlerFeature,放入到Http上下文中。之所以相對簡單主要原因還是UseExceptionHandler最終處理異常由我們自定義的終結(jié)點去處理,所以它只是負責(zé)包裝異常相關(guān)信息,并將它交于我們定義的異常處理終結(jié)點。
UseStatusCodePages
無論是UseDeveloperExceptionPage還是UseExceptionHandler都是通過捕獲異常的方式去處理異常信息,UseStatusCodePages則是通過Http狀態(tài)碼去判斷是否為成功的返回并進行處理,使用方式如下
app.UseStatusCodePages(); //或 app.UseStatusCodePages("text/plain;charset=utf-8", "狀態(tài)碼:{0}"); //或 app.UseStatusCodePages(async context => {context.HttpContext.Response.ContentType = "text/plain;charset=utf-8";await context.HttpContext.Response.WriteAsync($"狀態(tài)碼:{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($"狀態(tài)碼:{context.HttpContext.Response.StatusCode}"); }}); //或 app.UseStatusCodePages(configure => {configure.Run(async context =>{await context.Response.WriteAsync($"狀態(tài)碼:{context.Response.StatusCode}");}); });接下來我們查看一下UseStatusCodePages擴展方法相關(guān)實現(xiàn)
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);}); }雖然擴展方法比較多,但是本質(zhì)都是組裝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){//初始化StatusCodePagesFeaturevar statusCodeFeature = new StatusCodePagesFeature();context.Features.Set<IStatusCodePagesFeature>(statusCodeFeature);await _next(context);if (!statusCodeFeature.Enabled){return;}//這個范圍外的Http狀態(tài)碼直接忽略,不受程序處理只處理值為400-600之間的狀態(tài)碼if (context.Response.HasStarted|| context.Response.StatusCode < 400|| context.Response.StatusCode >= 600|| context.Response.ContentLength.HasValue|| !string.IsNullOrEmpty(context.Response.ContentType)){return;}//將狀態(tài)信息包裝到StatusCodeContext,傳遞給自定義處理終結(jié)點var statusCodeContext = new StatusCodeContext(context, _options, _next);await _options.HandleAsync(statusCodeContext);} }這個中間件的實現(xiàn)思路更為簡單,主要就是攔截請求判斷Http狀態(tài)碼,判斷是否是400-600,也就是4xx 5xx相關(guān)的狀態(tài)碼,如果符合則包裝成StatusCodeContext,交由自定義的終結(jié)點去處理。
總結(jié)
關(guān)于常用異常處理中間件我們介紹到這里就差不多了,接下來我們總結(jié)對比一下三種中間件的異同和大致實現(xiàn)的方式
UseDeveloperExceptionPage中間件主要工作方式就是捕獲后續(xù)中間件執(zhí)行異常,如果存在異常則將異常信息包裝成ErrorPageModel視圖模型,然后通過這個模型去渲染開發(fā)者異常界面。
UseExceptionHandler中間件核心思路和UseDeveloperExceptionPage類似都是通過捕獲后續(xù)中間件執(zhí)行異常,不同之處在于UseExceptionHandler將捕獲的異常信息包裝到ExceptionHandlerFeature然后將其放入Http上下文中,后續(xù)的異常處理終結(jié)點通過Http上下文獲取到異常信息進行處理。
UseStatusCodePages中間件相對于前兩種中間件最為簡單,其核心思路就是獲取執(zhí)行完成后的Http狀態(tài)碼判斷是否是4xx 5xx相關(guān),如果是則執(zhí)行自定義的狀態(tài)碼攔截終結(jié)點。這個中間件核心是圍繞StatusCode其實并不包含處理異常相關(guān)的邏輯,所以整體實現(xiàn)相對簡單。
最后我們再來總結(jié)下使用中間件的方式和使用IExceptionFilter的方式的區(qū)別
中間件的方式是對整個請求執(zhí)行管道進行異常捕獲,主要是負責(zé)整個請求過程中的異常捕獲,其生命周期更靠前,捕獲異常的范圍更廣泛。畢竟MVC只是Asp.Net Core終結(jié)點的一種實現(xiàn)方式,目前Asp.Net Core還可以處理GRPC Signalr等其它類型的終結(jié)點信息。
IExceptionFilter主要是針對Action執(zhí)行過程中的異常,畢竟終結(jié)點只是中間件的一種形式,所以可處理范圍比較有限,主要適用于MVC程序。對于其它終結(jié)點類型有點無能為力。
以上就是文章的全部內(nèi)容,由于能力有限,如果存在理解不周之處請多多諒解。我覺得學(xué)習(xí)一個東西,如果你能了解到它的工作方式或者實現(xiàn)原理,肯定會對你的編程思路有所提升,看過的代碼用過的東西可能會忘,但是思路一旦形成,將會改變你以后的思維方式。
????歡迎掃碼關(guān)注我的公眾號????
總結(jié)
以上是生活随笔為你收集整理的深入探究ASP.NET Core异常处理中间件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Azure Show】|第三期 人工智
- 下一篇: .NET Core加解密实战系列之——消