1. 程式人生 > >用ASP.NET Core 2.0 建立規範的 REST API -- GET 和 POST

用ASP.NET Core 2.0 建立規範的 REST API -- GET 和 POST

前綴 cat 編輯 derby 發生 就是 media 展現 targe

本文轉自:https://www.cnblogs.com/cgzl/archive/2018/05/23/9047626.html

本文所需的一些預備知識可以看這裏: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314.html

本文介紹的是使用ASP.NET Core建立Richardson成熟度為2級的偽RESTful web API, 本文介紹的是GET和POST.

使用的項目是(右鍵另存為, 然後把後綴名改為zip): https://images2018.cnblogs.com/blog/986268/201805/986268-20180516191053536-1701412182.jpg

RESTful API 資源 (Resource) 的命名指導規範

首先, 資源應該使用名詞, 它是個東西, 不是動作.

例如:

  • api/getusers 就是不正確的.
  • GET api/users 就是正確的
  • GET api/users/{userId}.

所以資源應該使用的是名詞.

如果是非分層結構的資源, 那麽它不應該這樣命名: api/xxx/xxx/users, 而應該使用 api/users.

如果是單個資源, 不應該這樣 api/id/users, 而應該是 api/users/{userId}.

(資源名是否復數還是根據個人習慣吧).

命名應該可以體現資源的結構

例如 api/department/{departmentId}/emoloyees

, 這就表示了department (部門)和 員工(employee)之前是主從關系.

api/department/{departmentId}/emoloyees/{employeeId}, 就表示了該部門下的某個員工.

而過濾, 排序等不是資源, 所以這樣寫 api/users/orderby/username 是不正確的.

過濾排序這類的參數是可以作為查詢參數傳遞進來的, 正確的寫法應該是: api/users?orderby=username.

但是有時候, RPC風格的方法調用很難映射成規範的資源命名, 所以有時可以打破規範 例如 api/users/{userId}/totalsalaries.

應該使用什麽類型作為ID

如果使用int型作為ID的話, 大部分時候是沒有問題的, 但是如果您使用的數據庫的ID是自增整型的, 如果你替換數據庫了, 然後把原有數據遷移到新數據庫了, 那麽現有數據的ID就會發生變化, 那麽相當於所有的資源的地址發生了變化, 這就違反了這個:

資源的URI應該永遠都是一樣的.

所以GUID應該作為ID來使用. (但是我為了省事, 還是使用自增int作為ID吧??).

使用GUID作為主鍵的好處就是:

  • 可以切換數據庫
  • 一定層度上隱藏了內部實現細節

通過HTTP方法與資源交互

針對項目裏的Country這個資源, 請參考下面這個列表:

技術分享圖片

這裏GET可以理解為獲取(查詢)資源, POST為添加資源, PUT為整體更新資源, PATCH為局部更新資源, DELETE為刪除資源.

這裏需要提的是後兩個:

  • HEAD: 和GET差不多, 但是它不應該返回響應的body, 所有沒有響應的payload. 它主要使用來獲取資源的一些信息, 例如查看資源是否可用等.
  • OPTIONS: 它是用來查詢某個資源URI的可交互方式有哪些, 換句話說就是, 使用它可以知道某個URI是否可以執行GET或者POST動作, 這些結果通常是在響應的Headers裏面而不是body裏, 所以也沒有響應的payload.

建立Controller

首先需要建立一個CountryController:

技術分享圖片

註意在CountryController上面標註的[Route]屬性標簽,它的值是整個Controller下所有的Action的路由前綴,可以寫成固定的地址,也可以寫成"api/[controller]", 其中[controller]這部分會變成這個Controller的名字,這裏也就是"api/country".

如果使用[controller]的話,如果Controller重構後名字改了,那麽該Controller的路由地址也就是資源的地址也就改了,這樣很不好,所以建議還是寫成固定的地址不要使用[controller]。

GET 資源

GET 所有的Country:

技術分享圖片

AutoMapper的使用方法這裏就不介紹了

GET 一個Country:

技術分享圖片

這兩個方法裏返回的都是JsonResult,這看起來沒什麽問題,因為我們想要的就是JSON格式的結果。以第二個方法為例,使用POSTMAN測試,如果能查詢到數據:

技術分享圖片

這是沒有問題的,但是如果查詢一個不存在的資源:

技術分享圖片

這就有問題了,如果查詢不到資源,那麽返回的應該是404 NOF FOUND 而不是200 OK.

狀態碼

狀態碼是非常重要的,因為只有狀態碼會告訴API的消費者:

  • 請求是否如預期的成功,或者失敗
  • 如果出現了錯誤,誰該為這個錯誤負責

下面再列舉一下web API會用到的狀態碼:

200級別,表示成功:

  • 200 - OK
  • 201 - Created,表示資源創建成功了
  • 204 - No content,成功執行,但是不應該返回任何東西

