相關的一篇文章:RESTful API URI 設計的一些總結。
問題場景:判斷一個資源(Resources)是否存在,URI 該如何設計?
應用示例:判斷 id 為 1 用戶下,名稱為 windows 10 的產品是否存在?
如果這個問題出現在 MVC 項目中,我想我們一般會這樣設計:
public class PRoductService{ public async Task<bool> IsExist(int userId, string productName) { .... }}
看來沒什么問題,的確也沒什么問題,那如果把這部分代碼搬到 asp.net WebAPI 項目中實現,會是怎樣呢?我們來看一下:
public class ProductsController : ApiController{ [HttpGet] [Route("api/products/isexist/{userId}/{productName}")] public async Task<bool> IsExist(int userId, string productName) { ... }}
我想你應該發現一些問題了,這種寫法完全是 MVC 的方式,但并不適用于 WebAPI,主要有三個問題:
對于上面的三個問題,我們分別來探討下。
首先,我們知道在 REST API 中,URI 代表的是一種資源,它的設計要滿足兩個基本要求,第一名詞而非動詞,第二要能清晰表達出資源的含義,換句話說就是,從一個 URI 中,你可以很直接明了的知道訪問的資源是什么,我們再來看我們設計的 URI:
api/products/isExist/{userId}/{productName}
這是什么鬼???這種設計完全違背 URI 原則,首先,我們先梳理一下,我們想要請求的資源是什么?沒錯,是產品(Products),但這個產品是某一個用戶下的,所以用戶和產品有一個上下級關系,訪問產品首先得訪問用戶,這一點要在 URI 中進行體現,其次,我們是獲取產品?還是判斷產品是否存在?這個概念是不同的,產品的唯一標識和用戶一樣,都是 id,在 URI 的一般設計中,如果要訪問某一唯一標識下的資源(比如 id 為 1 的 product),會這樣進行設計:api/products/{id}
,HttpClient 請求中會用 HttpGet 方法(api/products/1),這樣我們就可以獲得一個 id 為 1 的 product,但現在的場景是,獲取產品不通過唯一標識,而是通過產品名稱,難道我們要這樣設計:
api/products/{productName}
咋看之下,這樣好像設計也沒什么問題,但總覺得有些不對勁,比如如果再加一個產品大小,難道要改成這樣:api/products/{productName}/{productSize}
,這種設計完全是不恰當的,上面說到,URI 代表的是一種資源,通過 URI 獲取資源的唯一方式是通過資源的唯一標識,除此之外的獲取都可以看作是對資源的查詢(Query),所以,針對我們的應用場景,URI 的設計應該是這樣(正確):
api/users/{userId}/products: api/users/1/products?productName=windows 10
上面的 URI 清晰明了的含義:查詢 id 為 1 用戶下名稱為 windows 10 的產品。
對于 IsExist 的命名,如果沒有很強的強迫癥,其實也是可以接受的,因為 WebAPI 的 URI 并不會像 MVC 的 Route 設計那樣,在訪問的時候,URL 一般會默認 Action 的名字,所以,在 WebAPI Action 設計的時候,會在 Action 前面加一個 Route 屬性,用來配置 URI,也就是說每一個 Action 操作會對應一個 URI 請求操作,這個請求操作也就是 HTTP 的常用方法。
如果我們想把 IsExist 改掉,那用什么命名會好些呢?先回憶一下,我們在使用 Visual Studio 創建 ASP.NET WebAPI 項目的時候,VS 會自動創建一些示例 Action,我們看看能不能從那里得到一些線索:
public class ValuesController : ApiController{ // GET api/values public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 public string Get(int id) { return "value"; } // POST api/values public void Post([FromBody]string value) { } // PUT api/values/5 public void Put(int id, [FromBody]string value) { } // DELETE api/values/5 public void Delete(int id) { }}
上面是 Values 資源的一些 Action 實現,我們可以看到,Action 的命名和 HTTP 方法一樣,比如 Get 就是 Get,而不是 GetById,Get 是動詞,表示它對資源的一種操作,具體是通過什么進行操作?在參數中可以很直觀的進行反應,一般會在 HelpPage 中進行注釋說明。
IsExist 的含義還是判斷資源是否存在,其本質上來說就是去獲取一個資源,也就是 Get 操作,所以,在 WebAPI Action 中對此的命名,我們直接使用 Get 會好一下,或者使用 Exist。
bool 一般是用在項目方法中的返回值,如果用在 HTTP 請求中,就不是很恰當了,我先貼出幾篇文章:
上面除去第一篇文章,其他文章都是在討論:檢查一個資源是否存在,REST API 該如何設計(HTTP status code)?客戶端獲取服務的響應不是通過 bool,而是通過 HTTP 狀態碼,主要設計三個:404、204 和 200:
204 和 404 有所不同的是,204 表示資源存在,但是為空,404 代表的是原始資源本身就不存在,并且通過唯一標識查詢不到,而 204 更多的是表示,在一定條件下的資源不存在,但可以通過唯一標識查詢到,所以如果資源不存在返回 204 不恰當,wikipedia 中 200 和 404 詳細說明:
HTTP status code 簡要說明:
在上面文章中,有一段很有意思的對話(請略過翻譯):
搜刮到的一張 HTTP status code 示意圖(點擊查看大圖):
針對一開始的應用問題,最終完善代碼:
public class ProductsController : ApiController{ [HttpGet] [Route("api/users/{userId}/products")] public async Task<HttpResponseMessage> Get(int userId, string productName) { if (true) { return Request.CreateResponse(HttpStatusCode.OK); } else { return Request.CreateResponse(HttpStatusCode.NotFound); } }}public class WebApiTest{ [Fact] public async Task Exists_Product_ByProductName() { using (var client = new HttpClient()) { client.BaseAddress = new System.Uri(Base_Address); var response = await client.GetAsync("/api/users/1/products?productName=windows 10"); //var requestMessage = new HttpRequestMessage(HttpMethod.Head, "/api/users/1/products?productName=windows 10"); //var response = await client.SendAsync(requestMessage);//更好的調用方式,只請求HEAD。 Console.WriteLine(response.StatusCode); Assert.True(response.IsSuccessStatusCode); } }}
新聞熱點
疑難解答