Ajax (Asynchronous JavaScript and XML 的縮寫),如我們所見,這個概念的重點已經不再是XML部分,而是 Asynchronous 部分,它是在後臺從伺服器請求資料的一種模型。MVC 框架內建了對 Unobtrusive Ajax 的支援,它允許我們通過 MVC 的 Help mothod 來定義 Ajax 的特性,而不用在 View 中參雜一大段 JavaScript 程式碼。
本文目錄:
普通 Ajax 使用方式
在講 MVC 中的 Unobtrusive Ajax 之前,我們先來看看 MVC 中 Ajax 的普通使用方式,讀者可以在閱讀後文的時候進行比較學習。
新建一個MVC應用程式(基本模板),新增一個名為 Home 的 controller,為自動生成的 Index action 新增檢視,編輯 Index.cshtml 程式碼如下:
@{ ViewBag.Title = "Index"; } <script type="text/javascript"> function test() { $.ajax({ url: '@Url.Action("GetTestData")', type: "POST", success: function (result) { $("#lblMsg").text(result.msg); } }); } </script> <h2 id="lblMsg"></h2> <input type="button" value="測試" onclick="test();" />
在 HomeController 中新增一個名為 Test 的 action,如下:
public JsonResult GetTestData() { return Json( new { msg = "Datetime from server:" + DateTime.Now.ToString("HH:mm:ss") } ); }
執行程式,點選測試按鈕,我們可以看到用 Ajax 從後臺取回來的時間:
每次點選測試按鈕時間都會重新整理。這個地方有一點需要提醒大家,這個例子中 $.ajax() 方法使用的是 POST 請求,如果要使用 GET 請求,Test action 中呼叫 Json 方法需要設定 JsonRequestBehavior 的值為 AllowGet(預設是 DenyGet),如下:
public JsonResult GetTestData() { return Json( new { msg = "Datetime from server:" + DateTime.Now.ToString("HH:mm:ss") }, JsonRequestBehavior.AllowGet ); }
另外,改成 GET 請求後,多次點選測試按鈕,時間不會重新整理。這是因為 GET 請求在 ASP.NET 中對於相同的URL請求返回的是快取中的資料。
什麼是 Unobtrusive Ajax
Unobtrusive Ajax 是在 Web 頁面使用 JavaScript 的一種通用方式。這個術語沒有明確的定義,但它有如下基本的原則(來自維基百科):
- 行為(JavaScript 程式碼)與 Web 頁面的結構(Html 標記)和表現(CSS樣式)分離。
- JavaScript 最佳實現,解決JavaScript語言本身存在的傳統問題(如缺乏可擴充套件性和開發人員編碼風格不一致性)。
- 解決瀏覽器相容性問題。
為了加深理解,請觀察如下某個 Unobtrusive Ajax 的“結構”部分的一段程式碼:
... <form action="/People/GetPeopleData" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#tableBody" id="form0" method="post"> ...
這是 MVC 開啟 Unobtrusive JavaScript 後呼叫 Ajax.BeginForm 方法生成的程式碼。這段程式碼和 JavaScript 是完全分離的,Html標籤通過一些標記來告訴 JavaScript 所具有什麼樣的行為。分離出來的 JavaScript 檔案(MVC中指引入的jquery.unobtrusive-ajax.min.js檔案)中的程式碼,沒有一句是專門為某個特定的Web頁面中的某個Html元素來編寫的,即所有函式都是通用的。這就是 Unobtrusive Ajax 的核心思想。
相對於普通使用 Ajax 的方式,Unobtrusive Ajax 更容易閱讀,增強了可擴充套件性和一致性,而且方便維護。
使用 MVC Unobtrusive Ajax
在 MVC 中使用 Unobtrusive Ajax ,首先要將其“開啟”,需要做兩個動作。一個是配置根目錄下的 Web.config 檔案,在 configuration/appSettings 節點下的 UnobtrusiveJavaScriptEnabled 值設為 true,如下所示:
... <configuration> <appSettings> ... <add key="UnobtrusiveJavaScriptEnabled" value="true" /> </appSettings> </configuration> ...
UnobtrusiveJavaScriptEnabled 的值在程式建立的時候預設為true,在開發的時候有時候只需要檢查一下。第二個動作就是在需要使用 MVC Unobtrusive Ajax 的 View 中引入jquery庫和jquery.unobtrusive-ajax.min.js檔案,一般更為常見的是在 /Views/Shared/_Layout.cshtml 中引入,如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script> </head> <body> @RenderBody() </body> </html>
現在我們來做一個使用 Unobtrusive Ajax 的例子,從伺服器獲取一個簡單的使用者列表。為此我們需要準備一個Model,如下:
namespace MvcApplication1.Models { public class Person { public string ID { get; set; } public string Name { get; set; } public Role Role { get; set; } } public enum Role { Admin, User, Guest } }
我一般習慣先寫後臺方法,再寫UI。建立一個名為 People 的 controller, 在該 controller 中寫好要用的 action,程式碼如下:
public class PeopleController : Controller { public class PeopleController : Controller { private Person[] personData = { new Person {ID = "ZhangSan", Name = "張三", Role = Role.Admin}, new Person {ID = "LiSi", Name = "李四", Role = Role.User}, new Person {ID = "WangWu", Name = "王五", Role = Role.User}, new Person {ID = "MaLiu", Name = "馬六", Role = Role.Guest} }; public ActionResult Index() { return View(); } public PartialViewResult GetPeopleData(string selectedRole = "All") { IEnumerable<Person> data = personData; if (selectedRole != "All") { Role selected = (Role)Enum.Parse(typeof(Role), selectedRole); data = personData.Where(p => p.Role == selected); } return PartialView(data); } public ActionResult GetPeople(string selectedRole = "All") { return View((object)selectedRole); } } }
這裡新增了 GetPeopleData action方法,根據 selectedRole 獲取使用者資料並傳遞給 PartialView 方法。
接著為 GetPeopleData action 建立一個partial view:/Views/People/GetPeopleData.cshtml ,程式碼如下:
@using MvcApplication1.Models @model IEnumerable<Person> @foreach (Person p in Model) { <tr> <td>@p.ID</td> <td>@p.Name</td> <td>@p.Role</td> </tr> }
再建立我們的主檢視 /Views/People/GetPeople.cshtml,程式碼如下:
@using MvcApplication1.Models @model string @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody" }; } <h2>Get People</h2> <table> <thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead> <tbody id="tableBody"> @Html.Action("GetPeopleData", new { selectedRole = Model }) </tbody> </table> @using (Ajax.BeginForm("GetPeopleData", ajaxOpts)) { <div> @Html.DropDownList("selectedRole", new SelectList( new[] { "All" }.Concat(Enum.GetNames(typeof(Role))))) <button type="submit">Submit</button> </div> }
先是建立了一個 AjaxOptions 物件,通過它的一些屬性(如UpdateTargetId、Url、HttpMethod等)可設定 Ajax 如何請求。這些屬性可見名思意,如 UpdateTargetId 表示呼叫 Ajax 請求後要重新整理的元素(通過元素ID來指定)。然後把需要提交到伺服器的表單包括在 Ajax.BeginForm() 方法內,通過 submit 元素將該表單資料提交到伺服器。
為了執行效果美觀些,我們在 _Layout.cshtml 檔案中為 table 元素新增一些樣式,如下:
... table, td, th { border: thin solid black; border-collapse: collapse; padding: 5px; background-color: lemonchiffon; text-align: left; margin: 10px 0; } ...
執行程式,URL 定位到 /People/GetPeople,在頁面中點選提交按鈕,效果如下:
Ajax.BeginForm 是通過提交表單的方式向伺服器傳送 ajax 請求,MVC中也可以使用 Ajax.ActionLink() 方法生成連結來向伺服器傳送 ajax 請求。下面我們在 GetPeople.cshtml 檢視中增加這種請求方式:
<div> @foreach (string role in Enum.GetNames(typeof(Role))) { @Ajax.ActionLink(role, "GetPeopleData", new {selectedRole = role}, new AjaxOptions {UpdateTargetId = "tableBody"}) @: } </div>
效果和前面是一樣的:
Ajax.ActionLink() 和 Ajax.BeginForm() 不同的是,前者只能通過 Url 引數向伺服器傳送資料。
Unobtrusive Ajax 如何工作
Unobtrusive Ajax 是如何工作的呢?
當呼叫 Ajax.BeginForm 方法後,通過 AjaxOptions 物件設定的屬性將會被轉化成 form 元素的屬性(標記),這些屬性以 data-ajax 開頭,如本示例生成的 form 元素:
<form action="/People/GetPeopleData" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#tableBody" id="form0" method="post"> ...
當 GetPeople.cshtml 檢視載入完成並呈現 Html 頁面時,jquery.unobtrusive-ajax.js 庫尋找所有 data-ajax 屬性值為true的元素,然後根據其他以 data-ajax 開頭的屬性值,jQuery 庫中的函式將知道如何去執行 Ajax 請求。
配置 AjaxOptions
AjaxOptions 類中的屬性告訴 MVC 框架如何生成 Ajax 請求相關的 JavaScript 和 Html 程式碼。它包含如下屬性:
這些屬性 VS 的智慧提示都有很好的解釋,這裡不一個一個講,只選幾個有代表性的講講。
AjaxOptions.Url 屬性
在上面的示例中,我們在 Ajax.BeginForm() 方中指定了 action 名稱引數,MVC 幫我們生成了Ajax請求的Url ( action="/People/GetPeopleData" )。這樣做存在一個問題,當瀏覽器禁用JavaScript的時候,點選提交按鈕頁面將發生新的請求(非Ajax請求 /People/GetPeopleData),這樣伺服器返回的資料將直接替換掉原來的頁面。解決這個問題可以使用 AjaxOptions.Url 屬性,原因是 AjaxOptions.Url 屬性會生成另外一個專門用於 ajax 請求的Url。如下我們對 /Views/People/GetPeople.cshtml 進行簡單的修改:
... @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData") }; } ... @using (Ajax.BeginForm(ajaxOpts)) { ... }
執行後我們看到的是和先前一樣的結果,說明在效果上沒有區別。但它生成的 form 屬性卻不一樣:
<form id="form0" action="/People/GetPeople" method="post" data-ajax-url="/People/GetPeopleData" data-ajax-update="#tableBody" data-ajax-mode="replace" data-ajax="true"> ...
它生成了兩個 Url,分別為 action 屬性 和 data-ajax-url 屬性的值,前者是 Ajax.BeginForm() 方法根據當前 controller 和 action 名稱生成的,後者是 AjaxOptions 的 Url 屬性生成的。當瀏覽器沒有禁用 JavaScript 時,Unobtrusive Ajax JS庫會獲取 data-ajax-url 屬性的值作為 Url 發生 ajax 請求。當瀏覽器禁用了 JavaScript 時,自然 action 屬性的值決定了表示提交的 Url,伺服器將返回原來整個的頁面。雖然區域性未能重新整理,但不會讓使用者覺得網站做得很糟糕。
Ajax 載入資料的同時給使用者反饋
當載入資料需要花較長時間,為了避免假死狀態,應當給使用者一個反饋資訊,如“正在載入...”字樣。在 MVC 的 Unobtrusive Ajax 中通過 AjaxOptions 的 LoadingElementId 和 LoadingElementDuration 兩個屬性可輕鬆做到這一點。修改 GetPeople.cshtml 如下:
@using MvcApplication1.Models @model string @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData"), LoadingElementId = "loading", LoadingElementDuration = 1000, }; } <h2>Get People</h2> <div id="loading" class="load" style="display:none"> <p>Loading Data...</p> </div> ...
不解釋,執行程式看效果:
彈出確認對話方塊
使用MVC中的 Unobtrusive Ajax 彈出確認對話方塊也很方便,設定一下 AjaxOptions.Confirm 屬性的值卻可,如下:
... @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData"), LoadingElementId = "loading", LoadingElementDuration = 1000, Confirm = "Do you wish to request new data?" }; } ...
彈出的對話方塊如下:
Ajax 回撥函式
AjaxOptions 類中的 OnBegin、OnComplete、OnFailure 和 OnSuccess 屬性允許我們在 ajax 請求週期的某個狀態點定義回撥函式。來看具體的用法。
在 GetPeople.cshtml 檔案中加入如下4個回撥函式:
<script type="text/javascript"> function OnBegin() { alert("This is the OnBegin Callback"); } function OnSuccess(data) { alert("This is the OnSuccessCallback: " + data); } function OnFailure(request, error) { alert("This is the OnFailure Callback:" + error); } function OnComplete(request, status) { alert("This is the OnComplete Callback: " + status); } </script>
接著設定 AjaxOptions 物件的4個事件屬性:
...
@{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData"), OnBegin = "OnBegin", OnFailure = "OnFailure", OnSuccess = "OnSuccess", OnComplete = "OnComplete" }; }
...
執行程式,彈出三個訊息框如下:
這四個事件屬性中最常用的就是 OnSuccess 和 OnFailure 兩個屬性了,如我們會經常在 OnSuccess 回撥函式中對返回的 Json 資料進行處理。
其實我個人更傾向於普通的 Ajax 使用方式。Ajax.BeginForm() 和 Ajax.ActionLink() 用的少,習慣用 Html.BeginForm() 或 Html.ActionLink() 和手寫 jQuery ajax 程式碼來代替。
參考:《Pro ASP.NET MVC 4 4th Edition》