之前講了RESTful API的統一資源接口這個限制,裡面提到了資源是通過URI來進行識别的,每個資源都有自己的URI。URI裡還涉及到資源的名稱,而針對資源的名稱卻沒有一個标準來進行規範,但是業界還是有一些最佳實踐的。那麼我們首先看看這些最佳實踐對資源命名是如何建議的。
下面讓我們來看看RESTful API資源命名的一些最佳實踐。
一個資源的URI代表的是一個實際上或概念上存在的東西,是以,它應該是名詞,是以也就不應該出現動詞,動詞應該使用HTTP方法來表達。
需求:我們看這樣一個需求的例子:“我想獲得系統裡所有的使用者”。
常見錯誤做法:你可能把API的URI設計成這樣:api/getusers。這樣的設計是不好的,因為裡面出現了一個動詞get。
分析:這個句話的主要動詞就是“擷取”,而想要擷取的資源(也就是主要的名詞)是“使用者”。
正确的做法:需求裡面主要的動詞應該通過HTTP方法來展現,“擷取”對應的HTTP方法就是GET。而“使用者”這個資源可以用英文user或者users來表示(是否使用複數一直存在争議,兩種方法都行,但你在使用的時候需要保持一緻)。是以正确的uri應該是 GET api/user。
還是上面那個需求:“我想獲得系統裡所有的使用者”。
我們可以把uri設計成 api/u 或者 api/ur。但是這樣設計的話,對API的消費者來說非常的不友好,因為不能直覺的看出來它到底代表的是什麼資源,可能是user,也可能是university。
是以建議的做法是要足夠友好,并且比較簡短,例如:api/users。
假設如果後端API系統裡面有若幹種資源,而使用者這個資源與其它的資源并沒有直接的關系,這樣的話擷取使用者資源的uri應該是 api/users。而不是 api/products/users,也不是api/catalogs/products/users,因為user和product或者catalog沒有直接的關系。
通過id擷取單個使用者的uri應該是:api/users/{userId},而不是api/userid/users。
這樣寫的好處是可以讓API具有很好的可預測性和一緻性。
需求1:系統裡有兩類資源,公司(Company)和員工(Employee),它們倆是包含關系,也就是一個公司包含多個員工。現在我想擷取某個公司下所有的員工資訊。
分析:這裡的主要動詞還是“擷取”,是以我們可以使用HTTP的GET。而這裡的資源有兩個,分别是公司和員工,而且它們是包含關系:一個公司包含多個員工或者說一個公司是一個員工的集合。是以API的URI在設計的時候需要展現這種包含關系。
常見的錯誤做法:如果你想獲得公司這個資源,我想你現在應該不會出錯,uri應該是 api/companies。而想要擷取某個公司下的員工,常見的錯誤做法有:api/employees,api/employees/{companyId}等等。這些設計非常不好是因為它無法展現出Company和Employee之間的結構關系。
建議的做法:需要展現Company和Employee之間的關系,是以uri應該是GET api/companies/{companyId}/employees。這樣做直接展現出了Company和Employee之間的結構關系,而且也展現出了一個Company就是一個Employee的集合體。
需求2:我想擷取某個公司的某個員工資訊。
常見的錯誤做法:api/employees/{employeeId},api/companies/{employeeId}等等。這些做法都無法展現出Company和Employee之間的關系。
建議的做法:api/companies/{companyId}/employees/{employeeId}。
我們經常會遇到這樣的需求,比如擷取按照某個資源排序後的資源,或者按照某些條件過濾後的資源。這時候應該怎對資源進行命名呢?
需求:“我想擷取所有的使用者資訊,并要求結果是按年齡從小到大進行排列的”。
常見錯誤的做法:api/users/orderby/age。之前說了,uri裡面使用的都應該是名詞,如果按照這個uri的結構來看,那麼orderby和age就應該是另外兩個資源,并且users包含orderby,orderby包含age,這顯然是錯誤的。
建議的做法:api/users?orderby=name,這樣設計更合理一些。這裡使用了query string作為查詢參數進行排序。
有一些需求總是無法滿足的達到RESTful的限制。
需求:“我想擷取系統裡所有使用者的數量”。
妥協的做法:我們确實可以先通過 GET api/users來擷取系統裡所有的使用者資訊,然後再算出使用者的數量,但是這樣做也太浪費資源并且效率也太低了。我們也很難使用某個名詞來表示這個需求的資源。例如:api/users/totalamountofuser。這樣的uri按理說就代表着我們将會擷取到一個集合資源,裡面是一堆數字,但針對這個需求,我也沒有特别好的辦法讓uri命名完全符合RESTful的限制,是以針對這個需求,我使用的就是這個uri。
下面我們就來實踐一下。打開之前的項目,并建立CompaniesController:
這裡有6個地方比較關鍵,我們挨個看一下:
RESTful API 或者其它Web API的Controller都應該繼承于 ControllerBase 這個類(點此檢視詳細的官方文檔),而不是Controller這個類。
Controller類繼承于ControllerBase,Controller添加了對視圖的支援,是以它更适合用于處理 MVC Web 頁面,而不是 Web API。但是如果你的Controller需要同時支援MVC Web頁面和Web API,那麼這時候就應該繼承于Controller這個類。
ControllerBase 類提供了很多用于處理 HTTP 請求的屬性和方法。 例如,ControllerBase.CreatedAtAction 傳回 201 狀态代碼。關于ControllerBase的屬性和方法的詳細清單,請檢視官方參考文檔。
[ApiController]。這個屬性是應用于Controller的,它其實并不是強制的,但是它提供了一些幫助,使得Web API的開發體驗更好。詳細教程請點選 [ApiController]的官方文檔。在Controller上面添加了[ApiController]屬性之後,就會啟用以下行為:
要求使用屬性路由(Attribute Routing)。也就是不能通過Startup的Configure方法統一配置路由模闆。這部分的詳細介紹請點選:官方文檔。
自動HTTP 400響應。也就是Action方法傳入的model含有驗證錯誤的時候,自動觸發HTTP 400響應。這部分的詳細介紹請點選:官方文檔。
推斷參數的綁定源。它将會推斷出Action方法的參數到底來自哪個綁定源,例如[FromBody]、[FromForm]等等。這部分的詳細介紹請點選:官方文檔。
Multipart/form-data 請求推斷。使用 [FromForm] 屬性批注操作參數時,[ApiController] 屬性将應用推斷規則,它會推斷 multipart/form-data 為請求的内容類型。這部分的詳細介紹請點選:官方文檔。
錯誤狀态代碼的問題詳細資訊。MVC 會将錯誤結果(狀态代碼為 400 或更高的結果)轉換為狀态代碼為 ProblemDetails 的結果。 ProblemDetails 類型基于 RFC 7807 規範,用于提供 HTTP 響應中計算機可讀的錯誤詳細資訊。這部分的詳細介紹請點選:官方文檔。
我們需要通過構造函數注入ICompanyRepository,并把它存放在一個隻讀的字段裡面。
如果注入的ICompanyRepository的執行個體為null,那麼就抛出一個ArgumentNullException。
想要傳回資料結果,我們需要在Controller裡面添加一個Action方法。我暫時把它的傳回類型寫為IActionResult(詳細介紹請點選官方文檔)。IActionResult裡面定義了一些合約,它們可以代表Action方法傳回的結果。
我暫時隻想把結果序列化為JSON格式并傳回,這裡我new了一個JsonResult(參考文檔),它可以做這項工作。
目前我隻做了這幾項最基本的工作:建立Controller,注入Repository,建立Action方法并傳回結果。下面運作一下看看報了什麼錯:
這是因為GetCompanies這個Action方法并沒有使用屬性路由(Attribute Routing)。關于路由這部分,下一篇文章再介紹。
