在傳統(tǒng)的web應用程序中,客戶端(瀏覽器)通過請求頁面來啟動與服務器的通信。然后服務器處理該請求,并發(fā)送HTML頁面到客戶端。在隨后頁面上的操作中——例如,用戶導航到一個鏈接或提交一個包含數據的表單——一個新的請求便被發(fā)送到服務器,并且重新開始了數據流:服務器處理請求,并將新頁面發(fā)送到瀏覽器以響應客戶端的新動作請求。
在單頁面應用程序(SPA)中,在初始化請求后整個頁面在瀏覽器中被加載出來,但通過Ajax請求來進行后續(xù)的交互。這意為著部分頁面改變后瀏覽器僅需要更新,而不需要整個重新加載。SPA實現(xiàn)減少了應用程序對于用戶動作的響應時間,從而制造了跟流暢的體驗。
在這個動手實驗中,你將使用這些先進技術的優(yōu)勢來實現(xiàn)Geek Quiz,這是一個基于SPA概念的網站。你將首先用ASP.NET Web API實現(xiàn)服務層以暴露所需的操作來檢索問題和存儲答案。然后,你將使用AngularJS和CSS3的變換效果來構建豐富多彩的UI。
在本動手實驗中,你將學習到: 1, 創(chuàng)建ASP.NET Web API服務來發(fā)送和接受JSON數據 2, 使用AngularJS來創(chuàng)建響應的UI 3, 使用CSS3變化來加強UI體驗
以下是完成這個動手實驗所必須的: Visual Studio Express 2013 for Web 或更先進的版本
為了在這個動手實驗中運行這個項目,你需要先安裝好開發(fā)環(huán)境: 1, 打開資源管理器并跳轉到lab’s的Source文件夾。 2, 右擊Setup.cmd并選擇Run as administrator來運行安裝程序,這會配置你的開發(fā)環(huán)境,并為本實驗安裝Visual Studio代碼片(code snippet)。 3, 如果彈出了用戶控制窗口,請確認該操作。 備注:在運行安裝前請確保你已經檢查了本實驗所需的所以依賴項。
在實驗文檔各處,你都被提示去插入代碼塊。為了更便利,所有這些代碼都通過Visual Studio Code Snippets提供,這能夠讓你在Visual Studio 2013下讀取這些代碼,而不用手動來做。
備注:每個練習都伴隨著該練習Begin文件夾下的開始解決方案,這能讓你分別完成每個練習。你要知道這些在練習過程中加入的代碼片可能在開始解決方案中被遺失或在你完成練習前無法工作。在每個練習的源碼中,你也會發(fā)現(xiàn)一個包含了完成相應聯(lián)系所需的剩余步驟的Visual Studio解決方案的End文件夾。如果你在完成這個動手實驗過程中需要額外的幫助,你就可以將這些解決方案當作指導。
該動手實驗包含以下練習: 1, Creating a Web API 2, Creating a SPA Interface
預計完成本實驗的時間:60分鐘
備注:當你第一次啟動Visual Studio時,你必須選擇一個預定義的設置。每個預定義都被設計成匹配不同的開發(fā)風格,并且決定了窗體布局、編輯器行為、智能感知代碼片和對話框選項。當使用General Development Settings集合時,實驗過程中會給出動作描述以幫助在Visual Studio中完成給定的任務。如果你為你的開發(fā)環(huán)境選擇了不同的設置,那么你該考慮到在接下來的步驟中可能會有差別。
服務層是SPA的一個關鍵部分,它能夠處理來自UI發(fā)送的Ajax請求和在響應調用時返回數據。數據的返回應該是機器可讀的格式,這樣才能被客戶端解析和使用。
Web API框架是ASP.NET棧的一部分,并被設計成更容易地實現(xiàn)HTTP服務,通常是通過RESTful API發(fā)送和接收JSON或XML格式的數據。在本練習中,你將創(chuàng)建一個Web站點用以托管Geek Quiz應用程序,然后使用ASP.NET Web API實現(xiàn)后臺服務用以暴露和維持知識競賽(quiz)的數據。
任務1:為Geek Quiz創(chuàng)建初始項目
在本任務中,你將基于Visual Studio的One ASP.NET項目類型來創(chuàng)建一個支持ASP.NET Web API的新的ASP.NET MVC項目。One ASP.NET合并了所有ASP.NET技術,并給予你隨意搭配和選擇的權利。你將添加Entity Framework的模板類和添加quiz問題的數據庫。
在Solution Explorer,右擊GeekQuiz項目的Models文件,并選擇Add | Existing Item...
備注:通過添加這些文件,你其實就為Geek Quiz應用程序添加了數據模型、Entity Framework數據庫上下文和數據庫初始化類。
Entity Framework(EF)是一個對象關系映射,它使你能夠通過對抽象化的應用程序模型編程來取代關系型存儲編程來創(chuàng)建數據庫訪問。
以下是你剛剛添加的這些類的描述:
TriviaOption: 代表了對應于quiz問題的單個選項
TriviaQues:代表了一個quiz問題和通過Options屬性暴露了該問題相關的選項
TriviaAnswer: 代表了對應于一個quiz問題的用戶選擇的選項
TriviaContext: 代表了Geek Quiz應用程序的Entity Framework數據庫上下文。這個類從DContext繼承而來并暴露了DbSet屬性,后者表示以上所描述的實體對象集合。
TriviaDatabaseInitializer: 通過繼承自CreateDatabaseIfNotExists為TriviaContext類實現(xiàn)了Entity Framework初始化功能。該類的默認行為是如果數據庫不存在則創(chuàng)建它,以及在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狀態(tài)碼401(Unauthorized)而不會執(zhí)行用戶操作。你可以將過濾器應用成全局的,也可以是控制器級別的,也可以是獨立操作級別的。
9 現(xiàn)在你應該來修改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節(jié)點。用以下高亮代碼來修改
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - Geek Quiz</p>
</footer>
</div>
任務2:創(chuàng)建名為TriviaController的Web API
在前一個任務中,你已經創(chuàng)建了Geek Quiz這個web應用程序的初始結構?,F(xiàn)在你要建立一個簡單的Web API服務,用以和quiz的數據模型互動并暴露以下動作: GET /api/trivia: 從quiz列表取出已認證用戶的下一個問題來讓用戶回答 POST /api/trivia: 為已認證用戶逐一存儲quiz答案 你將使用Visual Studio提供的ASP.NET支架工具來為Web API控制器類創(chuàng)建基線。 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節(jié)點被選中。然后選擇中間面板的Web API 2 Controller – Empty模板并點擊Add。
備注:ASP.NET Scafolding是一個ASP.NET Web應用程序的代碼生成框架。Visual Studio 2013對于MVC和Web API項目包含了預安裝的代碼生成器。當你想快速添加與數據模型交互的代碼以減少開發(fā)標準的數據操作所需的時間,你就可以在項目中使用支架(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方法執(zhí)行TriviaContext實例的Dispose方法,它會確保當TriviaContext實例被創(chuàng)建或垃圾回收時,所有被上下文對象使用的資源都被釋放掉。這也包括了被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動作方法。這個動作方法將答案和認證用戶聯(lián)系起來,并調用StoreAsync方法。然后,它會根據幫助方法的返回值發(fā)送一個包含布爾變量的響應。
// 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頁面應該會出現(xiàn)在瀏覽器上。 備注:當應用程序啟動時,默認的MVC路由被觸發(fā),它會默認地映射到HomeController類的Index動作上。因為HomeController被約束為認證用戶(在練習1中你已經將Authorize屬性添加到該類上),并且現(xiàn)在還沒有用戶經過認證,所以應用程序會向登錄頁面發(fā)送原始請求。
2 點擊Register來創(chuàng)建一個新用戶。
3 在Register頁面,輸入一個用戶名和密碼,然后點擊Register。
4 接下來應用程序就會注冊一個新賬號,并且該賬號是經過認證的,隨后會自動導航到主頁。
5 在瀏覽器上按F12以打開Developer Tools面板。按Ctrl+4或點擊Network圖標,然后點擊綠色箭頭按鈕以開始捕獲網絡傳輸。
6 在瀏覽器地址欄的URL上添加api/trivia。你將會檢測到來自TriviaController里的Get動作的響應細節(jié)。
備注:一旦下載完成,你將會被提示對下載的文件進行操作。留著這個對話框,為了在Developers Tool窗口查看響應的內容。 7 現(xiàn)在你可以檢測到響應體了。點擊Details,然后點擊Response body。你能發(fā)現(xiàn)下載的數據是一個對應著TriviaQuestion類的options(它是一個TriviaOption對象的列表)、id和title屬性的對象。
8 返回到Visual Studio并按Shift+F5以停止調試。