非常贊的四篇文章:
本篇閱讀目錄:
HTTP 常用方法:
我原先以為修改某一個資源,也是用 POST,后來發現還有一個 PATCH,但發現 HttpClient 并沒有提供此調用方法,需要我們進行擴展:
public static class HttpClientExtensions{ public static async Task<HttpResponseMessage> PatchAsync(this HttpClient client, Uri requestUri, HttpContent iContent) { var method = new HttpMethod("PATCH"); var request = new HttpRequestMessage(method, requestUri) { Content = iContent }; HttpResponseMessage response = new HttpResponseMessage(); try { response = await client.SendAsync(request); } catch (TaskCanceledException e) { Debug.WriteLine("ERROR: " + e.ToString()); } return response; }}
調用代碼:
HttpContent httpContent = new StringContent("Your JSON-String", Encoding.UTF8, "application/json");var responseMessage = await httpClient.PatchAsync(new Uri("testUri"), httpContent);
相關閱讀:You should use camelCase with JSON, but snake_case is 20% easier to read
camelCase(駱駝命名)我們都非常熟悉,因為 C# 就是使用的這個命名法,snake_case(蛇形命名)適用于 python 和 ruby,比如商品 ID,camelCase 會命名為 productId,snake_case 則會命名為 product_id。
需要注意的是,snake_case 只限于 JSON API 命名,并不限于 URI,URI 中一般也不會使用下劃線,為什么要對 JSON API 進行規范命名?因為 RESTful 是無狀態風格,也就是說 RESTful API 并不限于某一種客戶端進行調用,所以 JSON API 的命名必須要規范,如果只是 C# 調用的話,那么命名采用 camelCase 命名就可以了,但顯然并不是這樣,最后得出的結論是使用 snake_case 命名會比較好,以后在設計的時候,需要注意了。
API URI 設計最重要的一個原則:nouns (not verbs!),名詞(而不是動詞)。
CRUD 簡單 URI:
GET /users
- 獲取用戶列表GET /users/1
- 獲取 Id 為 1 的用戶POST /users
- 創建一個用戶PUT /users/1
- 替換 Id 為 1 的用戶PATCH /users/1
- 修改 Id 為 1 的用戶DELETE /users/1
- 刪除 Id 為 1 的用戶上面是對某一種資源進行操作的 URI,那如果是有關聯的資源,或者稱為級聯的資源,該如何設計 URI 呢?比如某一用戶下的產品:
GET /users/1/products
- 獲取 Id 為 1 用戶下的產品列表GET /users/1/products/2
- 獲取 Id 為 1 用戶下 Id 為 2 的產品POST /users/1/products
- 在 Id 為 1 用戶下,創建一個產品PUT /users/1/products/2
- 在 Id 為 1 用戶下,替換 Id 為 2 的產品PATCH /users/1/products.2
- 修改 Id 為 1 的用戶下 Id 為 2 的產品DELETE /users/1/products/2
- 刪除 Id 為 1 的用戶下 Id 為 2 的產品還有一種情況,我們一般在設計 API 的時候,會進行一些查詢操作,比如分頁和排序等,API 方法參數設計可能很容易,那重要的 URI 該如何設計呢?我們先看這樣的一個設計:
[HttpGet][Route("api/wzlinks/users-{spaceUserId}/{pageIndex=1}/{pageSize=20}")]public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int spaceUserId, int pageIndex, int pageSize){.....}
首先,這個 URI 想要表示的意思是:獲取某一用戶下,分頁查詢的網摘列表,這個 API 設計好不好呢?我們看下 GitHub 中的一個 API:
"current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}"
差別是不是很大?而且我們設計的 URI 表達也比較混亂,查詢應該是參數,并且是對 URI 進行的查詢,所以放在 URI 中會不太合適,我們完善下:
[HttpGet][Route("api/users/{space_user_id}/wzlinks")]public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int space_user_id, int page, int per_page){.....}
URI 表達為:獲取 space_user_id 為 1 用戶下的網摘分頁列表,上面設計會不會更好些呢?調用示例:
api.VEVb.com/api/users/1/wzlinks?page=1&per_page=20
GitHub API(規范參考):https://api.github.com
{ "current_user_url": "https://api.github.com/user", "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}", "authorizations_url": "https://api.github.com/authorizations", "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}", "emails_url": "https://api.github.com/user/emails", "emojis_url": "https://api.github.com/emojis", "events_url": "https://api.github.com/events", "feeds_url": "https://api.github.com/feeds", "following_url": "https://api.github.com/user/following{/target}", "gists_url": "https://api.github.com/gists{/gist_id}", "hub_url": "https://api.github.com/hub", "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}", "issues_url": "https://api.github.com/issues", "keys_url": "https://api.github.com/user/keys", "notifications_url": "https://api.github.com/notifications", "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}", "organization_url": "https://api.github.com/orgs/{org}", "public_gists_url": "https://api.github.com/gists/public", "rate_limit_url": "https://api.github.com/rate_limit", "repository_url": "https://api.github.com/repos/{owner}/{repo}", "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}", "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}", "starred_url": "https://api.github.com/user/starred{/owner}{/repo}", "starred_gists_url": "https://api.github.com/gists/starred", "team_url": "https://api.github.com/teams", "user_url": "https://api.github.com/users/{user}", "user_organizations_url": "https://api.github.com/user/orgs", "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}", "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}
補充:因為 space_user_id 違反 C# 的命名規則,Google 搜索“asp.net web api snake_case”,卻搜多到大量的“camelCasing”關鍵字,而且在微軟大部分 WebAPI 示例中,Route 的參數命名設計規范都是 camelCasing,所以。。。。沒辦法,只能使用 camelCasing 命名規則吧,誰讓用的是 .NET 呢,不過,有人還搞了個 SnakeCaseFormUrlEncodedMediaTypeFormatter 擴展,但好像是在過程中進行了轉化,并不是解決定義問題。
網摘的 API 我們再修改下:
[HttpGet][Route("api/users/{spaceUserId}/wzlinks")]public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int spaceUserId, int page, int perPage){.....}
不經意間,還發現 ASP.NET WebAPI Help 一個有意思的地方,比如上面的 API 設計,得到的是這樣的 Help 說明:
如果我們把 API 代碼修改成:
[HttpGet][Route("api/users/{space_user_id}/wzlinks")]public async Task<IEnumerable<WzLinkDTO>> GetPagedList(int spaceUserId, int page, int perPage){.....}
得到的卻是這樣的 Help 說明:
發現有什么不同了嗎?看來 ASP.NET WebAPI Help 還是蠻智能的呢。
新聞熱點
疑難解答