Web API 回傳資料的方式

這篇是要稍微記一下 Web API 回傳資料的幾種方式與格式, 會想要記這一篇是因為以前碰過的 Web API 專案都有統一的回傳 (response) 格式, 但是最近碰到一些專案的 Web API 在回傳資料時是沒有規範的, 例如直接就 return true, 所以就稍微整理一下回傳資料的幾種方式與優缺點.

直接回傳物件

這個就是上面說的, 直接像 return anyObject 這樣回傳, 如下面的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public bool Get()
{
return true;
}

public DemoModel Get()
{
var data = new DemoModel()
{
Name = "Ron",
Age = 18
};

return data;
}

public Dictionary<string,string> Get()
{
var data = new Dictionary<string, string>()
{
["Name"] = "Ron",
["Age"] = "18"
};

return data;
}

優缺點

優點

  • 簡單直覺又方便

缺點

  • 凌亂不統一, 回傳的資料可能出現原生型別, model 或是其他各種型別
  • 只回傳資料, 沒有任何狀態類的欄位與 http status, 當 client 端收到空物件或例外時難以判斷問題
  • 後期需要加狀態時很麻煩, 例如每支 API 各自加狀態欄位容易亂, 如果要抽共用還要考慮資料的結構或階層被改變對客戶端的影響

使用時機

這種做法適合用在

  • 簡單的小專案, 複雜度小就沒太大影響
  • Web API 只提供內部使用, client 跟 API 是同一組人開發的, 狀態類欄位的必要性不大

套用共用回傳物件與狀態

同時考慮回傳資料, http status 與例外內容三種情況, 如下

IHttpActionResult 是 Web API 2 的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ResponseModel<T>
{
public string Code { get; set; } = "0000";

public string Message { get; set; } = "Success";

public T Data { get; set; }
}

// Web API
public HttpResponseMessage Get(bool isError)
{
var responseModel = new ResponseModel<DemoModel>();

if (isError)
{
responseModel.Code = "0001";
responseModel.Message = "Prameter 'isError' should be false.";
var response = Request.CreateResponse(HttpStatusCode.BadRequest, responseModel);
throw new HttpResponseException(response);
}

responseModel.Data = new DemoModel()
{
Name = "Ron",
Age = 18
};

return Request.CreateResponse(HttpStatusCode.OK, responseModel);
}

// Web API 2
public IHttpActionResult Get(bool isError)
{
var responseModel = new ResponseModel<DemoModel>();

if (isError)
{
responseModel.Code = "0001";
responseModel.Message = "Prameter 'isError' should be false.";
return Content(HttpStatusCode.BadRequest, responseModel);
}

responseModel.Data = new DemoModel()
{
Name = "Ron",
Age = 18
};

return Ok(responseModel);
}

特別的 HttpResponseException

上面例子中, HttpResponseException 是一個很特別的例外, 這個例外不會被 ExceptionFilterAttribute, ExceptionHandler 或是 ExceptionLogger 所攔截, 所以在 service 層中使用 HttpResponseException 是一個很好的選項, 既能直接返回 http status 和內容, 又不會被全域例外處理機制攔截, 像是這樣 throw new HttpResponseException(response).

順帶一提, ExceptionHandler 和 ExceptionLogger 是實測的時候才發現會忽略 HttpResponseException, 官方文件只提到 ActionFilterAttribute 的部分, 節錄如下:

An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception. The HttpResponseException type is a special case, because it is designed specifically for returning an HTTP response.

優缺點

優點

  • 明確回傳 http status, 對 client 端來說遇到問題容易釐清方向
  • 細部狀態碼 (Code) 與訊息 (Message) 方便 client 端對接與使用

缺點

  • 比較麻煩, 需要花時間了解並選擇適合的 http status, 也需要定義細部狀態碼 (Code) 與訊息 (Message)

共用回傳物件的幾種設計

回傳物件的共用部分要怎麼設計也是有幾種方式

泛型版
包一層 ResponseModel<T>, 可以直接套用到所有需要回傳物件的 API 上, 但是因為多包了一層, 序列化的結果會多一層, 如果套用在舊的 API 上會使得回傳資料的階層被改變, 會影響所有 client 端.

1
2
3
4
5
6
7
8
public class ResponseModel<T>
{
public string Code { get; set; } = "0000";

public string Message { get; set; } = "Success";

public T Data { get; set; }
}

不用泛型
沒有泛型, ResponseModel 讓其他要回傳的 model 繼承或包含, 如果套用在舊的 API 上, 在序列化後只會增加欄位, 不會改變舊有欄位的階層, 對現存 API 的擴展很友善, 但是相對於泛型版比較不好用, 因為即使只是要回傳一個基本型別(bool / string) 也需要建立 model.

1
2
3
4
5
6
public class ResponseModel
{
public string Code { get; set; } = "0000";

public string Message { get; set; } = "Success";
}

回傳物件型別有兩種使用 ResponseModel 的方法, 繼承或包含, 序列化結果不同, 視情境或團隊共識使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 繼承
public class DemoModel : ResponseModel
{
public string Name { get; set; }

public int Age { get; set; }
}

// 包含
public class DemoModel
{
public string Name { get; set; }

public int Age { get; set; }

public ResponseModel Error {get; set; }
}

使用時機

這種做法適合用在

  • 邏輯複雜或是有各種驗證狀態與結果的 API
  • 需要對外開放的 API (跨部門 / 公開 API)

結論

雖然稍微整理一下幾種做法與適用情境, 不過實際運用不太會照抄, 還是要視情況做細部的調整, 就跟設計模式在使用的時候常常需要配合專案需求變形過一樣.

參考

Best practice to return errors in ASP.NET Web API
Exception Handling in ASP.NET Web API