【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,好比C#的小細節,AspnetCore,微服務中的.net知識等等。
5min+不是超過5分鐘的意思,"+"是知識的增長。so,它是讓您花費5分鐘如下的時間來提高您的知識儲備量。html
其實一說到AspNet Core裏面的全局異常,其實你們都不會陌生。由於這玩意兒用的很是頻繁,好的異常處理方案可以幫助開發者更快速的定位問題,也可以給用戶更好的用戶體驗。前端
好比,當您訪問到一個網頁,忽然,它喵的報錯了!您沒有看錯,它報錯了!!!而後顯示了這樣的一個錯誤頁面:程序員
請問,此刻電腦屏幕前的您會什麼感覺。(真想掏出那傳說中的95級史詩巨劍!)json
可是,倘若咱們稍微處理一下這個異常,好比用我們騰訊爸爸的手段,換個皮膚:app
用戶立刻就會想:「哎呀,錯誤就錯誤嘛,孰能無過,程序員鍋鍋也挺辛苦的。」異步
因而可知!!!全局異常的捕獲和處理是有多麼的重要。async
那麼在AspNet Core中咱們該如何捕獲和處理異常呢? 可能不少同窗都知道:IExceptionFilter
。 這個過濾器應該算是AspNet裏面的老牌過濾器了,從很早就延續至今,它容許我們捕獲AspNet Core的控制器中的錯誤。不過,對於使用 IExceptionFilter
,其實我更建議您考慮它的異步版本: IAsyncExceptionFilter
。(別問爲何,問就是愛的供養)。微服務
那麼咱們來看看該過濾器是怎麼使用的呢? 下面以 IAsyncExceptionFilter
爲例,對於同步版本其實也是同樣的:學習
public class MyCustomerExceptionFilter : IAsyncExceptionFilter { public Task OnExceptionAsync(ExceptionContext context) { if (context.ExceptionHandled == false) { string msg = context.Exception.Message; context.Result = new ContentResult { Content = msg, StatusCode = StatusCodes.Status200OK, ContentType = "text/html;charset=utf-8" }; } context.ExceptionHandled = true; //異常已處理了 return Task.CompletedTask; } }
上面我們新建了一個自定義的異常過濾器,代碼很簡單,就是報錯了以後依舊讓Http返回狀態碼爲200的結果。而且將錯誤信息返回到客戶端。網站
而後還須要在 Startup.cs
中,告訴 MVC
我們新加的這個過濾器:
services.AddControllers(options => options.Filters.Add(new MyCustomerExceptionFilter()));
而後就完了,是否是so easy? 來看看結果:
[HttpGet] public IEnumerable<WeatherForecast> Get() { throw new Exception("has error!"); }
若是不增長該過濾器,咱們將獲得Http狀態碼爲500的響應。這對於某些不致命的意外操做來講,有點殺雞用牛刀的感受,對於前端用戶來講也不是很友好(明明輸錯了一個字符,就直接被告知網站崩潰,而且出現喬殿下)。
而我們捕獲了異常,進行特殊處理以後就顯得很友好了。(返回200,而且告訴用戶輸錯了某字符等)。
在上面的代碼中,您會看到有一行 context.ExceptionHandled = true;
。注意!!! 這很關鍵,當您處理完異常以後,請記得將此屬性更改成true,代表異常已經處理過了。若是不更改的話,嘿嘿🤪。會有什麼結果呢? 請看下面↓
因爲AspNet Core管道的層層傳遞的特色,我們就有機會在管道中實現全局異常捕獲。 新建一箇中間件來試試吧:
public class MyExceptionMiddleware { private readonly RequestDelegate _next; public MyExceptionMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception ex) { httpContext.Response.ContentType = "application/problem+json"; var title = "An error occured: " + ex.Message; var details = ex.ToString(); var problem = new ProblemDetails { Status = 200, Title = title, Detail = details }; //Serialize the problem details object to the Response as JSON (using System.Text.Json) var stream = httpContext.Response.Body; await JsonSerializer.SerializeAsync(stream, problem); } } }
而後在 Startup.cs
中,註冊管道:
app.UseMiddleware<MyExceptionMiddleware>();
來看看效果:
仍是原來的味道,仍是熟悉的配方,爽歪歪!
管道的添加順序決定了它的執行順序,因此若是您想擴大異常捕獲的範圍,能夠將該管道放置在 Configure
的第一行。 可是!! 您會發現,這個默認的AspNet Core項目不是已經在第一行弄了一個異常處理麼? 我*&&……&。
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); }
這行代碼你們在初始化新AspNetCore項目時就會看到,也有可能您只有上半段,這和模板有關係。不過這都沒有關係,它的做用就是捕獲和處理異常而已。關於 UseDeveloperExceptionPage
該擴展我們就很少說了,它的意思是:對於開發模式,一旦報錯就會跳轉到錯誤堆棧頁面。 而第二個 UseExceptionHandler
就頗有意思了,從它命名就能夠看出,它確定是個錯誤攔截程序。那麼它和我們自定義的異常處理管道有什麼區別呢?
「不指定確定有個默認吧!」 是的,它就是默認的錯誤處理。因此,它其實也是一箇中間件,它的真身叫作 ExceptionHandlerMiddleware
。在使用 UseExceptionHandler
方法時,咱們能夠選填各類參數。好比上方的代碼,填入了 "/Error"
參數,表示當產生異常的時候,將定向到對應路徑,此處就定位的是: "http://localhost:5001/Error" 。固然您也能夠隨意指定頁面,好比 漂亮的喬殿下頁面。😝
還有指定 ExceptionHandlerOptions
參數的方法,該參數是ExceptionHandlerMiddleware
中間件的重要參數:
參數名 | 說明 |
---|---|
ExceptionHandlingPath | 重定向的路徑,好比剛纔的 ""/Error"" 實際上就是指定的該參數 |
ExceptionHandler | 錯誤攔截處理程序 |
ExceptionHandler
容許咱們在 ExceptionHandlerMiddleware
內部指定我們自有的異常處理邏輯。而該參數的類型爲 RequestDelegate
,是否很眼熟,沒錯,管道處理!所以UseExceptionHandler
提供了一個簡便的寫法,可讓咱們在ExceptionHandlerMiddleware
中又新建自定義的錯誤攔截管道來做爲處理程序:
//in Configure() app.UseExceptionHandler(appbuilder => appbuilder.Use(ExceptionHandlerDemo)); //該內容會在AspNetCore的管道返回結果至ExceptionHandlerMiddleware時,若是中間件捕獲到了異常時調用 private async Task ExceptionHandlerDemo(HttpContext httpContext, Func<Task> next) { //該信息由ExceptionHandlerMiddleware中間件提供,裏面包含了ExceptionHandlerMiddleware中間件捕獲到的異常信息。 var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>(); var ex = exceptionDetails?.Error; if (ex != null) { httpContext.Response.ContentType = "application/problem+json"; var title = "An error occured: " + ex.Message; var details = ex.ToString(); var problem = new ProblemDetails { Status = 500, Title = title, Detail = details }; var stream = httpContext.Response.Body; await JsonSerializer.SerializeAsync(stream, problem); } }
那麼上面兩個方法有什麼區別呢? 回答:攔截範圍。
IExceptionFilter 做爲MVC中間件之間的內容,它須要MVC在發現錯誤以後將錯誤信息提交給它處理,所以它的錯誤處理範圍僅限於MVC中間件。因此,假如咱們須要捕獲MVC中間件以前的一些錯誤,實際上是捕獲不到的。 而對於ExceptionHandlerMiddleware
中間件來講就很簡單了,它做爲第一個中間件,凡是在它以後的全部錯誤它都可以捕得到到。
那麼這麼看來是否IExceptionFilter
就毫無用武之地了呢? 非也,假如您想在MVC發生異常時快速捕獲和處理,使用過濾器實際上是您不錯得選擇,若是您僅僅關心控制器之間的異常,那麼過濾器也是很好的選擇。
還記得剛開始咱們在過濾器中說過的這一行代碼嗎:context.ExceptionHandled = true;
。若是在IExceptionFilter
中將異常標記爲已經處理以後,則第一道異常處理中間件就認爲沒有錯誤了,不會進入處處理邏輯中。因此,若是我們不把該屬性改成 true
,頗有可能出現攔截結果被覆蓋的狀況。
最後,偷偷說一句:創做不易,點個推薦吧.....