400級別,表示客戶端引起的錯誤:

  • 400 - Bad request,表示API的消費者發送到服務器的請求是錯誤的
  • 401 - Unauthorized,表示沒有權限
  • 403 - Forbidden,表示用戶驗證成功,但是該用戶仍然無法訪問該資源
  • 404 - Not found,表示請求的資源不存在
  • 405 - Method not allowed,這就是當我們嘗試發送請求給某個資源時,使用的HTTP方法卻是不允許的,例如使用POST api/countries, 而該資源只實現了 GET,所以POST不被允許
  • 406 - Not acceptable,這裏涉及到了media type,例如API消費者請求的是application/xml格式的media type,而API只支持application/json
  • 409 - Conflict,表示該請求無法完成,因為請求與當前資源的狀態有沖突,例如你編輯某個資源數據以後,該資源又被其它人更新了,這時你再PUT你的數據就會出現409錯誤;有時也用在嘗試創建資源時該資源已存在的情況。
  • 415 - Unsupported media type,這個和406正好返回來,比如說我向服務器提交數據的media type是xml的,而服務器只支持json,那麽就會返回415
  • 422 - Unprocessable entity,表示請求的格式沒問題,但是語義有錯誤,例如實體驗證錯誤。

500級別,服務器錯誤:

  • 500 - Internal server error,這表示是服務器發生了錯誤

回到剛才的那兩個方法,默認情況下 JsonResult會返回200 OK狀態碼,可以去修改JsonResult以支持其它的狀態碼。但是Controller裏提供了一些幫助方法返回IActionResult並指定特定的狀態碼,針對200,就是Ok()方法。

技術分享圖片

這時就不需要手動返回JsonResult了。

這裏需要註意的是,針對集合的內容協商,如果集合是空的,也不應該返回404,因為這個Country資源是存在的,只不過它的內容是空的而已。

然後看一下GET 特定單個資源:

技術分享圖片

針對單個資源,如果沒有找到,就需要返回404 Not Found,這時就可以使用Controller的幫助方法 NotFound().

技術分享圖片

處理異常

當Action發生異常的時候,默認情況下ASP.NET Core會返回500:

技術分享圖片

技術分享圖片

但還是自己處理一下比較好,可以在Action裏面使用try catch:

技術分享圖片

這裏由於是服務器的錯誤,所以應該返回500狀態碼 Internal Server Error。

註意這裏不應該返回Exception,因為這是程序的內部實現細節,再說它對客戶來說也沒什麽用。

技術分享圖片

此外,我們還可以全局處理異常。

在Startup裏面的Configure方法:

技術分享圖片

使用app.UseExceptionHandler(),裏面可以自定義一些邏輯。如果這地方代碼比較多的話,可以把它封裝成一個擴展方法,然後使用app.Usexxx的形式調用。

回頭我把Action裏面的try catch去掉試試,但是這裏要註意把環境變量ASPNETCORE_ENVIRONMENT的值改成Production(其實不是Development就可以):

技術分享圖片

GET 父子關系的資源

這是一個典型的情景,一個國家包含多個城市,這就是父子關系。

首先看一下domain model:

技術分享圖片

技術分享圖片

這個應該很簡單。

此外還要建立CityResource,Repository和IRepository,註冊配置,種子數據等等,這些就不貼了。

下面建立CityController

技術分享圖片

前面提到過,針對父子、主從關系的資源,其子資源的路由地址應該是上面這樣的,由於該Controller下所有的Action的路由前綴都是一樣的,所以把這個路由放到了Controller級別作為所有Action的前綴。

而GET方法本身比較簡單,沒什麽說的,裏面涉及的一些方法請自行編寫。

看看運行結果:

技術分享圖片

如果找不到Country,則返回404:

技術分享圖片

下面GET 單個city:

技術分享圖片

註意,單個資源找不到就應該返回404,而空集合怎不是,這個前面也提過。

找到資源的結果:

技術分享圖片

找不到country或者city的時候都應該返回404,就不貼圖了。

內容協商

簡單來說就是,如果資源支持多種展現格式,那麽消費者可以選擇它想要的格式

這裏就要用到media type,它可以通過請求的Accept Header來傳遞,常見的有:

application/json 和 application/xml...等等

在沒有指定Accept Header的情況下,就該返回一個默認的格式,在ASP.NET Core 2.0裏面就是application/json。

當請求的media type不可用的時候,並且消費者不支持默認格式,這時服務器就應該返回 406 Not Acceptable 狀態碼。

ASP.NET Core 支持輸出和輸入兩種格式化器

輸出的media type在accept header裏面,而輸入的media type在content-type header裏面

看一下當前的情況,請求的Accept Header為application/json時:

技術分享圖片

請求的Accept Header為application/xml時:

技術分享圖片

它們返回的都是json格式的。

因為服務器(項目)現在不支持xml,所以返回了默認的json格式,但嚴格來說,這樣做不正確,所以需要處理一下。

在Startup裏,ConfigureServices方法:

技術分享圖片

把這個ReturnHttpNotAcceptable屬性設為true,如果想要的格式不支持,那麽就會返回406 Not Acceptable:

