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。