[Web API] 如何讓 Web API 統一回傳格式以及例外處理

[Web API] 如何讓 Web API 統一回傳格式以及例外處理

前言

當我們在開發 Web API 時,一般的情況下每個 API 回傳的數據型態或格式都不盡相同,如果你的項目從頭到尾都是由你一個人獨力完成,那也許還可以說聲「阿密陀佛」,但如果是有其他人需要和你共享你的 Api ,而回傳的數據格式又不一樣,相信是會增加使用者的困擾,也大大增加了程序的複雜度與維護上的難度。所以本篇也紀錄一下自己在實作上的經驗,一方面留個紀錄也希望幫助更多人,廢物不多說我們開始吧!

瞭解架構並實作

原本在找數據時找到這篇 使用Asp.Net MVC打造Web Api (16) – 統一輸入/出格式以及異常處理策略,不過發現裏面的所用到的方法似乎是 For ASP.NET MVC,而非 Web API (不知道筆者這樣認知有沒有錯誤,如果有還麻煩前輩們指教),而本篇的思考模式跟這篇是一樣的,只是把它改成 Web API 能用的方法而已。

[]

按照上圖所示當使用者請求不同的 API 時,返回頁面之前會將數據重新打包後再傳回頁面給使用者,如此一來用戶所看到的數據格式就會是固定的。

1.所以首先我們需要先自定義一個 Model 來當作我們的包裝的容器,其類別的定義如下:

public class ApiResultModel
{
    public HttpStatusCode Status { get; set; }
    public object Data { get; set; }
    public string ErrorMessage { get; set; }
}

2.相信寫過 ASP.NET MVC 的朋友一定會知道,一般我們會將一些在 Action 中固定的邏輯,利用 Filter 來套用到每一個 Action 上面,例如:Authorize。如果你對 Filter 不是很熟悉可以參考一下網絡上前輩所寫的文章:[VS2010] ASP.NET MVC with Action Filters。 所以這邊我們也需要使用同樣的技巧來重新打包我們回傳的數據格式,我們先新增一個 ApiResultAttribute.cs 的檔案,且繼承 System.Web.Http.Filters.ActionFilterAttribute,並且複寫 OnActionExecuted 的方法,如下:

public class ApiResultAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
       public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
       {
            base.OnActionExecuted(actionExecutedContext);     
       }
}

3.而 OnActionExecuted 會在 Action 執行之後呼叫,也表示我們將資料送進這個方法裏面,接着處理我們主要打包的程序邏輯,程序代碼如下:

public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
    base.OnActionExecuted(actionExecutedContext);
 
    ApiResultModel result = new ApiResultModel();
 
    // 取得由 API 返回的狀態代碼
    result.Status = actionExecutedContext.ActionContext.Response.StatusCode;
    // 取得由 API 返回的資料
    result.Data = actionExecutedContext.ActionContext.Response.Content.ReadAsAsync<object>().Result;
    // 重新封裝回傳格式
    actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(result.Status, result);
}

4.而爲了要讓所有的 Web API 都能套用我們自定義的 Filter,所以我們需要到 App_Start → WebApiConfig.cs → Register 來註冊全局的 Web API Filter。(注意:若要註冊 Web API 的 Filter 需在 WebApiConfig.cs 中註冊,而非 FilterConfig.cs 中)

config.Filters.Add(new ApiResultAttribute());

5.重新建置之後我們再重新執行一次原先的 Web API 程序,就會看到回傳的格式已經變成我們自定義的格式了:

"Status": 200,
    "Data": [
        {
            "Account": "taxi",
            "Mark": "",
            "Name": "王大明",
            "Telephone": "0986540123",
            "AccountStatus": true
        },
        {
            "Account": "taxi2",
            "Mark": "",
            "Name": "方大同",
            "Telephone": "0922335111",
            "AccountStatus": true
        },
        {
            "Account": "q121234567",
            "Mark": null,
            "Name": "0000",
            "Telephone": "0972334334",
            "AccountStatus": true
        }
    ],
    "ErrorMessage": null

例外處理

前面我們已經將訊息打包成我們要的格式了,不過我們還沒確切地去處理有關例外的程序代碼,一般當程序發生錯誤產生例外時,我們當然也希望接收端能知道程序發生錯誤,進而顯示該顯示的訊息,而不是活生生地看着程序 Crash 或是停頓,這樣將帶給你的客戶不好的體驗,而在 ASP.NET MVC 中也有提供專門處理例外的 ExceptionFilterAttribute,所以接着來看看該如何打包我們的例外訊息吧。

1.新增一個 ApiErrorHandleAttribute.cs 並且繼承 System.Web.Http.Filters.ExceptionFilterAttribute,接着複寫 OnException 當例外發生時執行的方法,程序代碼如下:

public class ApiErrorHandleAttribute : System.Web.Http.Filters.ExceptionFilterAttribute
{
       public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
       {
           base.OnException(actionExecutedContext);
       }
}

2.透過 OnException 的方法能讓我們捕捉當例外發生時要處理的事情,一般系統我們也會在這邊將發生錯誤的時間、登入的用戶以及錯誤的狀況記錄下來 (例如:系統事件、存入數據庫、寫入 .txt 檔 … 等),不過這邊不是我們討論的重點,我們先來看看該如何打包我們的例外訊息,程序代碼如下:

public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
    base.OnException(actionExecutedContext);
    
    // 取得發生例外時的錯誤訊息
    var errorMessage = actionExecutedContext.Exception.Message;
 
    var result = new ApiResultEntity()
    {
        Status = HttpStatusCode.BadRequest,
        ErrorMessage = errorMessage
    };
 
    // 重新打包回傳的訊息
    actionExecutedContext.Response = actionExecutedContext.Request
        .CreateResponse(result.Status, result);
}

 

3.而因爲程序丟出例外後會先回到 OnActionExcuted 在進到例外的處理,所以我們稍微修改一下原本的 OnActionExcuted 這個方法,讓發生例外時就直接跳過不再這邊打包我們的訊息,程序代碼如下:

public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
    // 若發生例外則不在這邊處理
    if (actionExecutedContext.Exception != null)
    return;
    
    base.OnActionExecuted(actionExecutedContext);
 
    ApiResultModel result = new ApiResultModel();
 
    // 取得由 API 返回的狀態代碼
    result.Status = actionExecutedContext.ActionContext.Response.StatusCode;
    // 取得由 API 返回的資料
    result.Data = actionExecutedContext.ActionContext.Response.Content.ReadAsAsync<object>().Result;
    // 重新封裝回傳格式
    actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(result.Status, result);
}

3.接着我們一樣要將此自定義的 Filter 註冊到程序當中,所以一樣到 App_Start → WebApiConfig.cs → Register 來註冊我們的 Filter:

config.Filters.Add(new ApiErrorHandleAttribute());

4.重新建置後,當我們程序發生例外時也會依照我們的格式回傳給使用者:

{
    "Status": 400,
    "Data": null,
    "ErrorMessage": "嘗試以零除。"
}

總結

就這樣我們又成功解決了一個簡單的案例,不過這邊也需要提醒一下讀者,一般在處理例外這邊是不會直接將例外訊息回傳給用戶的,因爲如果假設你今天丟出的例外有包含了一些比較敏感的信息,例如:數據庫名稱或數據表名稱…等等,這樣一來你的程序就間接的有了漏洞了,所以如果真的要用此程序代碼記得後面例外捕捉那邊還要在包裝一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章