【ASP.NET MVC 5】第27章 Web API與單頁應用程序

注:《精通ASP.NET MVC 3框架》受到了出版社和廣大讀者的充分確定,這讓本人深感欣慰。目前該書的第4版不日即將出版,如今又已開始第5版的翻譯,這裏先貼出該書的最後一章譯稿,僅供你們參考。
去年,除了翻譯《Pro ASP.NET MVC 4》以外,另外還翻譯了兩本書。一本是《HTML5+CSS3開發實戰》(亞馬遜、京東),由清華大學出版社出版。另外一本是《ASP.NET MVC 4實戰》(亞馬遜、京東),也由人民郵電出版社出版。css

CHAPTER 27
第27章html

■ ■ ■jquery

27 Web API and Single-page Applications
27 Web API與單頁應用程序

In this chapter, I describe the Web API feature, which is a relatively new addition to the ASP.NET platform that allows you to quickly and easily create Web services that provide an API to HTTP clients, known as Web APIs.
本章描述Web API特性,它是新添加到ASP.NET平臺的,讓你可以快速而方便地建立Web服務,以便爲HTTP客戶端提供API,稱之爲Web APIangularjs

The Web API feature is based on the same foundation as the MVC Framework applications, but is not part of the MVC Framework. Instead, Microsoft has taken some key classes and characteristics that are associated with the System.Web.Mvc namespace and duplicated them in the System.Web.Http namespace. The idea is that Web API is part of the core ASP.NET platform and can be used in other types of Web applications or used as a stand-alone Web services engine. I have included Web API in this book because one of the main uses for it is to create single-page applications (SPAs) by combining the Web API with MVC Framework features you have seen in previous chapters. I'll explain what SPAs are and how they work later in the chapter.
Web API特性創建在MVC框架應用程序的一樣基礎之上,但不是MVC框架的一部分。微軟從System.Web.Mvc命名空間提取了一些關鍵的類和特徵,並將它們複製到了System.Web.Http命名空間。其思想是,Web API是核心ASP.NET平臺的一部分,於是能夠將其用於其餘類型的Web應用程序,或者做爲獨立的Web服務引擎。本書之因此包含Web API內容,是由於它的主要用途是,你能夠將這種Web API與前幾章所看到的MVC框架特性相結合,從而建立一種單頁應用程序(SPA,Single-Page Application)。本章的後面部分將解釋這種SPA及其工做機制。web

That is not to take away from the way that Web API simplifies creating Web services. It is a huge improvement over the other Microsoft Web service technologies that have been appearing over the last decade or so. I like the Web API and you should use it for your projects, not least because it is simple and built on the same design that the MVC Framework uses.
這並不是是在貶低Web API簡化建立Web服務的方式。它是對微軟早在十多年前就已出現的其餘Web服務技術的巨大改進。我喜歡這種Web API,你也應該將它用於本身的項目,尤爲由於它簡單,並且創建在MVC框架所使用的一樣設計模式之上。ajax

I start this chapter by creating a regular MVC Framework application and then using the Web API to transform it into a single-page application. This is a surprisingly simple example, so I have treated the process like an extended example and applied some of the relevant techniques from earlier chapters because you can never have enough examples. Table 27-1 provides the summary for this chapter.
本章首先從建立一個常規的MVC框架應用程序開始,而後使用Web API將其轉換成一個單頁應用程序。這是一個至關簡單的示例,所以,我將這一建立過程處理成一個逐步擴展的示例,並運用前幾章的一些相關技術,由於示例是不可能嫌多的。表27-1描述了本章概要。數據庫

Table 27-1. Chapter Summary
表 27-1. 本章概要
Problem
問題
Solution
解決方案
Listing
清單號
Create a RESTful web service
建立REST化的Web服務
Add a Web API controller to an MVC Framework application.
在MVC框架的應用程序中添加一個Web API控制器
1–10
Map between HTTP methods and action names in a Web API controller
Web API控制器中HTTP方法與動做名之間的映射
Apply attributes such as HttpPut and HttpPost to the methods.
在動做方法上運用HttpPutHttpPost等註解屬性。
11
Create a single-page application
建立單頁應用程序
Use Knockout and jQuery to obtain data via Ajax and bind it to HTML elements.
使用Knockout和jQuery,以便經過Ajax獲取數據,並將這些數據綁定到HTML元素。
12–17

27.1 Understanding Single-page Applications
27.1 理解單頁應用程序

The term single-page application (SPA) is a broadly applied term. The most consistently-used definition is a web application whose initial content is delivered as a combination of HTML and JavaScript and whose subsequent operations are performed using a RESTful web service that delivers data via JSON in response to Ajax requests.
術語單頁應用程序(SPA)是一個寬泛的用語。最慣用的定義指,這是一種Web應用程序,其呈現的最初內容由HTML和JavaScript所組成,而它的後繼操做是使用REST化的Web服務執行的,這種服務對Ajax請求進行響應,並經過JSON提供數據。express

This differs from the kind of application I have been building in most of the chapters of this book, where operations performed by the user result in new HTML documents being generated in response to synchronous HTTP requests, which I will refer to as round-trip applications (RTAs).
這與本書大部分章節所創建的應用程序不一樣,在那些應用程序中,用戶執行的操做所產生的結果是,對HTTP請求以同步響應的方式生成新的HTML文檔,我將其稱爲往返式應用程序(RTA,Round-Trip Application)。json

The advantages of a SPA are that less bandwidth is required and that the user receives a smoother experience. The disadvantages are that the smoother experience can be hard to achieve and that the complexity of the JavaScript code required for a SPA demands careful design and testing.
SPA的優勢是所需的帶寬較少,並且用戶能夠獲得更爲流暢的體驗。缺點是這種流暢的體驗難以實現,並且,SPA所需的JavaScript代碼較爲複雜,須要進行當心的設計與測試。bootstrap

Most applications mix and match SPA and RTA techniques, where each major functional area of the application is delivered as a SPA, and navigation between functional areas is managed using standard HTTP requests that create a new HTML document.
大多數應用程序會混合和搭配SPA與RTA技術,其中的主要功能區由SPA實現,而各功能區之間的導航,則使用標準的HTTP請求建立新的HTML文檔進行管理。

27.2 Preparing the Example Application
27.2 準備示例項目

For this chapter, I created a new ASP.NET project called WebServices using the Empty template. I checked the options to add the folders and references for both MVC and Web API applications, as shown in Figure 27-1.
本章使用Empty模板建立了一個新的ASP.NET項目,名稱爲WebServices,並選中了一些選項,以便同時爲MVC和Web API應用程序添加文件夾和引用,如圖27-1所示。

圖27-1

Figure 27-1. Creating the project with the MVC and Web API references
圖 27-1. 建立帶有MVC和Web API引用的項目

I will use this project to create a regular MVC Framework application and then use the Web API to create a web service. Once the web service is complete, I'll return to the MVC Framework application and make it into a single-page application.
本章將用該項目建立一個常規的MVC框架應用程序,而後使用Web API建立一個Web服務。Web服務完成後,將回到MVC框架應用程序,將其改爲單頁應用程序。

27.2.1 Creating the Model
27.2.1 建立模型

This application will create and maintain a series of reservations. I want to keep the application simple so that I can focus on the mechanics of the features I describe, and so these reservations will consist of just a name and a location. I added a class file called Reservation.cs to the Models folder, the contents of which are shown in Listing 27-1.
該應用程序將建立並維護一系列預定(Reservation)。爲保持應用程序簡單,以便將注意力集中於本章所描述的特性機制,所以這些預定將只包含姓名和地點。爲此,在Models文件夾中添加了一個類文件,名稱爲Reservation.cs,其內容如清單27-1所示。

Listing 27-1. The Contents of the Reservation.cs File
清單 27-1. Reservation.cs文件的內容

namespace WebServices.Models {
    public class Reservation {
        public int ReservationId { get; set; }
        public string ClientName { get; set; }
        public string Location { get; set; }
    }
}

I am going to create a simple in-memory collection of Reservation objects to act as the model repository. I don't want to go to the trouble of setting up a database, but I do need to be able to perform CRUD operations on a collection of model objects so that I can demonstrate some important aspects of the Web API. I added a class file called ReservationRepository.cs to the Models folder and you can see the contents of the new file in Listing 27-2.
我打算建立一個簡單的Reservation對象的內存集合,以此充當模型存儲庫。我不想介入創建數據庫的麻煩,但又確實須要可以對模型對象集合執行CRUD操做,以便可以演示Web API的一些重要方面。因而在Models文件夾中添加了一個名稱爲ReservationRepository.cs的類文件,能夠從清單27-2看到其內容。

Listing 27-2. The Contents of the ReservationRespository.cs File
清單 27-2. ReservationRespository.cs文件的內容

using System.Collections.Generic;
using System.Linq;
namespace WebServices.Models { 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; } } } }

Tip In a real project, I would be concerned about tight coupling between classes and introduce interfaces and dependency injection into the application. My focus in this chapter is just on the Web API and SPA applications, so I am going to take some shortcuts when it comes to other standard techniques.
提示:在一個實際項目中,我會關注類與類之間的緊偶合狀況,並在應用程序中引入接口和依賴性注入。但本章的焦點只是Web API和SPA應用程序,所以,在涉及其餘標準技術時,我會採起一些簡化。

