Web API從MVC4開始出現,可以服務於Asp.Net下的任何web應用,本文將介紹Web api在單頁應用中的使用。什么是單頁應用?Single-Page Application最常用的定義:一個最初內容只包含html和JavaScript,后續操作通過Restful風格的web服務傳輸json數據來響應異步請求的一個web應用。SPA的優勢就是少量帶寬,平滑體驗,劣勢就是只用JavaScript這些平滑的操作較難實現,不像MVC應用,我們可以異步form,partview。不用擔心,我們有利器:knockoutjs。
一、工程准備
1.新建一個Api工程。
2.創建模型和倉庫
Reservation:
public class Reservation { public int ReservationId { get; set; } public string ClientName { get; set; } public string Location { get; set; } }
ReservationRespository:在真實的項目中,倉庫都需要有接口和依賴注入,但是這里我們把重點放到后,將這個部分簡化。所以也沒有用數據庫,全部放到內存里面。

public class ReservationRespository { private static ReservationRespository repo = new ReservationRespository(); public static ReservationRespository Current { get { return repo; } } private List<Reservation> data = new List<Reservation> { new Reservation { ReservationId = 1, ClientName = "Adam", Location = "Board Room"}, new Reservation { ReservationId = 2, ClientName = "Jacqui", Location = "Lecture Hall"}, new Reservation { ReservationId = 3, ClientName = "Russell", Location = "Meeting Room 1"}, }; public IEnumerable<Reservation> GetAll() { return data; } public Reservation Get(int id) { return data.Where(r => r.ReservationId == id).FirstOrDefault(); } 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; } } }
用來為我們的單頁應用提供數據的增刪改查。
3.用Nuget添加Juqery,Bootstrap,Knockoutjs。
Install-Package jquery –version 1.10.2 Install-Package bootstrap –version 3.0.0 Install-Package knockoutjs –version 3.0.0
4.創建Api控制器。
public class WebController : ApiController { private ReservationRespository repo = ReservationRespository.Current; public IEnumerable<Reservation> GetAllReservations() { return repo.GetAll(); } public Reservation GetReservation(int id) { return repo.Get(id); } [HttpPost] public Reservation PostReservation(Reservation item) { return repo.Add(item); } [HttpPut] public bool PutReservation(Reservation item) { return repo.Update(item); } public void DeleteReservation(int id) { repo.Remove(id); } }
二、Web Api
這個時候運行訪問 /api/web ,chrome下是xml數據,而ie下是json數據,這是因為Web api會根據請求中的http header 的接收類型來返回客戶端“喜歡”的格式。
Web api的路由和普通的mvc路由略有不同,它是可以根據請求的操作類型(get,post,delete)以及參數來匹配對應的方法。
public static void Register(HttpConfiguration config) { // Web API 配置和服務 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
如果你還是想使用api/{controller}/{action}/{id}這樣的方式,加個action就好
config.Routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
WebApi的一些基本原理和操作大家可以移步 小牛之路 webapi 這里我就不再贅述了。 該文也詳細的介紹了使用ajax和Ajax.BeginForm的方法來進行交互。
三、Knockout
SPA將更多的任務移到了瀏覽器上,這樣就需要保存應用的狀態,需要可以更新的數據模型,一系列用戶可以通過UI元素觸發的邏輯操作。這樣就意味着需要一個微型的MVC模式。而微軟為此提供的類庫就是Knockout,准確的說,Knockout是MVVM模式,下面就用它完成一個簡單的應用。
1.在Shared文件夾中新增_Layout.cshtml,並引用相關類庫。
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/bootstrap.css" rel="stylesheet" /> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.10.2.min.js"></script> <script src="~/Scripts/bootstrap.min.js"></script> <script src="~/Scripts/knockout-3.0.0.js"></script> </head> <body> @RenderSection("Body") </body> @RenderSection("Scripts",false) </html>
2.添加HomeController。添加Index視圖。
public class HomeController : Controller { public ViewResult Index() { return View(); } }
控制器不需要其他的視圖和邏輯處理,因為這些都交給瀏覽器去處理了。
Knockout的感覺和WPF很像,簡單的說,就是定義好模型和事件,綁定到元素上面就行了。我們先實現所有數據的讀取和刪除。
1)定義模型
var model = { reservations: ko.observableArray() };
ko.observableArray()相當於是集合.如果是單個模型,用ko.observable("")。例如:name: ko.observable("")。而reservations相當於是model的一個屬性。
2)定義異步方法。
function sendAjaxRequest(httpMethod, callback, url) { $.ajax("/api/web" + (url ? "/" + url : ""), { type: httpMethod, success: callback }); }
繼而定義一個獲取數據和刪除數據的方法
function getAllItems() { sendAjaxRequest("GET", function (data) { model.reservations.removeAll(); for (var i = 0; i < data.length; i++) { model.reservations.push(data[i]);//一個個添加進來 } }); } function removeItem(item) { sendAjaxRequest("DELETE", function () { getAllItems(); }, item.ReservationId); }
這兩個方法只和數據相關,不用我們去關心dom的處理。
3)應用綁定。
$(document).ready(function () { getAllItems(); ko.applyBindings(model); });
全部腳本:

@section Scripts{ <script> var model = { reservations: ko.observableArray() }; function sendAjaxRequest(httpMethod, callback, url) { $.ajax("/api/web" + (url ? "/" + url : ""), { type: httpMethod, success: callback }); } function getAllItems() { sendAjaxRequest("GET", function(data) { model.reservations.removeAll(); for (var i = 0; i < data.length; i++) { model.reservations.push(data[i]); } }); } function removeItem(item) { sendAjaxRequest("DELETE", function () { getAllItems(); }, item.ReservationId); } $(document).ready(function () { getAllItems(); ko.applyBindings(model); }); </script> }
4)綁定到元素
綁定的表達式是
data-bind="type: expression"
綁定到一個集合:
<tbody data-bind="foreach: model.reservations">
綁定到對象的屬性
<td data-bind="text: ReservationId"></td>
綁定到一個事件
<button class="btn btn-xs btn-primary" data-bind="click: removeItem">Remove</button>
那全部的html的如下:

@section Body { <div id="summary" class="section panel panel-primary"> @*@Html.Partial("Summary", Model)*@ <div class="panel-body"> <table class="table table-striped table-condensed"> <thead> <tr><th>ID</th><th>Name</th><th>Location</th><th></th></tr> </thead> <tbody data-bind="foreach:model.reservations"> <tr> <td data-bind="text:ReservationId"></td> <td data-bind="text:ClientName"></td> <td data-bind="text:Location"></td> <td> <button class="btn btn-xs btn-primary" data-bind="click:removeItem"> Remove </button> </td> </tr> </tbody> </table> </div> </div> }
運行起來:
點擊Remove馬上刪除。平滑的異步體驗。但是我們看removeitem方法就有點奇怪,是刪除之后又調用了一次getAllitems()來更新數據,那么我們也可以直接更新model
... function removeItem(item) { sendAjaxRequest("DELETE", function () { for (var i = 0; i < model.reservations().length; i++) { if (model.reservations()[i].ReservationId == item.ReservationId) { model.reservations.remove(model.reservations()[i]); break; } } }, item.ReservationId); } ...
但上面的KO語法有點奇怪,model.reservations()[i].ReservationId,調用集合中的某個對象時,要像函數一樣調用。KO試圖去維持標准的JavaScript語法,但還有些奇怪的地方,初次使用有些困惑,那也只能說,你會很快習慣的。
當然還有同學有疑問了。這和@Razor的方式有什么不同?主要有三個不同。
1.模型數據不再包含在html元素中了。換句話說就是在html加載之后,瀏覽器才使用異步請求更新數據。用F12打開,看不到任何數據。只有綁定的表達式。
2.當視圖被渲染的時候,數據在瀏覽器端得到處理,而不是在服務器上。
換做是Razor語法,上面的語句就是這樣:
<tbody> @foreach (var item in Model) { <tr> <td>@item.ReservationId</td> <td>@item.ClientName</td> <td>@item.Location</td> <td> @Html.ActionLink("Remove","Remove",new{id=item.ReservationId},new{@class="btn btn-xs btn-primary"}) </td> </tr> } </tbody>
這都是在服務器端由Razor引擎渲染。
3.綁定的數據是“活”的,意味着數據模型的改變會馬上反應到有foreach和text綁定的內容中,F12,進入Console執行:model.reservations.pop() 取出一條數據,我們發現數據馬上更新了。
所以Web api只需要提供基本的增刪改查就行了。而且在前端也讓程序員少寫了很多dom操作,而dom操作是腳本最為繁瑣和容易出錯的地方。接下來繼續完善這個demo。我們加入編輯部分。修改model如下
var model = { reservations: ko.observableArray(), editor: { name: ko.observable(""), location: ko.observable("") } };
增加了一個editor,包含name和Location兩個屬性。
修改sendAjaxRequest 方法 增加傳輸的數據對象。
function sendAjaxRequest(httpMethod, callback, url, reqData) { $.ajax("/api/web" + (url ? "/" + url : ""), { type: httpMethod, success: callback, data: reqData }); }
增加一個編輯的方法:
function handleEditorClick() { sendAjaxRequest("POST", function (newItem) { model.reservations.push(newItem); }, null, { ClientName: model.editor.name, Location: model.editor.location }); }
這個方法的意思是,當提交的時候,用post方式將editor的name和Location傳遞給api,web api會根據post方式自動匹配到PostReservation方法(MVC的模型綁定會自動講這兩個值生成一個Reservation對象)。然后將返回的Reservation對象添加到model中。這個時候頁面就會更新。
html:
<div id="editor" class="section panel panel-primary"> <div class="panel-heading"> Create Reservation </div> <div class="panel-body"> <div class="form-group"> <label>Client Name</label> <input class="form-control" data-bind="value: model.editor.name" /> </div> <div class="form-group"> <label>Location</label> <input class="form-control" data-bind="value: model.editor.location" /> </div> <button class="btn btn-primary" data-bind="click: handleEditorClick">Save</button> </div> </div>
然后運行,我們添加數據之后,馬上更新。相對於傳統的方法,我們需要獲取dom中的數據,然后提交到后台,然后得到返回的數據更新table。
進而我們可以控制元素的顯示。修改model。添加displaysummary。初始化為一個bool值。
var model = {
reservations: ko.observableArray(),
editor: {
name: ko.observable(""),//相當於兩個變量。
location: ko.observable(""),
},
displaySummary: ko.observable(true)
};
添加隱藏方法,修改handleCreateClick
function handleCreateClick() { model.displaySummary(false); } function handleEditorClick() { sendAjaxRequest("POST", function (newItem) { model.reservations.push(newItem); model.displaySummary(true); }, null, { ClientName: model.editor.name, Location: model.editor.location }); }
綁定到元素: 這里用到了if表達式。
<div id="summary" class="section panel panel-primary" data-bind="if: model.displaySummary"> <div class="panel-heading">Reservation Summary</div> ... </div>
運行,就可以自在切換了。
小結:以上只是Knockout的簡單介紹,但是和Web api配合起來確實感覺不錯。更多Knockout的api請參考官網。knockoutjs 。 希望本文對你有幫助。
demo 下載: SPA
參考書籍:Apress Pro Asp.Net MVC5 此書在我的讀書群里面有下載,q群:452450927。歡迎愛讀書,愛分享的朋友加入。
參考文章:小牛之路 web api