天天看點

.NET Core:處理全局異常

一、前言

在程式設計中,我們會遇到各種各樣的異常問題,一個好的異常處了解決方案能夠幫助開發者快速的定位問題,也能夠給使用者更好的使用者體驗。那麼我們在AspNetCore中該如何捕獲和處理異常呢?我們以一個WebApi項目為例,講解如何捕獲和處理異常。

二、異常處理

開發過ASP.NET程式的人都知道:IExceptionFilter。這個過濾器同樣在AspNetCore中也可以用來捕獲異常。不過,對于使用IExceptionFilter,更建議使用它的異步版本:IAsyncExceptionFilter。那麼該如何使用過濾器呢?下面以IAsyncExceptionFilter為例,對于同步版本其實也是一樣的。

我們在項目中添加一個Model檔案夾,存放傳回結果實體類,這裡定義一個泛型類:

我們在項目中添加一個Filter檔案夾,所有的過濾器都放在該檔案夾下面。然後添加一個類:CustomerExceptionFilter,并使該類繼承自IAsyncExceptionFilter。代碼如下:

上面的代碼很簡單,我們建立了一個自定義的異常過濾器,然後在OnExceptionAsync方法中定義自己的處理邏輯,報錯之後依然讓http傳回狀态碼為200,并且将錯誤資訊傳回到用戶端。

然後添加一個控制器,命名為ExceptionFilter,在控制器中模拟發生異常的情況:

最後我們需要把自定義的異常過濾器進行注入,這裡選擇使用全局注入的方式,在Startup類的ConfigureServices方法中進行注入:

然後運作程式,檢視結果:

.NET Core:處理全局異常

如何我們沒有使用過濾器捕獲和處理異常,我們将得到Http狀态碼為500的内部錯誤,這種錯誤不友善定位問題,而且給用戶端傳回的資訊也不夠友好。使用了過濾器處理異常,進行特殊處理之後就會顯得很友好了。

在上面自定義過濾器的代碼中,有下面的一行代碼:

注意:這句代碼很關鍵,當你處理完異常之後,一定要将此屬性更改為true,表示異常已經處理過了,這樣其他地方就不會在處理這個異常了。 

我們知道,AspNetCore的管道模型具有層層傳遞的特點,那麼我們就可以在管道中實作全局異常捕獲。我們新建立一個自定義的異常中間件:

然後在建立一個擴充方法:

最後在Startup類的Configure方法中使用自定義的異常中間件:

然後我們注釋掉上面注冊的異常過濾器,運作程式進行通路:

.NET Core:處理全局異常

這樣也可以捕獲到異常。

 我們首先看下面一段代碼:

這段代碼在我們使用AspNetCore建立一個WebApi項目時就會看到,如果是建立的MVC項目,是下面一段代碼:

這兩段代碼的作用就是捕獲和處理異常,是第一個被添加到管道中的中間件。

UseDeveloperExceptionPage的意思很好了解:對于開發模式,一旦報錯就跳轉到錯誤堆棧頁面。而第二個UseExceptionHandler也很有意思,從它的名字中我們大緻可以猜出它肯定是個錯誤攔截程式。那麼它和上面自定義的異常進行中間件有什麼差別呢?

UseExceptionHandler其實就是預設的錯誤處理。它其實也是一個中間件,它的原名叫做ExceptionHandlerMiddleware。在使用UseExceptionHandler方法時,我們可以選填各種參數。比如上面的第二段代碼,填入了“/Error”參數,表示當産生異常的時候,将定位到對應的路徑,這裡定位的頁面就是“http://localhost:5001/Error”。這是MVC中自帶的一個錯誤頁面,當然,你也可以指定自己定義的一個頁面。

UseExceptionHandler還有一個指定ExceptionHandlerOptions參數的擴充方法,該參數是ExceptionHandlerMiddleware中間件的重要參數:

參數名

說明

ExceptionHandlingPath

重定向的路徑,比如剛才的 ""/Error"" 實際上就是指定的該參數

ExceptionHandler

錯誤攔截處理程式

ExceptionHandler允許我們在ExceptionHandlerMiddleware内部指定咱們自己的異常處理邏輯。而該參數的類型為RequestDelegate類型的委托。是以,UseExceptionHandler提供了一個簡便的寫法,可以讓我們在ExceptionHandlerMiddleware中建立自定義的錯誤攔截管道來處理異常:

三、中間件和過濾器的比較

在上面的例子中,我們分别使用了中間件和過濾器的方式來處理異常,那麼中間件和過濾器有什麼差別呢?兩者的差別:攔截範圍的不同。

.NET Core:處理全局異常

IExceptionFilter作為一種過濾器,它需要在控制器發現錯誤之後将錯誤資訊送出給它處理,是以它的異常處理範圍是控制器内部。如果我們想捕獲進入控制器之前的一些錯誤,IExceptionFilter是捕獲不到的。而對于ExceptionHandlerMiddleware異常中間件來說就很容易了,它作為第一個中間件被添加到管道中,在它之後發生的任何異常都可以捕獲的到。

那麼為什麼要有兩種異常處理的方式呢?隻使用ExceptionHandlerMiddleware中間件處理異常不可以嗎?它可以捕獲任何時候發生的異常,為什麼還要有過濾器呢?如果你想在控制器發生異常時快速捕獲和處理異常,那麼使用過濾器處理異常是非常不錯的選擇。如果是控制器内部發生了異常,首先是由過濾器捕獲到異常,最後才是中間件捕獲到異常。

我們在自定義過濾器的時候有這樣一段代碼:context.ExceptionHandled = true;如果在自定義過濾器中将異常标記為已經處理之後,則第一個異常進行中間件就認為沒有錯誤了,不會進入到處理邏輯中了。是以,如果不把 ExceptionHandled屬性設定為true,可能出現異常處理結果被覆寫的情況。