上一篇筆記整理了在 Web API 方法中傳回錯誤訊息的幾種寫法,可是如果要在每個動作方法中包裝錯誤訊息,一來費力,二來容易寫出多種不同的錯誤訊息的格式和處理方式。這時候就可以使用篩選器。同場加映:使用 Elmah 記錄錯誤。
在 ASP.NET MVC 應用程式中,儘管有將 HandleErrorAttribute 加入全域篩選器,像這樣:
但這個預設的錯誤處理篩選器對 Web API 完全沒有作用,因為 HandleErrorAttribute 會傳回 View,故不適用於需要傳回 HttpResponseMessage 的 Web API。
上一篇筆記已經整理了在 Web API 方法中傳回錯誤訊息的幾種寫法,包括使用 HttpResponseMessage、HttpResponseException、HttpError、和 Request.CreateErrorMessage() 等等。可是,如果每次發生錯誤時,都得要在每個動作方法中包裝錯誤訊息,一來費力,二來容易寫出多種不同的錯誤訊息的格式和處理方式。我們總是希望有一致的錯誤處理方式,而且集中寫在一個地方,省些力氣。這時候就可以使用篩選器。
實作 Exception Filter Attribute
實作 exception filter 的主要步驟有二:
程式碼的邏輯很簡單,就只是從 OnException 方法的傳入參數取得錯誤資訊,然後組合成 HttpResponseMessage 物件,以便傳回用戶端。
範例程式中的註解是要提醒一件事:我們可以在這裡篩選特定類型的 exception。也就是說,我們可以寫多個 filter class,每一種篩選器僅處理特定類型的 exception。
所以,上面的範例就是一網打盡的寫法,能夠攔截到所有類型的 exceptions--但只有一種 exception 是例外: HttpResponseException。為什麼?
因為 ASP.NET Web API 在篩選錯誤的過程中會自動排除 HttpResponseException。我想這是因為如果我們在程式中拋出 HttpResponseException,那就表示我們很清楚自己現在要傳回什麼樣的錯誤資訊(包括 HTTP 狀態碼和錯誤訊息)給用戶端;這種情況,ASP.NET Web API 的 exception filter 實在沒有介入的必要。這只是我的猜測。
套用 Exception Filter
寫好我們的篩選類別之後,就著就是將它套用到適當的地方。套用的層級有三種:
底下是套用至特定方法的範例:
套用至類別:
如欲套用至整個應用程式,可以在 Global.asax 的 Application_Start() 方法中註冊我們的篩選器:
或者,如果建立專案時選擇的專案範本是「ASP.NET MVC 4 Web Application」,則亦可寫在 App_Start\WebApiConfig.cs 的 Register 方法中,像這樣:
注意不是 FilterConfig 的 RegisterGlobalFilters 方法喔,就如本文開頭所說的,那個是給 ASP.NET MVC 的 exception filters 用的。
同場加映:使用 Elmah 來記錄錯誤
Elmah 可協助我們記錄與查看 ASP.NET 應用程式的 exception,是個相當好用的套件。既然跟錯誤處理有關,就順便整理進來。
安裝:利用 NuGet 為專案加入 Elmah 套件之後,web.config 會自動加入 Elmah 相關的組態,而且 Visual Studio 會自動開啟一個純文字檔,告訴你 Elmah 已經設置妥當。
接著修改先前的 exception filter 類別:
這樣就行了!當 Web API 發生 exception 時,除了會傳回錯誤訊息至用戶端,Elmah 也會將錯誤記錄下來,而我們也就可以利用如 http://localhost/myapp/elmah.axd 的方式查看錯誤記錄了(如下圖所示)。
小結
整理幾個重點:
在 ASP.NET MVC 應用程式中,儘管有將 HandleErrorAttribute 加入全域篩選器,像這樣:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } }
但這個預設的錯誤處理篩選器對 Web API 完全沒有作用,因為 HandleErrorAttribute 會傳回 View,故不適用於需要傳回 HttpResponseMessage 的 Web API。
上一篇筆記已經整理了在 Web API 方法中傳回錯誤訊息的幾種寫法,包括使用 HttpResponseMessage、HttpResponseException、HttpError、和 Request.CreateErrorMessage() 等等。可是,如果每次發生錯誤時,都得要在每個動作方法中包裝錯誤訊息,一來費力,二來容易寫出多種不同的錯誤訊息的格式和處理方式。我們總是希望有一致的錯誤處理方式,而且集中寫在一個地方,省些力氣。這時候就可以使用篩選器。
實作 Exception Filter Attribute
實作 exception filter 的主要步驟有二:
- 建立一個 filter 類別,此類別要繼承自 System.Web.Http.Filters.ExceptionFilterAttribute。然後改寫 OnException 方法,並在此方法中建立欲回傳給用戶端的回應結果。
- 套用我們的 filter 類別。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http.Filters; using System.Net; using System.Net.Http; namespace WebApiDemo.Filters { public class WebApiExceptionFilterAttribute : ExceptionFilterAttribute { public override void OnException(HttpActionExecutedContext context) { // // 你也可以用 if (context.Exception is XyzException) 來篩選特定類型的錯誤. // string msg = "Exception captured by WebApiExceptionFilter: " + context.Exception.GetBaseException().Message; var resp = context.Request.CreateErrorResponse(HttpStatusCode.BadRequest, msg); context.Response = resp; } } }
程式碼的邏輯很簡單,就只是從 OnException 方法的傳入參數取得錯誤資訊,然後組合成 HttpResponseMessage 物件,以便傳回用戶端。
範例程式中的註解是要提醒一件事:我們可以在這裡篩選特定類型的 exception。也就是說,我們可以寫多個 filter class,每一種篩選器僅處理特定類型的 exception。
所以,上面的範例就是一網打盡的寫法,能夠攔截到所有類型的 exceptions--但只有一種 exception 是例外: HttpResponseException。為什麼?
因為 ASP.NET Web API 在篩選錯誤的過程中會自動排除 HttpResponseException。我想這是因為如果我們在程式中拋出 HttpResponseException,那就表示我們很清楚自己現在要傳回什麼樣的錯誤資訊(包括 HTTP 狀態碼和錯誤訊息)給用戶端;這種情況,ASP.NET Web API 的 exception filter 實在沒有介入的必要。這只是我的猜測。
套用 Exception Filter
寫好我們的篩選類別之後,就著就是將它套用到適當的地方。套用的層級有三種:
- 方法層級:套用至 Web API Controller 類別中的特定動作方法。
- 類別層級:套用至 Web API Controller 類別。
- 應用程式層級:套用至整個應用程式。
底下是套用至特定方法的範例:
[Filters.WebApiExceptionFilter] [HttpGet] public Customer Demo0(int id) { throw new Exception("Parameter id must be specified!"); }
套用至類別:
[Filters.WebApiExceptionFilter] public class ErrorDemoController : ApiController { // ...略... }
如欲套用至整個應用程式,可以在 Global.asax 的 Application_Start() 方法中註冊我們的篩選器:
protected void Application_Start() { GlobalConfiguration.Configuration.Filters.Add(new Filters.WebApiExceptionFilterAttribute()); //...其他初始化動作... }
或者,如果建立專案時選擇的專案範本是「ASP.NET MVC 4 Web Application」,則亦可寫在 App_Start\WebApiConfig.cs 的 Register 方法中,像這樣:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Filters.Add(new Filters.WebApiExceptionFilterAttribute()); //...略... } }
注意不是 FilterConfig 的 RegisterGlobalFilters 方法喔,就如本文開頭所說的,那個是給 ASP.NET MVC 的 exception filters 用的。
同場加映:使用 Elmah 來記錄錯誤
Elmah 可協助我們記錄與查看 ASP.NET 應用程式的 exception,是個相當好用的套件。既然跟錯誤處理有關,就順便整理進來。
安裝:利用 NuGet 為專案加入 Elmah 套件之後,web.config 會自動加入 Elmah 相關的組態,而且 Visual Studio 會自動開啟一個純文字檔,告訴你 Elmah 已經設置妥當。
接著修改先前的 exception filter 類別:
public class WebApiExceptionFilterAttribute : ExceptionFilterAttribute { public override void OnException(HttpActionExecutedContext context) { // // 你也可以用 if (context.Exception is XyzException) 來篩選特定類型的錯誤. // string msg = "Exception captured by WebApiExceptionFilter: " + context.Exception.GetBaseException().Message; var resp = context.Request.CreateErrorResponse(HttpStatusCode.BadRequest, msg); context.Response = resp; // 加上這行: Elmah.ErrorSignal.FromCurrentContext().Raise(context.Exception); } }
這樣就行了!當 Web API 發生 exception 時,除了會傳回錯誤訊息至用戶端,Elmah 也會將錯誤記錄下來,而我們也就可以利用如 http://localhost/myapp/elmah.axd 的方式查看錯誤記錄了(如下圖所示)。
小結
整理幾個重點:
- ASP.NET MVC 預設的 HandleErrorAttribute 對 Web API 沒作用,因為 HandleErrorAttribute 的一個特性是會傳回 View,這對 Web API 不適用。
- 可針對特定類型的 exception 做相同的處理(行為一致),並減輕寫程式的負擔(因為不用在每個 action method 中自己包錯誤訊息)。
- 實作 Web API 的 exception filter 時,我們的類別要繼承自 ExceptionFilterAttribute,並改寫 OnException 方法。OnException 方法的傳入參數中包含了 Exception、Request、Response 等物件,方便我們取得錯誤資訊,並建立回傳給用戶端的回應結果。
- 篩選器可套用至整個應用程式(全域)、特定 Controller 類別、或者 Controller 類別中的特定方法。
- 錯誤篩選機制可攔截到所有類型的 exception,唯一的例外是 HttpResponseException。
參考資料
- Exception Handling in ASP.NET Web API by Mike Wasson, March 12, 2012
- 如何在 ASP.NET Web API 套用 ELMAH 錯誤紀錄模組 by 保哥, Jan 1, 2013
沒有留言: