本文所需的一些預備知識能夠看這裏: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314.htmlhtml
本文介紹的是使用ASP.NET Core創建Richardson成熟度爲2級的僞RESTful web API, 本文介紹的是GET和POST.git
使用的項目是(右鍵另存爲, 而後把後綴名改成zip): https://images2018.cnblogs.com/blog/986268/201805/986268-20180516191053536-1701412182.jpggithub
首先, 資源應該使用名詞, 它是個東西, 不是動做.web
例如:數據庫
因此資源應該使用的是名詞.編程
若是是非分層結構的資源, 那麼它不該該這樣命名: api/xxx/xxx/users, 而應該使用 api/users.json
若是是單個資源, 不該該這樣 api/id/users, 而應該是 api/users/{userId}.api
(資源名是否複數仍是根據我的習慣吧).安全
例如 api/department/{departmentId}/emoloyees, 這就表示了department (部門)和 員工(employee)以前是主從關係.服務器
而 api/department/{departmentId}/emoloyees/{employeeId}, 就表示了該部門下的某個員工.
而過濾, 排序等不是資源, 因此這樣寫 api/users/orderby/username 是不正確的.
過濾排序這類的參數是能夠做爲查詢參數傳遞進來的, 正確的寫法應該是: api/users?orderby=username.
可是有時候, RPC風格的方法調用很難映射成規範的資源命名, 因此有時能夠打破規範 例如 api/users/{userId}/totalsalaries.
若是使用int型做爲ID的話, 大部分時候是沒有問題的, 可是若是您使用的數據庫的ID是自增整型的, 若是你替換數據庫了, 而後把原有數據遷移到新數據庫了, 那麼現有數據的ID就會發生變化, 那麼至關於全部的資源的地址發生了變化, 這就違反了這個:
資源的URI應該永遠都是同樣的.
因此GUID應該做爲ID來使用. (可是我爲了省事, 仍是使用自增int做爲ID吧😂).
使用GUID做爲主鍵的好處就是:
針對項目裏的Country這個資源, 請參考下面這個列表:
這裏GET能夠理解爲獲取(查詢)資源, POST爲添加資源, PUT爲總體更新資源, PATCH爲局部更新資源, DELETE爲刪除資源.
這裏須要提的是後兩個:
首先須要創建一個CountryController:
注意在CountryController上面標註的[Route]屬性標籤,它的值是整個Controller下全部的Action的路由前綴,能夠寫成固定的地址,也能夠寫成"api/[controller]", 其中[controller]這部分會變成這個Controller的名字,這裏也就是"api/country".
若是使用[controller]的話,若是Controller重構後名字改了,那麼該Controller的路由地址也就是資源的地址也就改了,這樣很很差,因此建議仍是寫成固定的地址不要使用[controller]。
GET 全部的Country:
(AutoMapper的使用方法這裏就不介紹了)
GET 一個Country:
這兩個方法裏返回的都是JsonResult,這看起來沒什麼問題,由於咱們想要的就是JSON格式的結果。以第二個方法爲例,使用POSTMAN測試,若是能查詢到數據:
這是沒有問題的,可是若是查詢一個不存在的資源:
這就有問題了,若是查詢不到資源,那麼返回的應該是404 NOF FOUND 而不是200 OK.
狀態碼是很是重要的,由於只有狀態碼會告訴API的消費者:
下面再列舉一下web API會用到的狀態碼:
200級別,表示成功:
400級別,表示客戶端引發的錯誤:
500級別,服務器錯誤:
回到剛纔的那兩個方法,默認狀況下 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就能夠):
這是一個典型的情景,一個國家包含多個城市,這就是父子關係。
首先看一下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到這個地址 http://localhost:5000/api/countries/{id},
那麼, 若是該id的資源不存在, 則應該返回404;
若是該id的資源存在, 則應該返回409 Conflict.
(POST不是冪等性的, 它沒法屢次請求都產生一樣的結果).
測試一下id的資源不存在的狀況:
在測試一下Id的資源存在的狀況:
仍是404, 這個不行, 因此須要手動處理:
看看結果:
OK, 不管是Id存在的資源仍是不存在的資源都會返回正確的狀態碼.
以前講過如何返回xml的格式, 下面介紹一下如何使用xml格式進行請求, 首先在Startup.cs裏面添加這個:
而後, 須要把請求的Content-Type設爲application/xml:
我就不適用xml數據進行測試了.
此次先到這, 隨後會寫DELETE, UPDATE, PATCH.
本文的源碼地址: https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial