Web API屬於ASP.NET核心平台的一部分,它利用MVC框架的底層功能方便我們快速的開發部署WEB服務。我們可以在常規MVC應用通過添加API控制器來創建web api服務,普通MVC應用程序控制器根據用戶請求的action方法返回ActionResult,而web api服務返回的則是json封裝的模型數據。
在開始下面的內容前先給出相關類與接口的代碼:
public interface IReservationRepository { IEnumerable<Reservation> GetAll(); Reservation Get(int id); Reservation Add(Reservation item); void Remove(int id); bool Update(Reservation item); } public class Reservation { public int ReservationId { get; set; } public string ClientName { get; set; } public string Location { get; set; } } 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; } } }
創建API控制器
在已有的MVC WEB應用中添加API控制器來創建WEB服務,VS的添加控制器對話框中可以選擇創建API控制器,我們可以選擇“Empty API controller”創建不包含任何方法的空API控制器,手工添加對應各個WEB服務操作的方法,一個完整的API控制類類似:
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); } } }
當我們從瀏覽器訪問 /api/reservation時得到的GetAllReservations方法封裝的JSON數據,在IE10中得到的結果類似:
[{"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>
這種區別源於瀏覽器提交的Accept頭,IE10發送的Accept頭類似:
...
Accept: text/html, application/xhtml+xml, */*
...
表示IE10優先接受 text/html,接下來是application/xhtml+xml,如果前兩者不可行, */*表示接受任何格式。Web API支持XML和JSON兩種格式,但是優先使用JSON,所以IE10得到的*/*選擇的JSON格式。Chrome發送的Accept頭類似:
...
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
...
Chrome優先接受application/xml,所以WEB API使用XML格式發送數據。
理解API控制器如何作用
在IE10中如果訪問/api/reservation/3,得到的JSON數據類似:
{"ReservationId":3,"ClientName":"Jacqui","Location":"Paris"}
這里得到的ReservationIdvalue=3的對象數據,這和MVC的路徑映射很相似,實際上確實也是這樣,API有自己的的路徑映射,定義在 WebApiConfig.cs文件中:
using System.Web.Http; namespace WebServices { public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
這里的路徑映射非常類似於MVC控制器的路徑映射,但是所用的路徑映射表和配置類都來自於System.Web.Http命名空間,Microsoft在這個命名空間復制了MVC的對應的類,這樣做使得我們可以脫離MVC單獨使用WEB API。這里注冊的API路徑映射最后在global.asax中通過WebApiConfig.Register(GlobalConfiguration.Configuration)注冊到全局配置文件中。
和MVC控制器通過URL選擇action方法不同,API控制器根據HTTP請求方法的不同來選擇API控制器方法。API控制器方法的命名規則一般是HTTP方法作為前綴加上控制器的名稱,比如GetReservation(這只是常規做法,DoGetReservation、ThisIsTheGetAction都是允許的),我們從瀏覽器訪問/api/reservation所用的HTTP方法為GET,API控制器會查找所有包含GET的所有控制器方法,GetReservation和GetAllReservations都在考慮之類,但是具體選擇哪個還參考了所帶的參數,訪問/api/reservation沒有任何參數,因此API控制器選擇了GetAllReservations,訪問/api/reservation/3自然就選擇了GetReservation。由此我們也知道PostReservation、PutReservation、DeleteReservation分別對應HTTP的Post、Put、Delete三種方法(WEB API的Representation State Transfer - REST)。
PutReservation方法命名多少有點不自然,我們更習慣稱呼它為UpdateReservation,和MVC一樣,System.Web.Http也提供一些特性可以應用於控制器方法:
public class ReservationController : ApiController { IReservationRepository repo = ReservationRepository.getRepository(); public IEnumerable<Reservation> GetAllReservations() { return repo.GetAll(); } public Reservation GetReservation(int id) { return repo.Get(id); } [HttpPost] public Reservation CreateReservation(Reservation item) { return repo.Add(item); } [HttpPut] public bool UpdateReservation(Reservation item) { return repo.Update(item); } public void DeleteReservation(int id) { repo.Remove(id); } }
這里通過HttpPost特性指定CreateReservation對應HTTP的POST請求,HttpPut指定UpdateReservation對應HTTP的PUT請求,MVC也有類似的特性,但是注意它們雖然同名但是定義在System.Web.Http命名空間。
使用WEB API
如同常規WEB服務,我們可以有多種方式來調用WEB API,比如windows form程序、其他ASP.NET應用程序,這里給出如何在當前MVC應用中利用javascript腳本來使用web api。
視圖文件index.cshtml:
@{ ViewBag.Title = "Index";} @section scripts { <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script> <script src="~/Scripts/Home/Index.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 { 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> } </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>
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": $.ajax({ type: "DELETE", url: "/api/reservation/" + selectedRadio.attr('value'), success: function (data) { selectedRadio.closest('tr').remove(); } }); break; case "add": selectView("add"); break; 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; 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; } }); });
第一次訪問index視圖HTML界面裝載完成后調用JS函數selectView("summary"),它顯示ID=summaryDisplay的DIV塊,隱藏其他的addDisplay、editDisplay塊,然后通過調用JS函數getData(),getData使用GET方法向WEB API請求數據,返回的數據每個項目一行在表格中。summaryDisplay底部有Refresh、Add、Edit、Delete四個按鈕,這些按鈕的點擊在“$("button").click(function (e)”處理;點擊Refresh時調用getdata刷新數據;點擊add時隱藏其他DIV塊,顯示addDisplay DIV塊,這個DIV塊創建一個AJAX表單,POST方法提交到API控制器的CreateReservation;EDIT按鈕根據當前的選項從/api/reservation/{id} GET相應的對象后顯示editDisplay DIV塊,同時隱藏其他DIV塊;點擊editDisplay DIV塊中的submitEdit按鈕,JS使用PUT方法請求/api/reservation/{id}調用API控制器的UpdateReservation方法修改數據,完成后再次顯示summaryDisplay DIV塊,隱藏其他DIV塊;點擊delete按鈕則是使用DELETE方法請求/api/reservation/{id}調用控制器方法DeleteReservation刪除對象,完成后刪除summaryDisplay DIV塊中的相應行。
以上為對《Apress Pro ASP.NET MVC 4》第四版相關內容的總結,不詳之處參見原版 http://www.apress.com/9781430242369。