The repository class has an initial list of three Reservation objects and defines methods that allow me to view, add, delete and update the collection. Since there is no persistent storage, any changes that are made to the repository will be lost when the application is stopped or restarted, but this example is all about the way in which content can be delivered and not how it is stored by the server. To ensure that there is some persistence between requests, I have created a static instance of the ReservationRespository class, which is accessible through the Current property.
上述存儲庫類是一個有三個初始Reservation對象的列表,並定義了可以對該集合進行瀏覽、添加、刪除和更新的方法。因爲沒有持久化存儲,當應用程序中止或重啓時,對存儲庫所作的修改都會丟失,但該示例只關係到遞送內容的方式,而不是如何用服務器進行存儲。爲了確保可以在請求之間表現一些持久化的狀況,這裏建立了一個靜態的ReservationRespository類實例,能夠經過Current屬性進行訪問。

27.2.2 Adding the NuGet Packages
27.2.2 添加NuGet包

I am going to rely on three NuGet packages in this chapter: jQuery, Bootstrap and Knockout. I have already described and used jQuery and Bootstrap in earlier chapters. Knockout is the library that Microsoft has adopted for single-page applications. It was created by Steve Sanderson, whom I worked with on an earlier edition of this book and who works for the Microsoft ASP.NET team. Even though Steve works for Microsoft, the Knockout package is open source and widely used and you can learn more about it at http://knockoutjs.com. I'll explain how Knockout works later in the chapter, but for the moment I just need to install the NuGet packages. Select Package Manager Console from the Visual Studio ToolsLibrary Package Manager menu and enter the following commands:
本章打算依靠三個NuGet包:jQuery、Bootstrap和Knockout。在前面幾章中已經描述並使用過jQuery和Bootstrap。Knockout是微軟爲單頁應用程序而採納的一個庫。它是Steve Sanderson所建立的,他曾和我一塊兒著做過本書的早期版本,如今微軟的ASP.NET團隊工做。儘管Steve爲微軟工做,但Knockout包是開源的,並且被普遍使用,你能夠從http://knockoutjs.com瞭解更多狀況。本章稍後將解釋Knockout如何工做,但此刻只須要安裝這一NuGet包。在Visual Studio中選擇「Tools(工具)」 →「Library Package Manager(庫包管理器)」,以進入「Package Manager Console(包管理器控制檯)」,而後輸入以下命令:

Install-Package jquery –version 1.10.2
Install-Package bootstrap –version 3.0.0
Install-Package knockoutjs –version 3.0.0

27.2.3 Adding the Controller
27.2.3 添加控制器

I added a controller called Home to the example project, the definition of which you can see in Listing 27-3.
我在項目中添加一個Home控制器,其定義如清單27-3所示。

Listing 27-3. The Contents of the HomeController.cs File
清單 27-3. HomeController.cs文件的內容

using System.Web.Mvc;
using WebServices.Models;
namespace WebServices.Controllers {
public class HomeController : Controller { private ReservationRespository repo = ReservationRespository.Current;
public ViewResult Index() { return View(repo.GetAll()); }
public ActionResult Add(Reservation item) { if (ModelState.IsValid) { repo.Add(item); return RedirectToAction("Index"); } else { return View("Index"); } }
public ActionResult Remove(int id) { repo.Remove(id); return RedirectToAction("Index"); }
public ActionResult Update(Reservation item) { if (ModelState.IsValid && repo.Update(item)) { return RedirectToAction("Index"); } else { return View("Index"); } } } }

This is a fairly typical controller for such a simple application. Each of the action methods corresponds directly to one of the methods in the repository and the only value that the controller adds is to perform model validation, to select views, and perform redirections. In a real project, there would be more business domain logic, of course, but because the example application I am using is so basic, the controller ends up being little more than a wrapper around the repository.
對於這樣一個簡單的應用程序,這是一個至關典型的控制器。其中的每個方法直接對應於存儲庫中的相應方法,並且,控制器所添加的值只不過是爲了執行模型驗證、選擇視圖,或者執行重定向。固然,在一個實際項目中,應用程序會有較多的業務邏輯,但因爲本章的應用程序如此簡單,以至該控制器最終只比存儲庫封裝程序多了一點點內容。

27.2.4 Adding the Layout and Views
27.2.4 添加布局和視圖

To generate the content for the application, I started by creating the Views/Shared folder and adding a view file called _Layout.cshtml to it, the contents of which are shown by Listing 27-4.
爲了生成應用程序的內容,我首先建立了Views/Shared文件夾,並添加了一個名稱爲_Layout.cshtml的視圖文件,如清單27-4所示。

Listing 27-4. The Contents of the _Layout.cshtml File
清單 27-4. _Layout.cshtml文件的內容

@{
    Layout = null;
}
<!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" /> @RenderSection("Scripts") </head> <body> @RenderSection("Body") </body> </html>

This is a basic layout that has link elements for the Bootstrap CSS files. I have defined two layout sections, Scripts and Body, that I will use to insert content into the layout. My next step was to create the top-level view for the application. Although I am going through the process of creating a regular MVC Framework application, I know that I am going to end up with a single-page application and the transformation will be made easier if I create a single view that contains all the HTML that the application will require, even if it results in an odd appearance initially. I added a view file called Index.cshtml to the Views/Home folder, the contents of which you can see in Listing 27-5.
這是一個基本的佈局,它具備引用Bootstrap CSS文件(Bootstrap的樣式表文件——譯者注)的link元素。其中定義了ScriptsBody兩個佈局片斷,用於將內容插入該佈局。下一步是建立應用程序的頂級視圖。雖然中間會通過建立常規MVC框架應用程序的過程,但我知道,最終這是一個單頁應用程序,並且,若是建立一個單一的視圖,其中包含該應用程序所須要的所有HTML,後面的轉換會更爲容易,儘管該視圖最初的外觀有點怪異。爲此,在Views/Home文件夾中添加了一個名稱爲Index.cshtml的視圖,如清單27-5所示。

Listing 27-5. The Contents of the Index.cshtml File
清單 27-5. Index.cshtml文件的內容

@using WebServices.Models
@model IEnumerable<Reservation>
@{ ViewBag.Title = "Reservations"; Layout = "~/Views/Shared/_Layout.cshtml"; }
@section Scripts { }
@section Body { <div id="summary" class="section panel panel-primary"> @Html.Partial("Summary", Model) </div>
<div id="editor" class="section panel panel-primary"> @Html.Partial("Editor", new Reservation()) </div> }

The view model for this view is an enumeration of Reservation objects and I rely on two partial views to provide the functional building blocks that the user will see. The first partial view file is called Summary.cshtml. I created the file in the Views/Home folder and you can see the contents of the file in Listing 27-6.
該視圖的視圖模型是一個Reservation對象的枚舉,我依靠兩個分部分視圖提供了用戶可見的功能塊。第一個分部視圖文件叫作Summary.cshtml。這是在Views/Home文件夾中建立的,能夠從清單27-6看到其內容。

Listing 27-6. The Contents of the Summary.cshtml File
清單 27-6. Summary.cshtml文件的內容

@model IEnumerable<WebServices.Models.Reservation>
<div class="panel-heading">Reservation Summary</div> <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> @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> </table> </div>

The view model for the partial view is the same enumeration of Reservation object and I use it to generate a Bootstrap-styled table element that displays the object property values. I use the Html.ActionLink helper method to generate a link that will invoke the Remove action on the Home controller and use Bootstrap to style it as a button.
用於該分部視圖的視圖模型一樣是一個Reservation枚舉對象,用它生成了一個Bootstrap風格的table元素,在其中顯示各個對象的屬性值。這裏用Html.ActionLink輔助器方法生成了一個連接,它將調用Home控制器中的Remove動做,並使用Bootstrap使其成爲一個按鈕樣式。

The other partial view is called Editor.cshtml and I put this in the Views/Home folder as well. Listing 27-7 shows the contents of this file. This partial view contains a form that can be used to create new reservations. Submitting the form invokes the Add action on the Home controller.
另外一個分部視圖叫作Editor.cshtml,也放在Views/Home文件夾中。清單27-7顯示了該文件的內容。該分部視圖含有一個表單,用它建立新的預定。遞交該表單會調用Home控制器中的Add動做。

Listing 27-7. The Contents of the Editor.cshtml File
清單 27-7. Editor.cshtml文件的內容

@model WebServices.Models.Reservation
<div class="panel-heading"> Create Reservation </div> <div class="panel-body"> @using(Html.BeginForm("Add", "Home")) { <div class="form-group"> <label>Client Name</label> @Html.TextBoxFor(m => m.ClientName, new {@class = "form-control" }) </div> <div class="form-group"> <label>Location</label> @Html.TextBoxFor(m => m.Location, new { @class = "form-control" }) </div> <button type="submit" class="btn btn-primary">Save</button> } </div>

27.2.5 Setting the Start Location and Testing the Example Application
27.2.5 設置啓動位置並測試示例應用程序

