ASP.NET Web API Exception Filter

上一篇筆記整理了在 Web API 方法中傳回錯誤訊息的幾種寫法,可是如果要在每個動作方法中包裝錯誤訊息,一來費力,二來容易寫出多種不同的錯誤訊息的格式和處理方式。這時候就可以使用篩選器。同場加映:使用 Elmah 記錄錯誤。

在 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 的主要步驟有二:
  1. 建立一個 filter 類別,此類別要繼承自 System.Web.Http.Filters.ExceptionFilterAttribute。然後改寫 OnException 方法,並在此方法中建立欲回傳給用戶端的回應結果。
  2. 套用我們的 filter 類別。
底下是一個 filter 類別的範例,我把它放在專案的 Filters 目錄下。

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。
參考資料

Post Comments

技術提供:Blogger.