技術分享圖片

不指定Accept Header的情況下就返回默認的json格式:

技術分享圖片

下面,為項目添加Xml輸出格式的支持:

技術分享圖片

再試試:

技術分享圖片

這時就成功的返回了xml。

創建資源

首先了解一下方法的安全性和冪等性。

安全性是指方法執行後並不會改變資源的表述。

冪等性是指方法無論執行多少次都會得到同樣的結果。

下面是HTTP方法的安全性和冪等性列表:

技術分享圖片

參考這個列表可以幫助決定在某種情況下用哪種HTTP方法。

下面看看創建Country的代碼:

技術分享圖片

這個代碼很簡單,數據是從請求的body帶進來的。

需要註意的是返回什麽,如果POST操作執行成功的話,標準的做法是返回201 Created 狀態碼。

在這裏就可以使用CreatedAtRoute() 這個方法,它允許響應裏帶著Location Header,在這個Location Header裏包含著一個uri,通過這個uri就可以GET到我們剛剛創建好的資源(Country)。

這個方法的第一個參數是一個路由名,使用這個路由名可以用來生成剛才提到的uri。在本例裏,這個路由名應該對應的是GetCountry這個Action方法,所以為這個Action添加路由名:

技術分享圖片

這樣就和Post方法返回中用到的路由名一致了,第二個參數是一個匿名類裏面有個屬性id,它會編程路由裏的參數,最後一個參數是響應會返回的數據。

下面進行測試,發送請求的時候別忘了設置Content-type為applicaiton/json:

技術分享圖片

然後是數據:

技術分享圖片

然後發送請求,查看響應的body部分:

技術分享圖片

再看響應的header:

技術分享圖片

這裏可以看到Location Header的uri,通過這個uri,你就可以GET到這個剛剛創建的Country資源,這裏我就不測試了。

如果再次執行這個POST操作,看看結果:

技術分享圖片

這次返回的數據的id為6,與前面不一樣,所以POST不是冪等的,它每次執行後的結果是不一樣的。

創建子資源

Country的創建做完了,現在可以創建City了。

技術分享圖片

這個跟上面的差不多,只不過註意需要一下路由的參數即可。

測試:

技術分享圖片

同時創建父子資源

這是個常見的需求,一個Country和它下屬的Cities同時被傳遞進來,然後在Action裏一同創建。

首先需要修改CountryAddResource:

技術分享圖片

然後,就沒有然後了,所有的映射操作都交給AutoMapper和EntityFramework Core了。。

測試:

技術分享圖片

然後GET這兩個Cities:

技術分享圖片

創建集合資源

這次我要一次性添加一個集合的Countries。

由於Country的集合相當於是另外一種資源,所以可以把它放到單獨的Controller裏面,不放也沒問題。

技術分享圖片

這個其實也沒什麽特別的,註意傳進來的參數是IEnumerable。為了方便,暫時先返回OK()。

測試:

技術分享圖片

OK, 下面解決返回的問題.

我們要返回的是CreatedAtRoute方法, 由於裏面要包含可以返回該集合資源的路由地址, 所以需要創建一個Action, 它的參數應該是POST方法返回數據的Id的集合. 但是由於路由參數不支持集合形式, 只能以字符串形式傳遞, 所以可以做成這樣的路由參數: api/xx/(1,2,3,4,5).

而Action方法呢, 接受的參數應該是Id的集合, 應該是一個集合類型, 所以我們可以使用ModelBinder把id字符串轉化為id的集合:

技術分享圖片

然後, 還需要對應這個POST Action 做一個GET集合的Action 方法:

技術分享圖片

這個Action所期待的參數類型是Id的集合, 而實際傳入的是id的字符串, 通過ArrayModelBinder來實現轉化.

最後修改POST方法的返回:

技術分享圖片

測試一下:

技術分享圖片

然後再GET這個鏈接:

技術分享圖片

OK

如果POST到單個資源的地址

如果POST到這個地址 http://localhost:5000/api/countries/{id},

那麽, 如果該id的資源不存在, 則應該返回404;

如果該id的資源存在, 則應該返回409 Conflict.

(POST不是冪等性的, 它無法多次請求都產生同樣的結果).

測試一下id的資源不存在的情況:

技術分享圖片

在測試一下Id的資源存在的情況:

技術分享圖片

還是404, 這個不行, 所以需要手動處理:

技術分享圖片

看看結果:

技術分享圖片

OK, 無論是Id存在的資源還是不存在的資源都會返回正確的狀態碼.

支持輸入其它類型的Content-Type

之前講過如何返回xml的格式, 下面介紹一下如何使用xml格式進行請求, 首先在Startup.cs裏面添加這個:

技術分享圖片

然後, 需要把請求的Content-Type設為application/xml:

技術分享圖片

我就不適用xml數據進行測試了.

這次先到這, 隨後會寫DELETE, UPDATE, PATCH.

本文的源碼地址: https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial

用ASP.NET Core 2.0 建立規範的 REST API -- GET 和 POST