The last preparatory step is to set the location that Visual Studio will navigate to when the application is started. Select WebServices Properties from the Visual Studio Project menu, switch to the Web tab and check the Specific Page option in the Start Action section. You don't have to provide a value. Just checking the option is enough. To test the application in its classic MVC Framework form, select Start Debugging from the Visual Studio Debug menu. You will see the (slightly odd) all-in-one layout that provides the user with a list of the current reservations and the ability to create and delete items, as shown in Figure 27-2.
最後一項準備工做是設置啓動位置,應用程序啓動時,Visual Studio將導航到該位置。在Visual Studio的「Project(項目)」菜單中選擇「WebServices Properties(WebServices屬性)」,切換到「 Web」選項卡,並選中「 Start Action(啓動行爲)」小節中的「 Specific Page(特定頁面)」選項。沒必要爲其提供一個值,只需選中該選項便可。爲了對這一傳統形式的MVC框架表單應用程序進行測試,可從Visual Studio的「 Debug(調試)」菜單中選擇「 Start Debugging(開始調試)」。這將看到一個一體化的佈局(有點怪),它給用戶提供了一個當前預定的列表,並可以建立和刪除條目,如圖27-2所示。

圖27-2

Figure 27-2. Testing the example application
圖 27-2. 測試示例應用程序

27.3 Using Web API
27.3 使用Web API

The Web API feature is based on adding a special kind of controller to an MVC Framework application. This kind of controller, called an API Controller, has two distinctive characteristics:
Web API特性是在MVC框架應用程序基礎上添加一種特殊的控制器,這種控制叫作API控制器(API Controller),它有兩個明顯的特徵:

  1. Action methods return model, rather than ActionResult, objects.
    動做方法返回的是模型對象,而不是ActionResult對象。
  2. Action methods are selected based on the HTTP method used in the request.
    動做方法是根據請求所使用的HTTP方法來選擇的。

The model objects that are returned from an API controller action method are encoded as JSON and sent to the client. API controllers are designed to deliver Web data services, so they do not support views, layouts, or any of the other features that I used to generate HTML in the example application.
API控制器的動做方法所返回的模型對象被編碼成JSON,併發送給客戶端。API控制器的設計目的是提供Web的數據服務,所以,它們不支持視圖、佈局,也不支持用來在示例應用程序中生成HTML的任何其它特性。

Tip The inability of an API controller to generate HTML from views is the reason that single-page applications combine standard MVC Framework techniques with the Web API. The MVC Framework performs the steps required to deliver HTML content to the user (including authentication, authorization, and selecting and rendering a view). Once the HTML is delivered to the browser, the Ajax requests generated by the JavaScript it contains are handled by the Web API controller.
提示:API控制沒法經過視圖生成HTML,這正是單頁應用程序須要將標準的MVC框架技術與Web API相結合的緣由。MVC框架執行的一些步驟是向用戶投遞HTML內容(包括認證、受權,以及選擇並渲染視圖等)。一旦給瀏覽器提供了HTML,由其中包含的JavaScript生成的Ajax請求,即可以由Web API控制器來處理了。

從這裏能夠看出,Web API的職責其實很簡單,就是在服務器端對客戶端發送過來的請求(一般爲Ajax請求)進行響應,併爲該請求準備數據,而後將數據回遞給客戶端。說得更簡單一點,就是爲客戶端提供Web服務。

另外一方面,爲了實現這種Web服務,客戶端須要作兩件事:第一,「要求服務」。客戶端要向服務器發送要求服務的請求,這一般爲Ajax請求。所以,客戶端一般須要使用JavaScript代碼造成這種請求(一般會使用jQuery庫,或漸近式Ajax庫)。客戶端要作的第二件事是,「處理服務」。客戶端要對Web API回發過來的數據進行處理,這一般又須要用JavaScript代碼對這些數據(一般爲JSON格式的數據)進行解析,並將這些數據顯示出來。這兩件事都是在客戶端發生的——譯者注

As I demonstrated in Chapter 23, you can create action methods in regular controllers that return JSON data to support Ajax, but the API controller offers an alternative approach that separates the data-related actions in your application from the view-related actions, and makes creating a general-purpose Web API quick and simple.
正如第23章所演示的,你能夠在常規控制器中建立返回JSON的動做方法,以支持Ajax,但這種API控制器提供了另外一種辦法,能夠將應用程序中數據相關的動做與視圖相關的動做分離開來,並且使得建立通用目的的Web API快速而簡單。

27.3.1 Creating the Web API Controller
27.3.1 建立Web API控制器

Adding Web API to an application is incredibly simple. In part this is because I am creating a basic web service, but also because the integration with the underpinnings of the MVC Framework means that little work is required. I created the WebController.cs class file in the Controllers folder of the project and used it to define the controller shown in Listing 27-8.
在應用程序添加Web API簡單得難以置信。一方面是由於這是在建立一個基本的Web服務,另外一方面是由於這種與MVC框架基礎的集成,意味着只需作不多的工做。我在項目的Controllers文件夾中建立了WebController.cs類文件,並用它定義了該控制器,如清單27-8所示。

Listing 27-8. The Contents of the WebController.cs File
清單 27-8. WebController.cs文件的內容

using System.Collections.Generic;
using System.Web.Http;
using WebServices.Models;
namespace WebServices.Controllers {
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); }
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); } } }

That is all that is required to create a Web API. The API controller has a set of five action methods that map to the capabilities of the repository and provide web service access to the Reservation objects.
這就是建立Web API所須要的所有。該PAI控制器有五個動做方法,它們分別映射到存儲庫的功能,並提供了對Reservation對象進行訪問的Web服務。

27.3.2 Testing the API Controller
27.3.2 測試API控制器

I will explain how the API controller works shortly, but first I am going to perform a simple test. Start the application. Once the browser loads the root URL for the project, navigate to the /api/web URL. The result that you see will depend on the browser that you are using. If you are using Internet Explorer, then you will be prompted to save or open a file that contains the following JSON data:
我很快會解釋API控制器的工做機制,但我首先打算執行一個簡單的測試。啓動應用程序,一旦瀏覽器已加載了項目的根URL,便導航到/api/web。所看到的結果依賴於你所使用的瀏覽器。若是是Internet Explorer,將提示你保存或打開含有如下JSON數據的文件:

[{"ReservationId":1,"ClientName":"Adam","Location":"Board Room"},
 {"ReservationId":2,"ClientName":"Jacqui","Location":"Lecture Hall"},
 {"ReservationId":3,"ClientName":"Russell","Location":"Meeting Room 1"}]

If you navigate to the same URL using a different browser, such as Google Chrome, then the browser will display the following XML data:
若是用不一樣的瀏覽器導航到一樣的URL,如Google Chrome,則瀏覽器將顯示如下XML數據:

<ArrayOfReservation>
    <Reservation>
        <ClientName>Adam</ClientName>
        <Location>Board Room</Location>
        <ReservationId>1</ReservationId>
    </Reservation>
    <Reservation>
        <ClientName>Jacqui</ClientName>
        <Location>Lecture Hall</Location>
        <ReservationId>2</ReservationId>
    </Reservation>
    <Reservation>
        <ClientName>Russell</ClientName>
        <Location>Meeting Room 1</Location>
        <ReservationId>3</ReservationId>
    </Reservation>
</ArrayOfReservation>

There are a couple of interesting things to note here. The first is that the request for the /api/web URL has produced a list of all of the model objects and their properties, from which we can infer that the GetAllReservations action method in the Reservation controller was called.
這裏有兩件有趣的事須要注意。首先,對/api/web的請求,已經產生了所有模型對象及其屬性的列表,由此能夠推斷,它調用了Reservation控制器中的GetAllReservations動做方法。

The second point to note is that different browsers received different data formats. You might get different results if you try this yourself because later versions of the browsers may change the way they make requests, but you can see that one of requests has produced JSON and the other has produced XML. (You can also see why JSON has largely replaced XML for Web services. XML is more verbose and harder to process, especially when you are using JavaScript.)
要注意的第二點是,不一樣的瀏覽器接受不一樣的數據格式。若是你本身試一試,也許會獲得不一樣的結果。由於更高版本的瀏覽器也許會改變它們造成請求的方式。但能夠看出,其中一個請求產生了JSON,而其餘請求則產生XML(其實應當是,有些瀏覽器發送的請求,會致使返回JSON,而有些則返回XML——譯者注)。(還能夠看出,JSON爲何會普遍代替XML用於Web服務。由於XML更加冗長而難以處理,尤爲是使用JavaScript時。)

The different data formats are used because the Web API uses the HTTP Accept header contained in the request to work out what data type the client would prefer to work with. Internet Explorer got JSON because this is the Accept header it sends:
之因此會使用不一樣的數據格式,是由於Web API會使用包含在請求中的HTTP的Accept報頭,以便推斷出客戶端更喜歡使用哪一種數據類型。Internet Explorer獲得JSON,是由於它發送瞭如下Accept報頭:

