前言
在傳統的web應用程式中,用戶端(浏覽器)通過請求頁面來啟動與伺服器的通信。然後伺服器處理該請求,并發送HTML頁面到用戶端。在随後頁面上的操作中——例如,使用者導航到一個連結或送出一個包含資料的表單——一個新的請求便被發送到伺服器,并且重新開始了資料流:伺服器處理請求,并将新頁面發送到浏覽器以響應用戶端的新動作請求。
在單頁面應用程式(SPA)中,在初始化請求後整個頁面在浏覽器中被加載出來,但通過Ajax請求來進行後續的互動。這意為着部分頁面改變後浏覽器僅需要更新,而不需要整個重新加載。SPA實作減少了應用程式對于使用者動作的響應時間,進而制造了跟流暢的體驗。
在這個動手實驗中,你将使用這些先進技術的優勢來實作Geek Quiz,這是一個基于SPA概念的網站。你将首先用ASP.NET Web API實作服務層以暴露所需的操作來檢索問題和存儲答案。然後,你将使用AngularJS和CSS3的變換效果來建構豐富多彩的UI。
回顧
收獲
在本動手實驗中,你将學習到:
1, 建立ASP.NET Web API服務來發送和接受JSON資料
2, 使用AngularJS來建立響應的UI
3, 使用CSS3變化來加強UI體驗
前提工作
以下是完成這個動手實驗所必須的:
Visual Studio Express 2013 for Web 或更先進的版本
安裝
為了在這個動手實驗中運作這個項目,你需要先安裝好開發環境:
1, 打開資料總管并跳轉到lab’s的Source檔案夾。
2, 右擊Setup.cmd并選擇Run as administrator來運作安裝程式,這會配置你的開發環境,并為本實驗安裝Visual Studio代碼片(code snippet)。
3, 如果彈出了使用者控制視窗,請确認該操作。
備注:在運作安裝前請確定你已經檢查了本實驗所需的是以依賴項。
相關的開發環境的官方下載下傳位址是:
官方GitHub位址使用代碼片
在實驗文檔各處,你都被提示去插入代碼塊。為了更便利,所有這些代碼都通過Visual Studio Code Snippets提供,這能夠讓你在Visual Studio 2013下讀取這些代碼,而不用手動來做。
備注:每個練習都伴随着該練習Begin檔案夾下的開始解決方案,這能讓你分别完成每個練習。你要知道這些在練習過程中加入的代碼片可能在開始解決方案中被遺失或在你完成練習前無法工作。在每個練習的源碼中,你也會發現一個包含了完成相應聯系所需的剩餘步驟的Visual Studio解決方案的End檔案夾。如果你在完成這個動手實驗過程中需要額外的幫助,你就可以将這些解決方案當作指導。
練習
該動手實驗包含以下練習:
1, Creating a Web API
2, Creating a SPA Interface
預計完成本實驗的時間:60分鐘
備注:當你第一次啟動Visual Studio時,你必須選擇一個預定義的設定。每個預定義都被設計成比對不同的開發風格,并且決定了窗體布局、編輯器行為、智能感覺代碼片和對話框選項。當使用General Development Settings集合時,實驗過程中會給出動作描述以幫助在Visual Studio中完成給定的任務。如果你為你的開發環境選擇了不同的設定,那麼你該考慮到在接下來的步驟中可能會有差别。
練習1:建立Web API
服務層是SPA的一個關鍵部分,它能夠處理來自UI發送的Ajax請求和在響應調用時傳回資料。資料的傳回應該是機器可讀的格式,這樣才能被用戶端解析和使用。
Web API架構是ASP.NET棧的一部分,并被設計成更容易地實作HTTP服務,通常是通過RESTful API發送和接收JSON或XML格式的資料。在本練習中,你将建立一個Web站點用以托管Geek Quiz應用程式,然後使用ASP.NET Web API實作背景服務用以暴露和維持知識競賽(quiz)的資料。
任務1:為Geek Quiz建立初始項目
在本任務中,你将基于Visual Studio的One ASP.NET項目類型來建立一個支援ASP.NET Web API的新的ASP.NET MVC項目。One ASP.NET合并了所有ASP.NET技術,并給予你随意搭配和選擇的權利。你将添加Entity Framework的模闆類和添加quiz問題的資料庫。
- 打開Visual Stuio Express 2013 for Web,并選擇File | New Project… 來建立一個新解決方案。
- 在New Project對話框,選擇Visual C# | Web節點下的ASP.NET Web Application。請確定選擇了.NET Framework,命名為GeekQuiz,選擇一個Location并點選OK。
- 在New ASP.NET Project對話框,選擇MVC模闆并選擇Web API選項。同樣請確定Authentication選項被設定到Individual User Account。點選OK以繼續。
- 在Solution Explorer,右擊GeekQuiz項目的Models檔案,并選擇Add | Existing Item…
- 在Add Existing Item對話框,導航到Source/Assets/Model檔案夾,并選中所有檔案。點選Add。
備注:通過添加這些檔案,你其實就為Geek Quiz應用程式添加了資料模型、Entity Framework資料庫上下文和資料庫初始化類。
Entity Framework(EF)是一個對象關系映射,它使你能夠通過對抽象化的應用程式模型程式設計來取代關系型存儲程式設計來建立資料庫通路。
以下是你剛剛添加的這些類的描述:
TriviaOption: 代表了對應于quiz問題的單個選項
TriviaQues:代表了一個quiz問題和通過Options屬性暴露了該問題相關的選項
TriviaAnswer: 代表了對應于一個quiz問題的使用者選擇的選項
TriviaContext: 代表了Geek Quiz應用程式的Entity Framework資料庫上下文。這個類從DContext繼承而來并暴露了DbSet屬性,後者表示以上所描述的實體對象集合。
TriviaDatabaseInitializer: 通過繼承自CreateDatabaseIfNotExists為TriviaContext類實作了Entity Framework初始化功能。該類的預設行為是如果資料庫不存在則建立它,以及在Seed方法内插入實體對象。
6 打開Global.asax.cs檔案,并添加以下using語句。
using GeekQuiz.Models;
7 在Application_Start方法的開始添加以下代碼,它将TriviaDatabaseInitializer設定成資料庫初始器。
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(new TriviaDatabaseInitializer());
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
8 修改Home控制器以限制對使用者的認證。打開Controller檔案夾下的HomeController.cs檔案,并為HomeController類的定義添加Authorize屬性。
namespace GeekQuiz.Controllers
{
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
...
}
}
備注:Authorize過濾器會檢查使用者是否是經過認證的。如果使用者未認證,它會傳回HTTP狀态碼401(Unauthorized)而不會執行使用者操作。你可以将過濾器應用成全局的,也可以是控制器級别的,也可以是獨立操作級别的。
9 現在你應該來修改web頁面布局和綁定。打開Views | Shared檔案夾下的_Layout.cshtml檔案,通過将My ASP.NET Application修改成Geek Quiz來更新title元素的内容。
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - Geek Quiz</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
10 在同一個檔案中,通過移除About與Contact連結并重命名Home連結為Play來更新導航欄。另外,将Applicaiton name連結也修改成Geek Quiz。導航欄的HTML代碼應該看起來是下面這樣的:
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Geek Quiz", "Index", "Home", null, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Play", "Index", "Home")</li>
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
</div>
11 通過将My ASP.NET Application替換成Geek Quiz以更新頁面布局的footer節點。用以下高亮代碼來修改
元素的内容:
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - Geek Quiz</p>
</footer>
</div>
任務2:建立名為TriviaController的Web API
在前一個任務中,你已經建立了Geek Quiz這個web應用程式的初始結構。現在你要建立一個簡單的Web API服務,用以和quiz的資料模型互動并暴露以下動作:
GET /api/trivia: 從quiz清單取出已認證使用者的下一個問題來讓使用者回答
POST /api/trivia: 為已認證使用者逐一存儲quiz答案
你将使用Visual Studio提供的ASP.NET支架工具來為Web API控制器類建立基線。
1 打開App_Start檔案夾下的WebApiConfig.cs檔案。該檔案定義了Web API服務的配置項,例如路由如何映射到Web API控制器的動作。
2 在檔案首部添加如下using語句。
using Newtonsoft.Json.Serialization;
3 通過在Register方法内添加如下高亮代碼,以全局性配置Web API動作方法傳回的JSON資料格式。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Use camel case for JSON data.
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
備注: CamelCasePropertyNamesContractResolver會自動的将屬性名稱轉換成camel式,它是JavaScript中屬性的簡便名稱。
4 在Solution Explorer,右擊GeekQuiz項目的Controller檔案夾,選擇Add | New Scaffolded Item…
5 在Add Scaffold對話框,請確定左側面闆的Common節點被選中。然後選擇中間面闆的Web API 2 Controller – Empty模闆并點選Add。
備注:ASP.NET Scafolding是一個ASP.NET Web應用程式的代碼生成架構。Visual Studio 2013對于MVC和Web API項目包含了預安裝的代碼生成器。當你想快速添加與資料模型互動的代碼以減少開發标準的資料操作所需的時間,你就可以在項目中使用支架(scaffolding)。
支架(scaffolding)同樣會確定項目中的依賴項都被安裝。例如,如果你從一個空的ASP.NET項目開始,然後使用支架來添加了Web API控制器,與之相關的Web API NuGet包和引用會被自動添加你的項目中。
6 在Add Controller對話框,在Controller name文本框中鍵入TriviaController并點選Add。
7 之後TriviaController.cs檔案就會被添加到GeekQuiz項目的Controller檔案夾下,并包含一個空的TriviaController類。然後在檔案開頭添加以下using語句。
using System.Data.Entity;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Description;
using GeekQuiz.Models;
8 在TriviaController類開頭添加以下代碼,在控制器中定義、初始化和配置TriviaContext執行個體。
public class TriviaController : ApiController
{
private TriviaContext db = new TriviaContext();
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.db.Dispose();
}
base.Dispose(disposing);
}
}
備注:TriviaController的Dispose方法執行TriviaContext執行個體的Dispose方法,它會確定當TriviaContext執行個體被建立或垃圾回收時,所有被上下文對象使用的資源都被釋放掉。這也包括了被Entity Framework打開的資料庫連接配接。
9 在TrivialController類的結尾加上以下helper方法。這個方法根據具體使用者從資料庫中取出quiz問題給使用者回答。
private async Task<TriviaQuestion> NextQuestionAsync(string userId)
{
var lastQuestionId = await this.db.TriviaAnswers
.Where(a => a.UserId == userId)
.GroupBy(a => a.QuestionId)
.Select(g => new { QuestionId = g.Key, Count = g.Count() })
.OrderByDescending(q => new { q.Count, QuestionId = q.QuestionId })
.Select(q => q.QuestionId)
.FirstOrDefaultAsync();
var questionsCount = await this.db.TriviaQuestions.CountAsync();
var nextQuestionId = (lastQuestionId % questionsCount) + 1;
return await this.db.TriviaQuestions.FindAsync(CancellationToken.None, nextQuestionId);
}
10 在TrivialController類中添加以下Get動作的方法。這個動作的方法調用了在上一步中定義的用于為認證使用者取出下一個問題的NextQuestionAsync方法。
// GET api/Trivia
[ResponseType(typeof(TriviaQuestion))]
public async Task<IHttpActionResult> Get()
{
var userId = User.Identity.Name;
TriviaQuestion nextQuestion = await this.NextQuestionAsync(userId);
if (nextQuestion == null)
{
return this.NotFound();
}
return this.Ok(nextQuestion);
}
11 在TriviaController類中添加以下helper方法。
private async Task<bool> StoreAsync(TriviaAnswer answer)
{
this.db.TriviaAnswers.Add(answer);
await this.db.SaveChangesAsync();
var selectedOption = await this.db.TriviaOptions.FirstOrDefaultAsync(o => o.Id == answer.OptionId
&& o.QuestionId == answer.QuestionId);
return selectedOption.IsCorrect;
}
12 在TriviaController類中添加以下Post動作方法。這個動作方法将答案和認證使用者聯系起來,并調用StoreAsync方法。然後,它會根據幫助方法的傳回值發送一個包含布爾變量的響應。
// POST api/Trivia
[ResponseType(typeof(TriviaAnswer))]
public async Task<IHttpActionResult> Post(TriviaAnswer answer)
{
if (!ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
answer.UserId = User.Identity.Name;
var isCorrect = await this.StoreAsync(answer);
return this.Ok<bool>(isCorrect);
}
13 通過向TriviaContorller的類定義添加Authorize屬性,來修改Web API控制器以限制對認證使用者的通路。
[Authorize]
public class TriviaController : ApiController
{
...
}
任務3:運作解決方案
在本任務中,你将核實你在上一個任務重建立的Web API服務是否如預期般工作。你将會使用Internet Explorer的F12 Developer Tools來捕獲網絡資料傳輸和檢測來着Web API服務的全部響應。
備注:確定Visual Studio工具欄的Start按鈕上被選中為Internet Explorer。
1 按F5以運作解決方案。Log in頁面應該會出現在浏覽器上。
備注:當應用程式啟動時,預設的MVC路由被觸發,它會預設地映射到HomeController類的Index動作上。因為HomeController被限制為認證使用者(在練習1中你已經将Authorize屬性添加到該類上),并且現在還沒有使用者經過認證,是以應用程式會向登入頁面發送原始請求。
2 點選Register來建立一個新使用者。
3 在Register頁面,輸入一個使用者名和密碼,然後點選Register。
4 接下來應用程式就會注冊一個新賬号,并且該賬号是經過認證的,随後會自動導航到首頁。
5 在浏覽器上按F12以打開Developer Tools面闆。按Ctrl+4或點選Network圖示,然後點選綠色箭頭按鈕以開始捕獲網絡傳輸。
6 在浏覽器位址欄的URL上添加api/trivia。你将會檢測到來自TriviaController裡的Get動作的響應細節。
備注:一旦下載下傳完成,你将會被提示對下載下傳的檔案進行操作。留着這個對話框,為了在Developers Tool視窗檢視響應的内容。
7 現在你可以檢測到響應體了。點選Details,然後點選Response body。你能發現下載下傳的資料是一個對應着TriviaQuestion類的options(它是一個TriviaOption對象的清單)、id和title屬性的對象。
8 傳回到Visual Studio并按Shift+F5以停止調試。