Web API 是ASP.NET平臺新加的一個特性,它可以簡單快速地建立Web服務為HTTP客戶端提供API。Web API 使用的基礎庫是和一般的MVC框架一樣的,但Web API並不是MVC框架的一部分,微軟把Web API相關的類從 System.Web.Mvc 名稱空間下提取了出來放在 System.Web.Http 名稱空間下。這種理念是把 Web API 作為ASP.NET 平臺的核心之一,以使Web API能使用在其他的Web應用中,或作為一個獨立的服務引擎。本文將先帶大家理解Web API,再教大家在MVC中使用Web API。
本文目錄
理解 REST 和 RESTful Web API
為了更好的理解Web API,先帶大家瞭解一下 REST 和 RESTful Web API。以下內容大多來自維基百科。
REST(全名:Representational State Transfer),中文翻譯是表徵狀態轉移(也有叫表述性狀態轉移),是Roy Fielding博士在2000年他的博士論文中提出來的一種軟體架構風格。
REST 從資源的角度來觀察整個網路,分佈在各處的資源由URI確定,而客戶端的應用通過URI來獲取資源的表徵。獲得這些表徵致使這些應用程式轉變了其狀態。隨著不斷獲取資源的表徵,客戶端應用不斷地在轉變著其狀態,所謂表徵狀態轉移(Representational State Transfer)。
目前使用Web服務的三種主流的方式是:遠端過程呼叫(RPC),面向服務架構(SOA)以及表徵性狀態轉移(REST),其中REST模式的Web服務與複雜的SOA和RPC對比來講顯的更加簡潔,越來越多的web服務開始採用REST風格設計和實現。
需要注意的是,REST是設計風格而不是標準,但REST設計風格常基於使用HTTP,URI,和XML以及HTML這些現有的廣泛流行的協議和標準。REST設計風格有如下要點:
- 資源是由URI來指定。
- 對資源的操作包括獲取、建立、修改和刪除資源,這些操作正好對應HTTP協議提供的GET、POST、PUT和DELETE方法。
- 通過操作資源的表現形式來操作資源。
- 資源的表現形式則是XML或HTML,取決於讀者是機器還是人,是消費web服務的客戶軟體還是web瀏覽器。當然也可以是任何其他的格式,如JSON。
另外,使用REST需要滿足一些要求,如客戶端和伺服器結構、連線協議具有無狀態性、能夠利用Cache機制增進效能等。
RESTful Web API(也稱為RESTful Web服務)是一個使用HTTP並遵循REST原則的Web服務。它從以下請求資源的三個方面進行定義:
- URI,比如:http://example.com/resources/。
- Web服務接受與返回的網際網路媒體型別,比如:JSON,XML ,YAML 等。
- Web服務在該資源上所支援的一系列請求方法(比如:POST,GET,PUT或DELETE)。
本文要講的ASP.NET Web API 就是RESTful Web API的一種。下表列出了在實現 RESTful Web API 時HTTP請求方法的典型用途:
不像基於SOAP的Web服務,RESTful Web服務並沒有“正式”的標準。這是因為REST是一種架構,而SOAP只是一個協議。雖然REST不是一個標準,但在實現RESTful Web服務時可以使用其他各種標準(比如HTTP,URL,XML,PNG等)。
那麼REST和本文要講的ASP.NET API又有什麼關係呢?請繼續往下閱讀。
理解 ASP.NET Web API
ASP.NET Web API(本文簡稱Web API),是基於ASP.NET平臺構建RESTful應用程式的框架。可以說 Web API 就是為在.NET平臺下構建RESTful應用程式而生的,這也是本文要先介紹REST的原因。
Web API基於在 MVC 應用程式中新增的一個特殊的 Controller,這種 Controller 稱為 API Controller,和MVC普通的 Controller 相比它主要有如下兩個不同的特點:
- Action 方法返回的是 Model 物件,而不是ActionResult。
- 在請求時,Action 方法是基於 HTTP 請求方式來選擇的。
第一個不同點很好理解,第二個不同點可能讀者不太理解,一會看完本文的示例就理解了。
從API Controller的Action方法返回給客戶端的Model物件是經過JSON編碼的。API Controller的設計僅是為了提供傳遞Web資料的服務,因此它不支援View、Layout 和其它HTML呈現相關的特性。Web API 能支援任何有Web 功能的客戶端,但最常用的是為Web應用程式中的Ajax請求提供服務。
正如在 ASP.NET MVC 小牛之路]14 - Unobtrusive Ajax 中演示的,我們也可以通過普通的Controller中建立Action方法來返回JSON格式的資料來支援Ajax,但 API controller 的方式可以使得資料相關的Action和View相關的Action分開,並且使得建立只為資料服務的應用更快更簡單。
一般我們會在下面這兩種情況下選擇使用API Controler:
- 需要大量的返回JSON格式資料的Action方法。
- 和HTML無關,只是純粹為資料提供服務。
如果你對上面這些概念還不太理解,沒關係,當你閱讀完本文後再回頭看一下,你會對這些概念理解得更深些。
下面我會通過例子來解釋Web API是如何工作的,它非常簡單,因為很多東西都和我們之前講過的MVC的東西相同。
建立 Web API 應用程式
作為本文的示例,我們建立一個名為 WebServices 的MVC應用程式,選擇Web API模板。在本文的這個例子中,我們建立一個名為 Reservation 的Model,程式碼如下:
namespace WebServices.Models { public class Reservation { public int ReservationId { get; set; } public string ClientName { get; set; } public string Location { get; set; } } }
為這個Model建立一個Repository 介面和它的一個簡單的實現。如果你對 Repository 這個詞不太理解,可以閱讀:[ASP.NET MVC 小牛之路]05 - 使用 Ninject 。為了簡單,我們直接在Models資料夾中新增一個名為 IReservationRepository 的介面,程式碼如下:
namespace WebServices.Models { public interface IReservationRepository { IEnumerable<Reservation> GetAll(); Reservation Get(int id); Reservation Add(Reservation item); void Remove(int id); bool Update(Reservation item); } }
建立一個名為 ReservationRepository 的類,實現 IReservationRepository 介面,程式碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebServices.Models { public class ReservationRepository : IReservationRepository { private List<Reservation> data = new List<Reservation> { new Reservation {ReservationId = 1, ClientName = "Adam", Location = "London"}, new Reservation {ReservationId = 2, ClientName = "Steve", Location = "New York"},new Reservation {ReservationId = 3, ClientName = "Jacqui", Location = "Paris"}, }; private static ReservationRepository repo = new ReservationRepository(); public static IReservationRepository getRepository() { return repo; } public IEnumerable<Reservation> GetAll() { return data; } public Reservation Get(int id) { var matches = data.Where(r => r.ReservationId == id); return matches.Count() > 0 ? matches.First() : null; } public Reservation Add(Reservation item) { item.ReservationId = data.Count + 1; data.Add(item); return item; } public void Remove(int id) { Reservation item = Get(id); if (item != null) { data.Remove(item); } } public bool Update(Reservation item) { Reservation storedItem = Get(item.ReservationId); if (storedItem != null) { storedItem.ClientName = item.ClientName; storedItem.Location = item.Location; return true; } else { return false; } } } }
簡單起見,我們這裡沒有真正實現資料持久化,只是簡單的模擬。
在我們建立好Web API應用程式時,VS已經新增好了一個預設的HomeController和Index.cshtml View。我們不打算在HomeControllerr 的Action中傳遞Model給View,因為一會要在View中使用JavaScript呼叫Web API來獲取所有資料。在一個MVC工程中,你可以自由混合地使用普通Controller和API Controller。
修改 Index.cshtml 如下:
@{ ViewBag.Title = "Index";} @section scripts { <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script> } <div id="summaryDisplay" class="display"> <h4>Reservations</h4> <table> <thead> <tr> <th class="selectCol"></th> <th class="nameCol">Name</th> <th class="locationCol">Location</th> </tr> </thead> <tbody id="tableBody"> <tr><td colspan="3">The data is loading</td></tr> </tbody> </table> <div id="buttonContainer"> <button id="refresh">Refresh</button> <button id="add">Add</button> <button id="edit">Edit</button> <button id="delete">Delete</button> </div> </div> <div id="addDisplay" class="display"> <h4>Add New Reservation</h4> @{ AjaxOptions addAjaxOpts = new AjaxOptions { // options will go here }; } @using (Ajax.BeginForm(addAjaxOpts)) { @Html.Hidden("ReservationId", 0) <p><label>Name:</label>@Html.Editor("ClientName")</p> <p><label>Location:</label>@Html.Editor("Location")</p> <button type="submit">Submit</button> } </div> <div id="editDisplay" class="display"> <h4>Edit Reservation</h4> <form id="editForm"> <input id="editReservationId" type="hidden" name="ReservationId"/> <p><label>Name:</label><input id="editClientName" name="ClientName" /></p> <p><label>Location:</label><input id="editLocation" name="Location" /></p> </form> <button id="submitEdit" type="submit">Save</button> </div>
並把 _Layout.cshtml 中的一些沒用的內容清理一下,刪除 /Content/Site.css 中的樣式後新增如下樣式程式碼:
table { margin: 10px 0;} th { text-align: left;} .nameCol {width: 100px;} .locationCol {width: 100px;} .selectCol {width: 30px;} .display { float: left; border: thin solid black; margin: 10px; padding: 10px;} .display label {display: inline-block;width: 100px;}
到這,我們的程式執行起來是這樣的:
接下來我們需要建立一個API Controller,通過它我們可以用JavaScript程式碼和Repository的內容進行互動。
右擊 Controllers 資料夾,選擇“新增”--"控制器",在彈出的對話方塊中修改Controller的名稱為 ReservationController,並從模板中選擇 Empty API。新增好後,修改 ReservationController 如下:
using System.Collections.Generic; using System.Web.Http; using WebServices.Models; namespace WebServices.Controllers { public class ReservationController : ApiController { IReservationRepository repo = ReservationRepository.getRepository(); public IEnumerable<Reservation> GetAllReservations() { return repo.GetAll(); } public Reservation GetReservation(int id) { return repo.Get(id); } public Reservation PostReservation(Reservation item) { return repo.Add(item); } public bool PutReservation(Reservation item) { return repo.Update(item); } public void DeleteReservation(int id) { repo.Remove(id); } } }
ReservationController的基類是 System.Web.Http.ApiController,也實現了 IController 介面(在[ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)中有介紹),它和普通Controller的基類System.Web.Mvc.Controller 處理請求的方式基本上是一樣的。
到這我們就已經建立好了一個Web API了。
測試 API Controller
我們先來看看建立好的 API Controller 能否工作,下文將通過 API Controller 對資料的處理結果來解釋API Controller 如何工作。
執行程式,URL定位到 /api/reservation。你看到的結果將依賴於你所使用的版本,如果你使用的是IE 10/11,會彈出一個提示儲存檔案的對話方塊,該檔案的內容是以下JSON資料:
[{"ReservationId":1,"ClientName":"Adam","Location":"London"}, {"ReservationId":2,"ClientName":"Steve","Location":"New York"}, {"ReservationId":3,"ClientName":"Jacqui","Location":"Paris"}]
如果你使用的是另外一種瀏覽器,如Chrome或Firefox,瀏覽器將顯示如下XML資料:
<ArrayOfReservation> <Reservation> <ClientName>Adam</ClientName> <Location>London</Location> <ReservationId>1</ReservationId> </Reservation> <Reservation> <ClientName>Steve</ClientName> <Location>New York</Location> <ReservationId>2</ReservationId> </Reservation> <Reservation> <ClientName>Jacqui</ClientName> <Location>Paris</Location> <ReservationId>3</ReservationId> </Reservation> </ArrayOfReservation>
對於看到的結果,我們有兩點感興趣。第一,我們請求 /api/reservation URL時,伺服器返回了Model物件的列表,根據該列表的資料,我們可以推斷呼叫的是ReservationController中的 GetAllReservations Action方法。第二,不同的瀏覽器接收到了不同格式的資料,我們可以猜測是由於不同版本的瀏覽器傳送請求的方式不一樣。
實際上,之所以會有不同格式的資料結果,是因為 Web API 使用HTTP請求報文頭部的Accept資訊來判斷客戶端更願意接收何種型別的資料。IE 接收到JSON格式的資料是因為它傳送的Accept頭部資訊是:
...
Accept: text/html, application/xhtml+xml, */*
...
瀏覽器通過這段報文資訊告訴伺服器它最想要的是 text/html 內容,其次是 application/xhtml+xml。最後的 */* 意思是如果前兩種不滿足,就接收任何型別的資料。
Web API 支援JSON和XML,但它會優先選擇JSON格式。即IE的傳送Accept資訊中的 */* 使得Web API生成了JSON格式的資料。下面是 Chrome 瀏覽器傳送的Accept頭部資訊:
...
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
...
這段資訊的 application/xml 比 */* 優先順序高,所以Web API選擇為Chrome瀏覽器返回XML格式的資料。
對於Web服務,JSON已經開始大幅度替代XML了,因為XML冗長難以處理,尤其是在JavaScript中。
API Controller 如何工作
通過觀察Web API返回結果的資料,我們似乎有點理解API Controller是如何工作的了。如果你再將請求URL改為 /api/reservation/3,你將看到這樣的JSON資料(使用IE):
{"ReservationId":3,"ClientName":"Jacqui","Location":"Paris"}
這時,你可能更加明白了什麼。這時返回的資料是 ReservationId 為 3 的Reservation物件,我們可以推測Web API根據請求的URL(/api/reservation/3)呼叫的是GetReservation這個Action方法。這讓我們想到了之前講過的路由知識[ASP.NET MVC 小牛之路]07 - URL Routing。
API Controller 在 /App_Start/WebApiConfig.cs 中有它們自己的路由配置,你可以開啟該檔案看看VS預設註冊好的路由:
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
API Controller使用的路由配置(WebApiConfig.cs)和普通MVC使用的配置(RouteConfig.cs)是在兩個分開的檔案中,註冊方法接收的引數(HtttpConfiguration 物件)和新增路由配置的方法(MapHttpRoute)也不一樣。原理都是和 [ASP.NET MVC 小牛之路]07 - URL Routing 講的一樣的,這裡不再累述了。
這個預設的Web API路由有一個靜態片段(api),還有controller和 id片段變數。和MVC路由一個關鍵不同點是,它沒有action片段變數。當然也可以和MVC一樣定義action片段變數,你可以閱讀 Routing in ASP.NET Web API 文章來了解更多Web API路由的細節。
當應用程式接收到一個和Web API 路由匹配的請求時,Action方法的呼叫將取決於傳送HTTP請求的方式。當我們用 /api/reservation URL測試API Controller時,瀏覽器指定的是GET方式的請求。API Controller的基類 ApiController根據路由資訊知道需要呼叫哪個Controller,並根據HTTP請求的方式尋找適合的Action方法。
一般約定在Action方法前加上HTTP請求方式名作為字首。這裡字首只是個約定,Web API能夠匹配到任何包含了HTTP請求方式名的Action方法。也就是說,本文示例的GET請求將會匹配 GetAllReservations 和 GetReservation,也能夠匹配 DoGetReservation 或 ThisIsTheGetAction。
對於兩個含有相同HTTP請求方式的Action方法,API Controlller會根據它們的引數和路由資訊來尋找最佳的匹配。例如請求 /api/reservation URL,GetAllReservations 方法會被匹配,因為它沒有引數;請求 /api/reservation/3 URL,GetReservation 方法會被匹配,因為該方法的引數名和URL的 /3 片段對應的片段變數名相同。我們還可以使用 POST、DELETE 和 PUT請求方式來指定ReservationController的其它Action方法。這就是前文提到的REST的風格。
但有的時候為了用HTTP方式名來給Action方法命名會顯得很不自然,比如 PutReservation,習慣上會用 UpdateReservation。不僅用PUT命名不自然,POST也是一樣的。這時候就需要使用類似於MVC的Controller中使用的Action方法選擇器了。在System.Web.Http 名稱空間下同樣包含了一系列用於指定HTTP請求方式的特性,如下所示:
public class ReservationController : ApiController { ... [HttpPost] public Reservation CreateReservation(Reservation item) { return repo.Add(item); } [HttpPut] public bool UpdateReservation(Reservation item) { return repo.Update(item); } ... }
從這我們也看到,在API Controller中使用東西很多都是和MVC中的Controller是一樣的,但它們分別在 System.Web.Http 和 System.Web.Mvc兩個不同的名稱空間下,正如本文開始所說,Web API把MVC中的很多東西抽取出來放在System.Web.Http名稱空間中,以使Web API作為ASP.NET平臺的一個獨立的核心。
使用 JavaScript 和 Web API 互動
在Scripts資料夾下新增一個Home資料夾,在該資料夾下新增一個 Index.js 檔案。在我們寫JS程式碼之前,先把這個JS檔案引用到Index.cshtml中,如下:
... @section scripts { <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script> <script src="~/Scripts/Home/Index.js"></script> } ...
在 Index.js 中先把一些基本的方法寫好,如下:
function selectView(view) { $('.display').not('#' + view + "Display").hide(); $('#' + view + "Display").show(); } function getData() { $.ajax({ type: "GET", url: "/api/reservation", success: function (data) { $('#tableBody').empty(); for (var i = 0; i < data.length; i++) { $('#tableBody').append('<tr><td><input id="id" name="id" type="radio"' + 'value="' + data[i].ReservationId + '" /></td>' + '<td>' + data[i].ClientName + '</td>' + '<td>' + data[i].Location + '</td></tr>'); } $('input:radio')[0].checked = "checked"; selectView("summary"); } }); } $(document).ready(function () { selectView("summary"); getData(); $("button").click(function (e) { var selectedRadio = $('input:radio:checked') switch (e.target.id) { case "refresh": getData(); break; case "delete": break; case "add": selectView("add"); break; case "edit": selectView("edit"); break; case "submitEdit": break; } }); });
我們定義了三個方法。第一個 selectView 方法用於控制div的顯示和隱藏。第二個 getData 方法使用jQuery Ajax通過GET請求/api/reservation URL,並將返回的JSON資料填充到summaryDisplay的table中。第三個是jQuery的ready函式,在頁面內容載入完成時執行。這時的效果如下:
接下來一步一步完美編輯、儲存和刪除的功能。新增“編輯”功能程式碼如下:
... case "edit": $.ajax({ type: "GET", url: "/api/reservation/" + selectedRadio.attr('value'), success: function (data) { $('#editReservationId').val(data.ReservationId); $('#editClientName').val(data.ClientName); $('#editLocation').val(data.Location); selectView("edit"); } }); break; ...
當使用者點選編輯時,先會取得radio button的value值,並以此組成一個URL(如/api/reservation/1),HTTP請求方式(GET)和URL將使API Controller呼叫 GetReservation 方法獲取一個Reservation物件的JSON,並將其填充到editDisplay的文字框中。效果如下:
下面再完善一下刪除和儲存功能。程式碼如下:
... case "delete": $.ajax({ type: "DELETE", url: "/api/reservation/" + selectedRadio.attr('value'), success: function (data) { selectedRadio.closest('tr').remove(); } }); break; ... case "submitEdit": $.ajax({ type: "PUT", url: "/api/reservation/" + selectedRadio.attr('value'), data: $('#editForm').serialize(), success: function (result) { if (result) { var cells = selectedRadio.closest('tr').children(); cells[1].innerText = $('#editClientName').val(); cells[2].innerText = $('#editLocation').val(); selectView("summary"); } } }); break; ...
根據Ajax的DELETE請求,API Controller將呼叫 DeleteReservation 將選中的Reservation物件從集合中刪除。同樣,根據PUT請求API Controller 將呼叫PutReservation方法。
最後完善一下新增功能,該ajax請求使用的是 Unobtrusive Ajax,修改 Index.cshtml 如下:
... <h4>Add New Reservation</h4> @{ AjaxOptions addAjaxOpts = new AjaxOptions { OnSuccess = "getData", Url = "/api/reservation" }; } @using (Ajax.BeginForm(addAjaxOpts)) { @Html.Hidden("ReservationId", 0) <p><label>Name:</label>@Html.Editor("ClientName")</p> <p><label>Location:</label>@Html.Editor("Location")</p> <button type="submit">Submit</button> } ...
Ajax.BeginForm生成的表單預設使用的是POST請求,相應的 API Controller將呼叫PostReservation 方法新增Reservation物件。效果如下:
通過這個完整的示例我們可以看到,熟悉MVC後,使用Web API也非常簡單,操作上基本和MVC類似,主要的不同體現在 ApiController 和 Action方法的匹配上。
參考:《Pro ASP.NET MVC 4 4th Edition》