...
Accept: text/html, application/xhtml+xml, */*
...

The browser specified that it would like text/html content most of all, and then application/xhtml+xml. The final part of the Accept header is */*, which means the browser will accept any data type if the first two are not available.
瀏覽器指定了它最喜歡的是text/html格式的內容,其次是application/xhtml+xml。上述Accept報頭的最後部分是*/*,這意味着若是前兩個不可用,瀏覽器能夠接受任何數據類型。

The Web API supports JSON and XML, but it gives preference to JSON, which is what it used to respond to the */* part of the IE Accept header. Here is the Accept header that Google Chrome sent:
Web API支持JSON和XML,但對JSON會給予優先,這也是它針對IE的Accept報頭中的*/*部分所使用的響應。如下是Google Chrome所發送的Accept報頭:

...
Accept : text/html, application/xhtml+xml, application/xml; q=0.9, */*; q=0.8
...

I have highlighted the important part: Chrome has said that it prefers to receive application/xml data in preference to the */* catchall. The Web API controller honored this preference and delivered the XML data. I mention this because a common problem with Web API is getting an undesired data format. This happens because the Accept header gives unexpected preference to a format, or it is missing from the request entirely.
這裏已高亮了重要部分:Chrome說過,它更喜歡優先於*/*通配符而接受application/xml數據(故在上述報頭中,application/xml位於*/*通配符以前——譯者注)。Web API控制器聽從了這種優先,於是交付了XML數據。這裏說起此事,是由於Web API的一個常見問題是,它會得到非指望的數據格式。究其緣由是Accept報頭對非指望的格式給予了優先——或者,Accept報頭根本不在請求中出現。

27.4 Understanding How the API Controller Works
27.4 理解API控制器工做機制

You will understand a lot more about how the API controller works by navigating to the /api/web/3 URL. You will see the following JSON (or the equivalent XML if you are using another browser):
導航到/api/web/3網址,你會對API控制器的工做狀況有更多瞭解。這會看到如下JSON(或者,若是使用其餘瀏覽器,會看到對等的XML):

{"ReservationId":3,"ClientName":"Russell","Location":"Meeting Room 1"}

This time, the Web API controller has returned details of the Reservation object whose ReservationId value corresponds to the last segment of the URL I requested. The format of the URL and the use of the URL segment should remind you of Chapter 15, where I explained how MVC Framework routes work.
這一次,Web API返回了具體的Reservation對象,其ReservationId的值對應於請求URL的最後一個片斷。這種URL格式以及URL片斷的用法應該讓你回憶起第15章,在那裏解釋了MVC框架的路由工做機制。

API controllers have their own routing configuration, which is completely separate from the rest of the application. You can see the default configuration that Visual Studio creates for new projects by looking at the /App_Start/WebApiConfig.cs file, which I have shown in Listing 27-9. This is one of the files that Visual Studio adds to the project when you check the Web API box during project creation.
API控制器有其本身的路由配置,它與應用程序的其他部分是徹底分開的。經過考察/App_Start/WebApiConfig.cs文件,能夠看到Visual Studio爲新項目所建立的默認配置,如清單27-9所示。這是在建立項目期間選中Web API複選框時,Visual Studio會添加到項目的一個文件。

Listing 27-9. The Contents of the WebApiConfig.cs File
清單 27-9. WebApiConfig.cs文件的內容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace WebServices { public static class WebApiConfig { public static void Register(HttpConfiguration config) {
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }

The WebApiConfig.cs file contains the routes used for API Controllers, but uses different classes from the regular MVC routes defined in the RouteConfig.cs file. The Web API feature is implemented as a stand-alone ASP.NET feature and it can be used outside of the MVC Framework, which means that Microsoft has duplicated some key MVC Framework functionality in the System.Web.Http namespace to keep MVC and Web API features separate. (This seems oddly duplicative when writing an MVC Framework application but makes sense since Microsoft is trying to target non-MVC developers with Web API, too.) Visual Studio also adds a call from the Application_Start method in the Global.asax class so that the Web API routes are added to the application configuration, as shown in Listing 27-10.
WebApiConfig.cs文件包含了用於API控制器的路由,但使用了與RouteConfig.cs文件中所定義的常規MVC路由不一樣的類。Web API是做爲一個獨立的ASP.NET特性而實現的,並且能夠將它用於MVC框架以外,這意味着,微軟在System.Web.Http命名空間中複製了某些關鍵的MVC框架功能,以保持MVC與Web API特性相互獨立。(在編寫MVC框架的應用程序時,這彷佛是一種奇怪的重複,但這是有意義的,由於微軟這是在嘗試讓Web API也能面向非MVC開發者)。Visual Studio也在Global.asax類的Application_Start方法中添加了一個調用,以便將Web API路由添加到應用程序,如清單27-10所示。

Listing 27-10. The Contents of the Global.asax File
清單 27-10. Global.asax文件的內容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Http;
namespace WebServices { public class Global : HttpApplication { void Application_Start(object sender, EventArgs e) { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); RouteConfig.RegisterRoutes(RouteTable.Routes); } } }

The result is that the application has two sets of routes: those used for MVC Framework controllers and those used for Web API controllers.
其結果是,應用程序有兩套路由:分別用於MVC框架控制器和Web API控制器。

27.4.1 Understanding API Controller Action Selection
27.4.1 理解API控制的動做選擇

The default Web API route, which you can see in Listing 27-9, has a static api segment, and controller and id segment variables, the latter being optional. They key difference from a regular MVC route is that there is no action segment variable, and this is where the behavior of API controllers takes shape.
默認的Web API路由(見清單27-9)有一個靜態的api片斷,還有controllerid兩個片斷變量,後者(id)是可選的。這種路由與規則MVC路由的關鍵差異在於,它沒有action片斷變量,而這正是造成API控制器行爲之所在。

When a request comes in to the application that matches a Web API route, the action is determined from the HTTP method used to make the request. When I tested the API controller by requesting /api/reservation using the browser, the browser specified the GET method.
當一個與Web API路由匹配的請求到達應用程序時,動做是經過造成該請求的HTTP方法來決定的。在使用瀏覽器請求/api/reservation來測試API控制器時,瀏覽器指定的是GET方法。

The ApiController class, which is the base for API controllers, knows which controller it needs to target from the route and uses the HTTP method to look for suitable action methods.
API控制器的基類,ApiController類,經過路由會知道它須要以哪個控制器爲目標,並使用HTTP方法查找合適的動做方法。

The convention when naming API controller action methods is to prefix the name with the action method that it supports and include some reference to the model type that it operates on. But this is just a convention because Web API will match any action method whose name contains the HTTP method used to make the request.
在對API控制器的動做方法進行命名時,其約定是,以它所支持的HTTP方法爲前綴,而且含有對它所操做模型類型的某種參考。但這只是一種約定,由於Web API能夠匹配任何動做方法,只要其名稱包含了用以造成該請求的HTTP方法。

For the example, that means that a GET request results in a choice between the GetAllReservations and GetReservation, but method names like DoGetReservation or just ThisIsTheGetAction would also be matched.
對於本示例而言,這意味着GET請求會致使在GetAllReservationsGetReservation之間進行選擇,可是,也能夠匹配諸如DoGetReservationThisIsTheGetAction之類的方法名。

To decide between the two action methods, the controller looks at the arguments that the contenders accept and uses the routing variables to make the best match. When requesting the /api/reservation API, there were no routing variables except for controller, and so the GetAllReservations method was selected because it has no arguments. When requesting the /api/reservation/3 URL, I supplied a value for the optional id segment variable, which made the GetReservation the better match because it accepts an id argument.
爲了在兩個動做方法之間作出決定,控制器會考察其參數,給予優先的是接收並使用了路由變量者(即動做方法具備與路由片斷變量同名的參數——譯者注),以造成最佳匹配。在請求/api/reservation這一API時,沒有controller之外的其餘路由變量,因而會選擇GetAllReservations方法,由於它沒有參數。當請求/api/reservation/3這一URL時,它爲可選的id片斷變量提供了值,這會使GetReservation更爲匹配,由於它接收了一個id參數。

The other actions in the Web API controller are targeted using other HTTP methods: POST, DELETE, and PUT. This is the foundation for the Representation State Transfer (REST) style of Web API, known more commonly as a RESTful service, where an operation is specified by the combination of a URL and the HTTP method used to request it.
上述Web API控制器中的其餘動做方法也進行了目標定位,使用的是其餘HTTP方法:POST、DELETE以及PUT。這是「表現狀態傳輸(Representation State Transfer,簡稱REST)」風格的Web API的基礎,更常常地稱爲REST化服務(RESTful Service),在這種服務中,某個操做是經過URL和請求該URL的HTTP方法相結合的方式來指定的。

Note REST is a style of API rather than a well-defined specification, and there is disagreement about what exactly makes a Web service RESTful. One point of contention is that purists do not consider Web services that return JSON as being RESTful. Like any disagreement about an architectural pattern, the reasons for the disagreement are arbitrary and dull. I try to be pragmatic about how patterns are applied, and JSON services are RESTful as far as I am concerned.
注:REST是一種API風格,而不是一個完善定義的規範,到底什麼是Web服務的REST化是有爭議的。其中一種論點是,有些純粹主義者認爲,返回JSON的Web服務不是REST化的。如同「架構模式(Architectural Pattern)」的爭論同樣,爭論的理由是隨意而無聊的。就咱們而言,應儘可能務實地看待如何運用模式,以及JSON服務是不是REST化的(做者傾向於認爲JSON服務是REST化的——譯者注)。

27.4.2 Mapping HTTP Methods to Action Methods
27.4.2將HTTP方法映射到動做方法

I explained that the ApiController base class uses the HTTP method to work out which action methods to target. It is a nice approach, but it does mean that you end up with some unnatural method names that are inconsistent with conventions you might be using elsewhere. For example, the PutReservation method might be more naturally called UpdateReservation. Not only would UpdateReservation make the purpose of the method more obvious, but it may allow for a more direct mapping between the actions in your controller and the methods in your repository.
以上曾解釋過,ApiController基類會使用HTTP方法得出目標動做方法。這是一種很好的辦法,但這確實意味着,你最終會有一些不天然的方法名稱,它們與你可能在其餘地方使用的約定不相一致。例如,PutReservation方法(注意,這是一個以HTTP方法爲前綴的方法名,符合ApiController基類的命名約定——譯者注)也許叫作UpdateReservation(該方法名與約定不符——譯者注)會更天然些。UpdateReservation不只使該方法的目的更爲明顯,並且它或許可以更爲直接地映射出控制器中的動做與存儲庫中的方法之間的關係。

Tip You might be tempted to derive your repository class from ApiController and expose the repository methods directly as a Web API. I recommend against that and strongly suggest you create a separate controller, even as simple as the one I created in the example. At some point, the methods you want to offer via your API and the capabilities of your repository will diverge, and having a separate API controller class will make that easier to manage.
提示:你也許會嘗試根據ApiController來派生你的存儲庫類,並將這些存儲方法直接暴露成Web API。個人建議與此相左,而是強烈建議你建立一個獨立的控制器,哪怕是在建立如同此示例同樣簡單的項目。在有些狀況下,你但願經過API提供的方法與存儲的功能是有誤差的,於是擁有獨立的API控制器,會更易於管理。

The System.Web.Http namespace contains a set of attributes that you can use to specify which HTTP methods an action should be used for. You can see how I have applied two of these attributes in Listing 27-11 to create a more natural set of method names.
System.Web.Http命名空間包含了一組註解屬性,能夠用來指定一個動做所使用的HTTP方法。從清單27-11能夠看出,我已經運用了其中兩個註解屬性,以建立更爲天然的方法名稱(讀者應該注意到,在如下清單中,動做方法名稱並未包含POST、PUT等字樣,而按命名約定是應該包含這些字樣的,但因爲使用了註解屬性,故可使用這種更爲天然的動做方法名稱——譯者注)。

Listing 27-11. Applying Attributes in the WebController.cs File
Listing 27-11. 在WebController.cs文件中動做註解屬性

using System.Collections.Generic;
using System.Web.Http;
using WebServices.Models;
namespace WebServices.Controllers {
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 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); } } }

You can see the duplication with the MVC Framework features and the Web API. The HttpPost and HttpPut attributes that I used in Listing 27-11 have the exact same purpose as the attributes with the same name that I used in Chapter 19, but they are defined in the System.Web.Http namespace and not System.Web.Mvc. Duplication aside, the attributes work in the same way and I have ended up with more useful method names that will still work for the POST and PUT HTTP methods. (There are, of course, attributes for all of the HTTP methods, including GET, DELETE and so on.)
能夠看出,MVC框架的特性與Web API是重複的。清單27-11所使用的HttpPostHttpPut註解屬性與第19章所使用的同名註解屬性具備徹底相同的目的,但它們是在System.Web.Http命名空間而不是在System.Web.Mvc中定義的。除了這種重複之外,這些註解屬性的工做方式也相同,因而咱們最終擁有了更爲有用的方法名,它們仍然可以爲POSTPUT等HTTP方法而工做(固然,全部HTTP方法都有註解屬性,包括GETDELETE等)。

27.5 Using Knockout for Single-page Applications
27.5 將Knockout用於單頁應用程序

The purpose of creating the Web API web service is to refactor the example application so that operations on the application data can be performed using Ajax requests whose JSON results will be used to update the HTML in the browser. The overall functionality of the application will be the same, but I won't be generating complete HTML documents for each interaction between the client and the server.
建立上述Web API這種Web服務的目的,是從新構造示例應用程序,以便使用Ajax請求來執行應用程序的數據操做,將Ajax請求的JSON結果用於更新瀏覽器中的HTML。應用程序的整體功能是相同的(指重構先後——譯者注),但對於客戶端與服務器之間的每個交互,無需生成完整的HTML。

The transition to a single-page application puts more of a burden on the browser because I need to preserve application state at the client. I need a data model that I can update, a series of logic operations that I can perform to transform the data and a set of UI elements that allows the user to trigger those operations. In short, I need to recreate a miniature version of the MVC pattern in the browser.
轉換成單頁應用程序後,將更多的責任放到了瀏覽器端,由於須要在客戶端保持應用程序的狀態。我須要一個可以對之進行更新的數據模型、須要一系列可以對數據執行轉換的邏輯操做,還須要一組UI元素,以便用戶可以觸發這些操做。簡言之,我須要在瀏覽器中重建一個小型版的MVC模式。

The library that Microsoft has adopted for single-page applications is Knockout, which creates a JavaScript implementation of the MVC pattern (or, more accurately, the MVVM pattern which I described in Chapter 3 and is sufficiently close to the MVC pattern that I am going to treat them as the same thing). In the sections that follow, I am going to return to the MVC Framework side of the example application and apply the Knockout library to create a simple SPA.
被微軟採納用於單頁應用程序的庫是Knockout,它建立了JavaScript實現的MVC模式(或者更確切地說,是第3章所描述的MVVM模式,它與MVC模式十分接近,我打算把它們當作一回事)。在接下來的幾小節中,將回到上述MVC框架的示例應用程序,運用這個Knockout庫建立一個SPA。

Note Knockout does a lot more than I am going to demonstrate here and I recommend you explore the library in more depth to see what it is capable of. You can learn more at http://knockoutjs.com or from my Pro JavaScript for Web Apps book, which is published by Apress. I like Knockout, but for more complex applications I prefer AngularJS. It has a steeper learning curve, but the investment is worthwhile. You can learn more at http://angularjs.org or read my Pro AngularJS book, which—as you might have guessed by now—is also published by Apress.
注:Knockout要比這裏所演示的功能強大得多,建議對該庫作更深刻的考察,看看它可以作些什麼。更多信息可參閱http://knockoutjs.com,或我著的Pro JavaScript for Web Apps,該書由Apress出版。我喜歡Knockout,但對於更復雜的應用程序,我更喜歡AngularJS。它有一個陡峭的學習曲線,但這種付出是值得的。更多信息可參閱http://angularjs.org,或閱讀個人圖書Pro AngularJS——你此刻可能已經猜到,該書也由Apress出版。

27.5.1 Adding the JavaScript Libraries to the Layout
27.5.1 在佈局中添加JavaScript庫

The first step is to add the Knockout and jQuery files to the layout so that they are available in the view. You can see the script element I added in Listing 27-12.
第一步是對佈局添加Knockout和jQuery文件,以使它們在視圖中是可用的。從清單27-12能夠看出,已經添加了script元素。

Listing 27-12. Adding the Knockout JavaScript File to the _Layout.cshtml File
清單 27-12. 在_Layout.cshtml文件中添加Knockout和JavaScript文件

@{
    Layout = null;
}
<!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.js"></script> <script src="~/Scripts/knockout-3.0.0.js"></script> @RenderSection("Scripts") </head> <body> @RenderSection("Body") </body> </html>

I will be using Knockout to create the MVC implementation and jQuery to handle the Ajax requests.
如下將使用Knockout來建立MVC實現,並用jQuery處理Ajax請求。

27.5.2 Implementing the Summary
27.5.2 實現Summary

The first major change I will make is to replace the Summary partial view with some inline Knockout and jQuery. You don't have to use Knockout in a single file, but I want to leave the partial views intact so you can see the difference between the standard MVC Framework way of working and the SPA techniques. In Listing 27-13, you can see the changes that I made to the Index.cstml view file.
要作的第一個重要修改是,用一些內聯的Knockout和jQuery替換Summary分部視圖。你不必定要在一個單一的視圖中使用Knockout,這裏只是但願保持該分部視圖完整,以使你可以看出標準的MVC框架的工做方式與SPA技術之間的差異。從清單27-13能夠看出對Index.cstml視圖文件所作的修改。

Listing 27-13. Using Knockout and jQuery to Implement the Summary in the Index.cshtml File
清單 27-13. 使用Knockout和jQuery實現Index.cshtml文件中的Summary

@using WebServices.Models
@{
    ViewBag.Title = "Reservations";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
@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> }
@section Body { <div id="summary" class="section panel panel-primary"> <div class="panel-heading">Reservation Summary</div> <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> <div id="editor" class="section panel panel-primary"> @Html.Partial("Editor", new Reservation()) </div> }

There is a lot going on here, so I am going to break down the changes and explain each of them in turn. The overall effect of the changes, however, is that the browser uses the Web API controller to get details of the current reservations.
這裏作了不少事,所以我會對這些修改進行分解,並依次對它們進行解釋。不過,這些修改的整體效果是,讓瀏覽器使用Web API控制器獲取當前預定的細節。

Note Notice that I have removed the @model expression from the Index view in Listing 27-13. I am not using the view model objects to generate the content in the view, which means that I don't need a view model. The controller is still obtaining the Reservation objects from the repository and passing them to the view, but I'll fix this later in the chapter.
注:注意,清單27-13中已經刪除了Index視圖的@model表達式。因而將不會使用視圖模型對象來生成該視圖的內容,意即,這裏已經不須要視圖模型。但控制器仍然會獲取存儲庫的Reservation對象,並將它們傳遞給視圖(注意,這是指清單27-3所示的Home控制器,由於,這個Index視圖是由Home控制器中的Index動做方法所渲染的。此刻,在Home控制器的Index動做方法中,仍會從存儲庫中獲取Reservation對象,並將其傳遞給視圖——譯者注),本章稍後會對此加以修正。

1. Defining the Ajax Functions
1. 定義Ajax函數

jQuery has excellent support for making Ajax requests. To that end, I have defined a function called sendAjaxRequest that I will use to target methods on the Web API controller, as follows:
jQuery對造成Ajax請求有很好的支持。爲此,我定義了一個名稱爲sendAjaxRequest的函數,將它用於定位Web API控制器中的目標方法,以下所示:

...
function sendAjaxRequest(httpMethod, callback, url) {
    $.ajax("/api/web" + (url ? "/" + url : ""), {
        type: httpMethod, success: callback
    });
}
...

The $.ajax function provides access to the jQuery Ajax functionality. The arguments are the URL you want to request and an object that contains configuration parameters. The sendAjaxRequest function is a wrapper around the jQuery functionality and its arguments are the HTTP method that should be used for the request (which affects the action method selected by the controller), a callback function that will be invoked when the Ajax request has succeeded and an optional URL suffix. Using the sendAjaxRequest function as a foundation, I defined functions to get all of the data available and to delete a reservation, like this:
其中的$.ajax函數提供了對jQuery Ajax功能的訪問。其參數是你但願請求的URL和一個含有配置參數的對象。上述sendAjaxRequest函數是這一jQuery功能的封裝程序(指sendAjaxRequest$.ajax的封裝程序——譯者注),其參數是用於該請求的HTTP方法(它會影響到控制器所選擇的動做方法)、Ajax請求成功後要調用的回調函數,和一個可選的URL後綴。以這個函數爲基礎,我定義了一些函數,用於獲取全部可用數據,以及刪除預定等,以下所示:

...
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); } ...

The getAllItems function targets the GetAllReservations controller action method and retrieves all of the reservations from the server. The removeItem function targets the DeleteReservation action method and calls the getAllItems function to refresh the data after a deletion.
getAllItems函數以控制器的GetAllReservations動做方法爲目標,並從服務器接收全部預定。removeItem函數以DeleteReservation動做方法爲目標,並在執行刪除後調用getAllItems函數,以刷新數據。

2. Defining the Model
2. 定義模型

Underpinning the Ajax functions is the model, which I defined like this:
Ajax函數的基礎是模型,我是這樣對它進行定義的:

...
var model = {
    reservations: ko.observableArray()
};
...

Knockout works by creating observable objects that it monitors for changes and uses to update the HTML displayed by the browser. My model is simple. It consists of an observable array, which is just like a regular JavaScript array, but is wired up so that any changes I made are detected by Knockout. You can see how I use the model in the Ajax functions, like this:
Knockout是經過建立可觀察對象(Observable Object)進行工做的(能夠將這種「可觀察對象」理解爲「Knockout能夠看到(觀察到)的對象」,意即,Knockout可以觀察或感知該對象中所發生的數據變化——譯者注),Knockout會監視該對象的變化,並用它對瀏覽器所顯示的HTML進行更新。該模型很簡單,它是一個可觀察數組,這種數組如同常規的JavaScript數組同樣,但被掛接起來了(被掛接到Knockout上了——譯者注),所以,Knockout能夠檢測到該數組的任何變化。從如下代碼能夠看到如何在Ajax函數中使用該模型:

...
function getAllItems() {
    sendAjaxRequest("GET", function(data) {
        model.reservations.removeAll();
        for (var i = 0; i < data.length; i++) {
            model.reservations.push(data[i]);
        }
    });
}
...

The two statements I highlighted are how I get the data from the server into the model. I call the removeAll method to remove any existing data from the observable array and then iterate through the results I get from the server with the push method to populate the array with the new data.
這裏高亮的兩條語句,說明了如何將從服務器獲得的數據送入模型。首先調用removeAll方法,以刪除可觀察數組中的已有數據,而後遍歷從服務器獲得的結果,以push方法對該數組填充新數據。

3. Defining the Bindings
3. 定義綁定

Knockout applies changes in the data model to HTML elements via data bindings. Here are the most important data bindings in the Index view:
Knockout經過數據綁定(Data Binding)將數據模型中的變化運用於HTML元素。如下是Index視圖中最重要的綁定:

...
<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>
...

Knockout is expressed using the data-bind attribute and there is a wide range of bindings available, three of which I have used in the view. The basic format for a data-bind attribute is:
Knockout綁定用data-bind標籤屬性表示,並且有大量可用的綁定,該視圖中使用了其中三個。data-bind標籤屬性的基本格式是:

data-bind="type: expression"
data-bind="<類型> : <表達式>"

The types of the three bindings in the listing are foreach, text and click, and I picked these three because they represent the different ways in which Knockout can be used.
上述清單中三個綁定的「類型」分別是,foreachtextclick,我之因此挑選這三種綁定,是由於它們表明示Knockout綁定所能使用的不一樣方式。

The first two, the foreach and text bindings, generate HTML elements and content from the data model. When the foreach binding is applied to an element, Knockout generates the child elements for each object in the expression, just like the Razor @foreach that I was using in the partial view.
前兩個綁定,foreachtext,經過數據模型生成HTML及其內容。當foreach綁定被運用於一個元素時,Knockout爲「表達式」中的每個對象生成子元素,這就如同在分部視圖中使用Razor的@foreach同樣。

The text binding inserts the value of the expression as the text of the element that it is applied to. This means that when I use this binding, for example:
text綁定將表達式的值做爲文本,插入到它所運用的元素。這意味着,當像下面這樣使用綁定時:

...
<td data-bind="text: ClientName"></td>
...

Knockout will insert the value of the ClientName property of the current object being processed by the foreach binding, which has the same effect as the Razor @Model.ClientName expression I used previously.
Knockout將插入foreach綁定所處理的當前對象的ClientName屬性的值,這與以前所使用的Razor表達式@Model.ClientName的效果相同。

The click directive is different: it sets up an event handler for the click event on the element to which it has been applied. You don't have to use Knockout to set up events, of course, but the click binding is integrated with the other bindings and the function you specify to call when the event is triggered is passed the data object that was being processed by the foreach binding when the binding was applied. This is why the removeItem function is able to define an argument that receives a Reservation object (or its JavaScript representation, anyway).
click指示符有所不一樣:它會在所運用元素上,爲click事件創建一個事件處理程序。固然,你不必定要使用Knockout來創建事件,但這種click綁定是與其餘綁定集成在一塊兒的,並且,在運用該綁定時,foreach綁定所處理的數據對象,會被傳遞給你所指定的事件觸發時要調用的函數。這是removeItem函數可以定義一個參數,以接收一個Reservation對象(或該對象的任意JavaScript事物)的緣由。

4. Processing the Bindings
4. 處理綁定

Knockout bindings are not processed automatically, which is why I included this code in the script element:
Knockout綁定並非自動執行的,這是我在script元素中包含如下代碼的緣由:

...
$(document).ready(function () {
    getAllItems();
    ko.applyBindings(model);
});
...

The $(document).ready call is a standard jQuery technique to defer execution of a function until all of the HTML elements in the document have been loaded and processed by the browser. Once that happens, I call the getAllItems function to load the data from the server and then the ko.applyBindings function to use the data model to process the data-bind attributes. This final call is the one that connects the data objects to the HTML elements, generates the content I require, and sets up the event handlers.
$(document).ready調用是jQuery推遲函數執行的一項標準技術,它直到瀏覽器加載並處理了文檔的全部HTML元素以後纔會執行。一旦開始執行,便調用getAllItems函數加載從服務器過來的數據,而後調用ko.applyBindings函數,使用數據模型處理那些data-bind標籤屬性。調用的結果是,將數據對象鏈接到HTML元素,生成了所須要的內容,並創建事件處理程序。

5. Testing the Summary Bindings
5. 測試Summary中的綁定

You might be wondering why I have gone to all this trouble, given that I have essentially replaced Razor expressions with their equivalent Knockout bindings. There are three important differences and to demonstrate them fully, I am going to use the browser F12 tools.
你也許會奇怪,爲何要作以上這麼多麻煩事,這已經基本上用對等的Knockout綁定替換了全部Razor表達式。這裏有三個重要的差異,並且,爲了對這些差異進行演示,將會使用瀏覽器的F12工具。

The first difference is that the model data is no longer included in the HTML that is sent to the browser. Instead, once the HTML has been processed, the browser makes an Ajax request to the Web API controller and gets the list of reservations expressed as JSON. You can see this by starting the application and using the F12 tools to monitor the requests that the browser makes (as described in Chapter 26). Figure 27-3 shows the results.
第一個差異是,模型數據已經再也不包含在發送給瀏覽器的HTML中。相反,一旦HTML已經獲得了處理,瀏覽器便會造成一個發送給Web API控制器的Ajax請求,並獲得一個表示成JSON的列表。經過啓動應用程序,並用F12工具監視瀏覽器所造成的請求(如第26章所描述的那樣),便會看到這種狀況。圖27-3顯示了其結果(從圖中能夠看出,/api/web網址的請求類型是application/json,這說明該請求獲得的是JSON數據,可見這是發送Ajax請求所產生的效果——譯者注)。

圖27-3

Figure 27-3. Monitoring the connections made by the browser
圖 27-3. 監視瀏覽器造成的鏈接

The second difference is that the data is processed by the browser, rather than at the server, as the view is rendered. To test this, you can edit the getAllItems function so that it doesn't make the Ajax request or process the data it receives, like this:
第二個差異是,數據是在視圖被渲染時,由瀏覽器而不是服務器進行處理的。爲了對此進行測試,你能夠編輯getAllItems函數,以使它不會造成Ajax請求,或者不處理它所獲得的數據,以下所示:

...
function getAllItems() {
    return;
    sendAjaxRequest("GET", function(data) {
        model.reservations.removeAll();
        for (var i = 0; i < data.length; i++) {
            model.reservations.push(data[i]);
        }
    });
}
...

The function will return before the Ajax request is made and you can see the effect by restarting the application, as shown in Figure 27-4. This may seem obvious, but it is an important characteristic of SPAs that the browser does a lot more work, including processing data and generating HTML content.
該函數會在造成Ajax請求以前就返回,經過啓動應用程序,能夠看到其效果,如圖27-4所示。這彷佛是顯然的,但倒是SPA的重要特徵,代表瀏覽器作了大量工做,包括處理數據,以及生成HTML內容。

圖27-4

Figure 27-4. Demonstrating that the data is retrieved and processed by the browser
圖 27-4. 演示數據是由瀏覽器進行接收和處理的

The final difference is that the data bindings are live, meaning that changes in the data model are reflected in the content that the foreach and text bindings generate. To test this, ensure that you return the getAllItems function to its working state and reload the application. Once the browser has requested, received and processed the data, open the F12 tools and switch to the Console section. Enter the following command into the console and hit Enter:
最後一個差異是,數據綁定是實時的,意即,數據模型中的變化會反映在foreachtext綁定所生成的內容中。爲了對此進行測試,請確保getAllItems函數回到它的正常工做狀態(掉去上述代碼中的return語句——譯者注),並重載應用程序。一旦瀏覽器發送了請求,接收並處理了數據以後,請打開F12工具,並切換到Console(控制檯)小節。在控制檯中輸入下列命令,並按回車。

model.reservations.pop()

This expression removes the last item from the array of data objects in the model and as soon as you enter the command, the layout of the HTML page will reflect the change, as shown in Figure 27-5. The overall effect is that I have shifted some of the complexity of generating the HTML from the server to the client.
該表達式刪除了模型中數據對象數組的最後一個條目,並且,只要你輸入該命令,HTML頁面佈局就會反應出這一變化,如圖27-5所示。最後的整體效果是,我已經轉移了從服務器向客戶端生成HTML的一些複雜性。

圖27-5

Figure 27-5. Manipulating the model via the JavaScript console
圖 27-5. 經過JavaScript控制檯操縱模型

27.5.3 Improving the Delete Feature
27.5.3 改善Delete特性

Now that you have seen how applying Knockout has changed the nature of the client, I am going to quickly loop back and remove a shortcut that I took when I defined the Ajax methods for the application. The removeItem function is badly written:
如今你已經看到,運用Knockout已經改變了客戶端的性質,我打算馬上回過頭來,去掉在爲應用程序定義Ajax方法時所採用的一個簡化方法。removeItem函數寫得很糟:

...
function removeItem(item) {
    sendAjaxRequest("DELETE", function () {
        getAllItems();
    }, item.ReservationId);
}
...

I have highlighted the problem: the function makes two Ajax requests to the server—the first to perform the deletion and the second to request the contents of the repository to update the data model. Now that I have demonstrated that the client maintains its own model and that live bindings reflect model changes in the HTML, I can improve upon this function, as shown in Listing 27-14.
這裏高亮了問題所在:該函數向服務器造成了兩個Ajax請求——第一個執行刪除,第二個是請求存儲庫的內容,以便更新數據模型。如今,我已經演示過,客戶端維護它本身的模型,並且,實時綁定會在HTML中反映模型的變化,所以能夠對該函數加以改進,如清單27-14所示。

Listing 27-14. Improving the RemoveItem Function in the Index.cshtml File
清單 27-14. 改善Index.cshtml文件中的RemoveItem函數

...
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);
}
...

When the request to the server succeeds, I remove the corresponding data object from the model, which means that the second Ajax request is no longer required.
當發送給服務器的請求成功時,我從模型中刪除了相應的數據對象,這意味着已再也不須要第二個Ajax請求(在上述代碼中,sendAjaxRequest函數向服務器發送DELETE請求,成功時會執行上述回調函數,在該回調用函數中,直接在客戶端的模型對象中刪除了相應的數據對象——譯者注)。

GETTING USED TO THE KNOCKOUT SYNTAX
讓本身習慣於Knockout語法

There are some syntax quirks when working with Knockout observable arrays and two of them can be seen in Listing 27-14. To get an item from the array, I have to treat model.reservations like a function, as follows:
在使用Knockout可觀察數組時,有一些怪異的語法。在清單27-14中能夠看到其中兩種。爲了從數組中獲取一個條目,須要將model.reservations做爲一個函數來看待(如下代碼中的「 ()」,就意味着已經將model.reservations當作是一個函數了——譯者注),以下所示:

...
model.reservations() [i].ReservationId
...

And when it comes to removing items from the array, I use a function that is not standard JavaScript:
另外,在刪除數組中的條目時,使用了一個非標準的JavaScript函數(可見,如下代碼中的remove方法不屬於JavaScript語法,而是Knockout提供的函數——譯者注):

...
model.reservations.remove (model.reservations()[i]);
...

Knockout tries to maintain the standard JavaScript syntax, but there are some compromises required to track changes to data objects, such as these quirks. They can be confusing when you first start working with Knockout, but you soon get used to them. And you learn that when you don't get the effect you require, the likely cause is a mismatch between the standard JavaScript syntax and that required for a Knockout observable object or array. You can get further information about the Knockout API at http://knockoutjs.com.
Knockout試圖保持標準的JavaScript語法,但有時須要有一些折衷的辦法,以便跟蹤數據對象的變化,好比,這些怪異的語法。在第一次使用Knockout時,可能會對此感到困惑,但很快便會習慣。所以,在未獲得預期的效果時,應該意識到,可能的緣由是,標準的JavaScript語法與須要用於Knockout可觀察對象或數組的語法之間搭配不當。Knockout API的更多信息請參考http://knockoutjs.com。

27.5.4 Implementing the Create Feature
27.5.4 實現Create特性

The next step is to use Knockout to replace the Editor partial view. Once again, I could have updated the partial view to contain the Knockout functionality, but I have chosen to include everything in the Index.cshtml file, as shown in Listing 27-15.
下一個步驟是使用Knockout替換Editor分部視圖。一樣,能夠對Editor分部視圖進行更新,以便包含Knockout功能,但我選擇了將全部事情都歸入Index.cshtml文件,如清單27-15所示。

Listing 27-15. Implementing the Create Feature in the Index.cshtml File
清單 27-15. 實現Index.cshtml文件中的Create特性

@using WebServices.Models
@{
    ViewBag.Title = "Reservations";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
@section Scripts { <script> var model = { reservations: ko.observableArray(), editor: { name: ko.observable(""), location: ko.observable("") } };
function sendAjaxRequest(httpMethod, callback, url, reqData) { $.ajax("/api/web" + (url ? "/" + url : ""), { type: httpMethod, success: callback, data: reqData }); }
// ... other functions omitted for brevity ... // ... 出於簡化,忽略了其餘函數 ...
function handleEditorClick() { sendAjaxRequest("POST", function (newItem) { model.reservations.push(newItem); }, null, { ClientName: model.editor.name, Location: model.editor.location }); }
$(document).ready(function () { getAllItems(); ko.applyBindings(model); }); </script> }
@section Body { <div id="summary" class="section panel panel-primary"> <!-- elements omitted for brevity(出於簡化,已忽略了這裏的元素) --> </div> <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> }

To create the editor, I have used Knockout in a different way, as I'll explain step-by-step in the sections that follow.
爲了建立編輯器(這裏的「編輯器」是指對新預定進行編輯的HTML——譯者注),我以一種不一樣的方式使用了Knockout,在接下來的小節中對其逐步解釋。

1. Extending the Model
1. 擴展模型

I need to collect two pieces of information from the user in order to create a new Reservation in the repository: the name of the client (corresponding to the ClientName property) and the location (corresponding to the Location property). My first step is to extend the model so that I define variables that I can use to capture these values, like this:
爲了在存儲庫中建立一個新的Reservation,須要從用戶那裏收集兩個信息片斷:客戶名稱(對應於ClientName屬性)和地點(對應於Location屬性)。個人第一個步驟是擴展模型,以便定義一些可以用來捕捉這些值的變量,以下所示:

...
var model = {
    reservations: ko.observableArray(),
    editor: {
        name: ko.observable(""),
        location: ko.observable("")
    }
};
...

The ko.observable function creates an observable value, which I will rely on later in the chapter. Changes to these values will be reflected in any bindings that use the name and location properties.
ko.observable函數能夠建立一個可觀察值,本章的後面要依靠這裏所建立的可觀察值(這裏建立了兩個可觀察值——譯者注)。對這些值的修改會反映在使用namelocation屬性的任何綁定中。

2. Implement the Input Elements
2. 實現input元素

The next step is to create the input elements through which the user will supply values for my new model properties. I have used the Knockout value binding, which sets the value attribute on an element, as follows:
下一個步驟是建立input元素,用戶將經過這些元素爲新的模型屬性提供值。這裏使用了Knockout的value綁定,它會設置元素上的value屬性,以下所示:

...
<input class="form-control" data-bind="value: model.editor.name" />
...

The value bindings ensure that the values entered by the user into the input elements will be used to set the model properties.
value綁定確保會將用戶在input元素中輸入的值用於設置模型的屬性。

Tip Notice that I don't need a form element anymore. I will be using an Ajax request to send the data values to the server in response to a button click, none of which relies on the standard browser support for forms.
提示:注意,這裏已再也不須要form元素。我將使用一個Ajax請求將這些數據值發送給服務器,以便對一個按鈕的點擊事件進行響應,它們都不須要依靠瀏覽器對錶單的標準支持。

3. Creating the Event Handler
3. 建立事件處理程序

I used the click binding to handle the click event from the button element displayed under the input elements, as follows:
我使用了click綁定,以便對顯示在input元素下方的button元素的click事件進行處理,以下所示:

...
<button class="btn btn-primary" data-bind="click:handleEditorClick">Save</button>
...

The binding specifies that the handleEditorClick function should be called when the button is clicked and I defined this function in the script element, as follows:
該綁定指定,在按鈕被點擊時,應該調用handleEditorClick函數,該函數在script元素中已經進行了定義,以下所示:

...
function handleEditorClick() {
    sendAjaxRequest("POST", function (newItem) {
        model.reservations.push(newItem);
    }, null, {
        ClientName: model.editor.name,
        Location: model.editor.location
    });
}
...

The event handler function calls the sendAjaxRequest function. The callback adds the newly created data object sent back from the server to the model. I send an object containing the new model properties to the sendAjaxRequest function, which I have extended so that it will send them to the server as part of the Ajax request, using the data option property.
該事件處理器函數調用了sendAjaxRequest函數。其中的回調函數將服務器回發過來的新建數據對象添加到模型。在sendAjaxRequest函數中,發送了一個含有新模型屬性的對象,以前已經對該對象進行了擴展,以使它隨同Ajax請求一塊兒發送給服務器,這使用了data選項屬性。

4. Testing the Create Feature
4. 測試Create特性

You can see how the Knockout implementation of the create feature works by starting the application, entering a name and location into the input elements and clicking the Save button, as illustrated by Figure 27-6.
啓動應用程序,在input元素中輸入姓名和地點,並點擊Save按鈕,即可以看到上述Knockout實現的Create特性的工做狀況,如圖27-6所示。

圖27-6

Figure 27-6. Creating a new reservation
圖 27-6. 建立新預定

27.6 Completing the Application
27.6 完成應用程序

Now that you have seen how I can apply Knockout and the Web API to create a single-page application, I am going to finish this chapter by completing the application to add some missing features and remove some of the quirks.
如今已經看到,能夠運用Knockout和Web API來建立一個單頁應用程序,本章的其他部分打算添加一些缺失的特性,並去除一些怪異的代碼,以完成這一應用程序。

27.6.1 Simplify the Home Controller
27.6.1 簡化Home控制器

The Home controller is still set up with action method to manipulate the repository to retrieve and manage Reservation objects, even though all of the data displayed by the client is being requested via Ajax to the Web API controller. In Listing 27-16, you can see how I have updated the controller to remove the action methods that the Web API controller has replaced. I have also updated the Index action method so that it no longer passes a view model object.
Home控制器仍然是用動做方法來操做存儲庫,以便接收和管理Reservation對象,儘管客戶端所顯示的所有數據都是經過Ajax向Web API控制器發送請求而實現的。在清單27-16中能夠看到如何對該控制器進行更新,在其中刪除了Web API已有的動做方法。也更新了Index動做方法,以使它再也不傳遞視圖模型對象。

Listing 27-16. Removing the Data Selection from the HomeController.cs File
清單 27-16. 去除HomeController.cs文件中的數據選擇

using System.Web.Mvc;
using WebServices.Models;
namespace WebServices.Controllers { public class HomeController : Controller { public ViewResult Index() { return View(); } } }

27.6.2 Manage Content Visibility
27.6.2 管理內容的可見性

The final change I am going to make is to manage the visibility of the elements in the HTML document so that only the summary or the editor is visible. You can see how I have done this in Listing 27-17.
最後要作的一項修改是管理HTML文檔中元素的可見性,以使得只有summaryeditor是可見的。能夠從清單27-17看到我已經作了些什麼。

Listing 27-17. Managing Element Visibility in the Index.cshtml File
清單 27-17. 管理Index.cshtml文件中元素的可見性

@using WebServices.Models
@{ ViewBag.Title = "Reservations"; Layout = "~/Views/Shared/_Layout.cshtml"; }
@section Scripts { <script> var model = { reservations: ko.observableArray(), editor: { name: ko.observable(""), location: ko.observable("") }, displaySummary: ko.observable(true) };
function sendAjaxRequest(httpMethod, callback, url, reqData) { $.ajax("/api/web" + (url ? "/" + url : ""), { type: httpMethod, success: callback, data: reqData }); }
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 () { 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); }
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 }); }
$(document).ready(function () { getAllItems(); ko.applyBindings(model); }); </script> }
@section Body { <div id="summary" class="section panel panel-primary" data-bind="if: model.displaySummary"> <div class="panel-heading">Reservation Summary</div> <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> <button class="btn btn-primary" data-bind="click: handleCreateClick">Create</button> </div> </div> <div id="editor" class="section panel panel-primary" data-bind="ifnot: model.displaySummary"> <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> }

I have added a property to the model that specifies whether the summary should be shown. I use this property with the if and ifnot bindings, which add and remove elements to and from the DOM based on their expression. If the displaySummary property is true, then the data summary will be shown and if it is false, then the editor will be shown. The final changes I made were to add a Create button that calls a function that changes the displaySummary property and an addition to the callback function that processes new items that changes it back again. You can see the final result in Figure 27-7.
我給模型添加了一個屬性(即displaySummary屬性——譯者注),用以指定是否應該顯示summary。對該屬性使用了ififnot綁定,它會根據其中的<表達式>,對DOM進行添加和刪除元素的操做。若是displaySummary屬性爲true,那麼便顯示數據摘要,若爲false,便顯示編輯器。最後所作的修改是添加一個Create按鈕,以便調用一個函數(即handleCreateClick函數——譯者注),用以修改displaySummary屬性。另外還補充了處理新條目的回調函數,將displaySummary屬性再改回來。能夠從圖27-7看到最終的結果。

圖27-7

Figure 27-7. Adding a reservation using the final application
圖 27-7. 在最終的應用程序中添加預定

27.7 Summary
27.7 小結

In this chapter, I showed you how to use the Web API and Knockout to create a single-page application that performs data operations using RESTful Web service. While not part of the MVC Framework, the Web API is modeled so closely on the nature and structure of MVC that it is familiar to MVC developers and, as I demonstrated, Web API controllers can be added alongside regular MVC controllers in an application.
本章展現瞭如何使用Web API和Knockout建立單頁應用程序,以便使用REST化的Web服務執行數據操做。雖然這不屬於MVC框架部分,但Web API的建模十分接近於MVC的性質與結構,這對MVC開發者來講是熟悉的。並且,正如我所演示的,在一個應用程序中,Web API控制器能夠與常規的MVC控制器並存。

And that is all I have to teach you about the MVC Framework. I started by creating a simple application, and then took you on a comprehensive tour of the different components in the framework, showing you how they can be configured, customized, or replaced entirely. I wish you every success in your MVC Framework projects and I can only hope that you have enjoyed reading this book as much as I enjoyed writing it.
以上即是我須要教授的所有MVC框架內容了。本書首先建立一個簡單的應用程序,而後帶領你在框架的不一樣組件中進行了一次綜合性的旅行,展現瞭如何對它們進行配置、定製、或者徹底替換。我但願你的每個MVC框架項目都能得到成功,也但願你能像我愉快地寫做同樣,也能愉快地閱讀。

看完此文若是以爲有所收穫,請給個推薦。 你的推薦是我繼續下去的動力,也會讓更多人關注並獲益,這也是你的貢獻。

相關文章
相關標籤/搜索