以 GET 實現查詢會員 API

當前後端分離後,後端退守 API,使用 .NET Core 實現 Web API 為最常見的功能,本文將以實務上常使用的 查詢會員 為範例,示範如何實作出 GET action 的 Web API。

Version


macOS High Sierra 10.13.4
.NET Core 2.1
Rider 2018.2
Paw 3.1.5

Check API (GET)


Is Member

post000

  1. 使用 GEThttp://localhost:5001/api/exists/Sam 查詢

  2. 回傳

1
2
3
{
exists: "true"
}

若為會員,會回傳 existstrue

Not Member

post001

  1. 使用 GEThttp://localhost:5001/api/exists/Kevin

  2. 回傳

1
2
3
{
exists: "false"
}

若為會員,會回傳 existsfalse

ExistsController.cs

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
using System.Collections.Generic;
using System.Linq;
using Exists.Models;
using Microsoft.AspNetCore.Mvc;

namespace Exists.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ExistsController : ControllerBase
{
private readonly List<Member> _members
= new List<Member> {new Member {Username = "Sam", Password = "1234"}};

// GET api/exists/:username
[HttpGet("{username}")]
public ActionResult<Response> CheckMember(string username)
{

return _members
.Where(IsMember)
.DefaultIfEmpty(DefaultMember())
.Select(Result)
.First();

bool IsMember(Member x) => x.Username == username;

Member DefaultMember()
=> new Member {Username = string.Empty, Password = string.Empty};


Response Result(Member x)
=> new Response {Exists = !string.IsNullOrEmpty(x.Username)};

}
}
}

12 行

1
2
private readonly List<Member> _members 
= new List<Member> {new Member {Username = "Sam", Password = "1234"}};

為簡化起見,就沒從 database 判斷 username 與 password,暫時將資料放在 List 內。

16 行

1
2
[HttpGet("{username}")]
public ActionResult<Response> CheckMember(string username)
  • 使用 HttpGet attribute 描述 LoginMember 使用 GET action
  • 回傳值 Response 自訂型別,被包在 .NET Core 規定的 ActionResult

19 行

1
2
3
4
5
return _members
.Where(IsMember)
.DefaultIfEmpty(DefaultMember())
.Select(Result)
.First();

Where():判斷資料是否在 List

LINQ 的 Where() 相當於 FP 的 filter()

DefaultIfEmpty()Where() 一定可能找不到資料,因此會回傳 null,但 null 相當於癌細胞,只要一使用到 null,就必須到處判斷 null ,非常麻煩,而且只要有一個地方沒判斷到 null,程式就可能在 run-time 出錯。

比較好的方式是在 Where() 之後馬上配上 DefaultIfEmpty() 描述,只要 Where() 找不到資料,就不會回傳 null,而是回傳 default object。

DefaultIfEmpty() 就是 LINQ 支援 Null Object Pattern / Default Object Pattern 的一種實現

Select():我們要回傳的是 Response 自訂型別,而不是 Member 型別,勢必要做轉換,Select() 幫我們將 Member 自訂型別轉成 Result 自訂型別。

LINQ 的 Select() 相當於 FP 的 map()

First():別忘了 Select() 的轉換有個特色:原本是 CollectionSelect() 之後還是 Collection,但我們要的是 Response,因此使用 First() 只取一筆 Response

25 行

1
bool IsMember(Member x) => x.Username == username;

IsMember() Local Function,提供 Where() 的 Predicate Function。

Q : Parameter 使用 x 符合 Clean Code 原則 ?

Local Function 與 Lambda 強調的就是 function 要短要小,且常常配合 Body Expression,若 parameter 名稱取太長,Lambda 就不美了。

由於 Lambda 來自於數學,一般會採用數學 y = f(x) 習慣,使用 xyz 即可。

x 會難閱讀嗎 ? 由於 Local Function 與 Lambda 很短,很容易看到看到其型別定義,所以沒有閱讀與維護的問題。

27 行

1
2
Member DefaultMember()
=> new Member {Username = string.Empty, Password = string.Empty};

DefaultMember() Local Function,主要提供 DefaultIfEmpty() 所需要的 default object。

32 行

1
2
3
4
5
6
Response Result(Member x)
=> new Response

{
Success = !string.IsNullOrEmpty(x.Username),
Username = x.Username
};

Result() Local Function,主要提供 Select() 所需要的 data 與型別轉換。

Conclusion


  • .NET Core 提供了 HttpGet attribute,讓我們簡單的將 Controller 的 Method 提供 Web API 的 GET 服務
  • 使用 LINQ 的 Where()DefaultIfEmpty() 讓我們避開 null 魔咒,不用再到處判斷 null,也就是所謂的 Null Object Pattern 或 Default Object Pattern 的實踐
  • Local Function 與 Lambda 的 parameter 可以打破 OOP 與 Imperative 的習慣,以 xyz 命名即可,因為 Lambda 功能都很單一,且 function 很短,不像 Imperative 會拉很長,因此才需要取有意義的 parameter 名稱

Sample Code


完整的範例可以在我的 GitHub 上找到