創建基本的鏈接和URL<!--?xml:namespace prefix = "o" ns = "urn:schemas-microsoft-com:office:office" /--> 在我們介紹鏈接或URL之前先做一些準備,我們這部分要介紹的知識將要使用的項目就是之前建立的HelperMeth ...
創建基本的鏈接和URL
在我們介紹鏈接或URL之前先做一些準備,我們這部分要介紹的知識將要使用的項目就是之前建立的HelperMethods項目,現在需要先為其添加一個People控制器,併在其中定義一個Person模型對象的集合,具體如下:
using HelperMethods.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HelperMethods.Controllers { public class PeopleController: Controller { private Person[] personData = { new Person{FirstName = "Adam",LastName = "Freeman",Role = Role.Admin}, new Person{FirstName = "Steven",LastName = "Sanderson",Role = Role.Admin}, new Person{FirstName = "Jacqui",LastName = "Griffyth",Role = Role.User}, new Person{FirstName = "John",LastName = "Smith",Role = Role.User}, new Person{FirstName = "Anne",LastName = "Jones",Role = Role.Guest} }; public ActionResult Index() { return View(); } public ActionResult GetPeople() { return View(personData); } public ActionResult GetPeople(string selectedRole) { if (selectedRole == null || selectedRole == "All") { return View(personData); } else { Role selected = (Role)Enum.Parse(typeof(Role), selectedRole); return View(personData.Where(p => p.Role == selected)); } } } }
將會使用的CSS如下(添加在/Content/Site.css,在/Views/Shared/_Layout.cshtml文件中有一個引用此文件的link元素):
… table, td, th { border: thin solid black; border-collapse: collapse; padding: 5px; background-color: lemonchiffon; text-align: left; margin: 10px 0; } div.load { color: red; margin: 10px 0; font-weight: bold; } div.ajaxLink { margin-top: 10px; margin-right: 5px; float: left; } …
創建鏈接或URL是視圖的常見功能,它能夠幫用戶導航至程式的其他部分。下表總結了一些常見的HTML輔助器,大家需要知道使用這些輔助器方法生成鏈接和URL有個好處就是:輸出來自路由配置,這意味著路由的改變會自動反映在鏈接和URL中。
描述 |
示例 |
相對於程式的URL |
Url.Content ("∼/Content/Site.css") 輸出: /Content/Site.css |
鏈接到指定的動作/控制器 (輸出鏈接) |
Html.ActionLink("My Link", "Index", "Home") 輸出: <a href "/">My Link</a> |
動作URL (輸出URL) |
Url.Action ("GetPeople", "People") 輸出: /People/GetPeople |
使用路由數據的URL |
Url.RouteUrl(new {controller = "People", action = "GetPeople"}) 輸出: /People/GetPeople |
使用路由數據的鏈接 |
Html.RouteLink("My Link", new {controller = "People", action = "GetPeople"}) 輸出: <a href "/People/GetPeople">My Link</a> |
鏈接到指定路由 |
Html.RouteLink("My Link", "FormRoute", new {controller = "People", action = "GetPeople"}) 輸出: <a href "/app/forms/People/GetPeople">My Link</a> |
後面我們創建一個視圖來觀察一下上述表格中的輔助器方法的使用及其效果:
@{ ViewBag.Title = "Index"; } <h2>Basic Links Á URLs</h2> <table> <thead><tr><th>Helper</th><th>Output</th></tr></thead> <tbody> <tr> <td>Url.Content("~/Content/Site.css")</td> <td>@Url.Content("~/Content/Site.css")</td> </tr> <tr> <td>Html.ActionLink("My Link", "Index", "Home")</td> <td>@Html.ActionLink("My Link", "Index", "Home")</td> </tr> <tr> <td>Url.Action("GetPeople", "People")</td> <td>@Url.Action("GetPeople", "People")</td> </tr> <tr> <td>Url.RouteUrl(new { controller = "People", action = "GetPeople" })</td> <td>@Url.RouteUrl(new { controller = "People", action = "GetPeople" })</td> </tr> <tr> <td>Html.RouteLink("My Link", new {controller = "People", action = "GetPeople"})</td> <td>@Html.RouteLink("My Link", new { controller = "People", action = "GetPeople" })</td> </tr> <tr> <td>Html.RouteLink("My Link", "FormRoute", new {controller = "People", action = "GetPeople"})</td> <td>@Html.RouteLink("My Link", "FormRoute", new { controller = "People", action = "GetPeople" })</td> </tr> </tbody> </table>
最終運行效果如:
使用MVC的漸進式Ajax
Ajax即Asynchronous JavaScript and XML(非同步JavaScript與XML)的縮寫。相對於其XML部分,其非同步部分更為重要,且用途更廣。MVC框架實現了對其支持,這也方便了我們的開發。
創建同步表單視圖
下麵先對GetPeople動作方法創建視圖,內容如下:
@using HelperMethods.Models @model IEnumerable<Person> @{ ViewBag.Title = "GetPeople"; } <h2>Get People</h2> <table> <thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead> <tbody> @foreach (Person p in Model) { <tr> <td>@p.FirstName</td> <td>@p.LastName</td> <td>@p.Role</td> </tr> } </tbody> </table> @using (Html.BeginForm()) { <div> @Html.DropDownList("selectedRole", new SelectList(new[] { "All" }.Concat(Enum.GetNames(typeof(Role))))) <button type="submit">提交</button> </div> }
效果如下圖:
這是一個HTML表單基本限制的一個簡單示例,它會在提交時重載整個頁面。這在真實環境中將會帶來很大的代價,而且,在頁面重載的時候還不能執行其他操作,我們只能等待最終展示完成。所以,也就需要一個能夠非同步載入的方法了,後面就來看看這一技術的使用。
為漸進式Ajax準備項目
若要使用漸進式Ajax特性的,需要在程式中兩個地方配合創建。
第一:在/Web.config文件(項目根文件夾中的那個)中,configuration/appSettings元素含有一個用於UnobtrusiveJavaScriptEnabled屬性的項,必須將其設置為true(MVC框架為創建新項目是預設也會是一個true值),如:
<configuration> … <appSettings> <add key="webpages:Version" value="2.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="PreserveLoginUrl" value="true" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> </appSettings> … </configuration>
第二:添加對實現漸進式Ajax功能的jQuery JavaScript庫的引用。我們在開發時可以在單個視圖中引用該庫,但更常用的一種方式是在佈局文件中進行引用,以便實現在使用該佈局的視圖中能夠通用。下麵是在/Views/Shared/_Layout.cshtml文件添加了兩個對JavaScript庫的引用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<link href="~/Content/Site.css" rel="stylesheet" />
<style type="text/css">
label {
display: inline-block;
width: 100px;
}
.dateElem {
margin: 5px;
}
h2 > label {
width: inherit;
}
.editor-label, .editor-field {
float: left;
}
.editor-label, .editor-label label, .editor-field input {
height: 20px;
}
.editor-label {
clear: left;
}
.editor-field {
margin-left: 10px;
margin-top: 10px;
}
input[type=submit] {
float: left;
clear: both;
margin-top: 10px;
}
.column {
float: left;
margin: 10px;
}
</style>
<script src="~/Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>
</head>
<body>
@RenderBody()
</body>
</html>
當使用Basic模板選項創建MVC項目時,Visual Studio會將這些以script元素進行引用的文件添加到項目的Scripts文件夾中。(註意,這裡添加的是min版本的js文件,這種版本是壓縮版,在部署到伺服器上時使用這種版本可以增加網站的載入效率,但是開發的時候建議使用普通版,這樣方便閱讀源代碼。)
創建漸進式Ajax表單
前面已經做好了創建漸進式Ajax特性的準備,後面我們通過以等價Ajax逐步替換常規的同步表單的方式實現一個Ajax表單來瞭解漸進式Ajax特性的工作原理。
準備控制器
這裡需要實現的是當點擊提交按鈕時,只有HTML的table元素中的數據被更新。因此,就需要我們首先重構People控制器中的動作方法,以便通過一個子動作僅獲取想要的數據:
using HelperMethods.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace HelperMethods.Controllers { public class PeopleController : Controller { private Person[] personData = { new Person{FirstName = "Adam",LastName = "Freeman",Role = Role.Admin}, new Person{FirstName = "Steven",LastName = "Sanderson",Role = Role.Admin}, new Person{FirstName = "Jacqui",LastName = "Griffyth",Role = Role.User}, new Person{FirstName = "John",LastName = "Smith",Role = Role.User}, new Person{FirstName = "Anne",LastName = "Jones",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); } } }
此時,我們刪去了GetPeople動作方法的HttpPost版本,僅保留了一個GetPeople動作方法。並新增了GetPeopleData動作,用於選擇需要顯示的Person對象,並傳遞給PartialView方法,以便生成所需的表格行。
好了,再為GetPeopleData動作方法創建一個新的分部視圖文件即可(視圖文件路徑:/Views/People/GetPeopleData.cshtml),該分部視圖僅負責生成填充表格的tr元素:
@using HelperMethods.Models @model IEnumerable<Person> @foreach (Person p in Model) { <tr> <td>@p.FirstName</td> <td>@p.LastName</td> <td>@p.Role</td> </tr> }
更新一下/Views/People/GetPeople.cshtml視圖:
@using HelperMethods.Models @model string @{ ViewBag.Title = "GetPeople"; } <h2>Get People</h2> <table> <thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead> <tbody> @Html.Action("GetPeopleData", new { selectedRole = Model }) </tbody> </table> @using (Html.BeginForm()) { <div> @Html.DropDownList("selectedRole", new SelectList(new[] { "All" }.Concat(Enum.GetNames(typeof(Role))))) <button type="submit">提交</button> </div> }
創建Ajax表單
目前雖然仍是一個同步表單,但已經在控制器中將功能分離開了,使得可以通過GetPeopleData動作只請求表格的行。
現在來看看如何使用Ajax請求這一目標動作:
@using HelperMethods.Models @model string @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody" }; } <h2>Get People</h2> <table id="tableBody"> <thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead> <tbody> @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">提交</button> </div> }
其實,MVC框架支持Ajax表單的核心在於Ajax.BeginForm輔助器方法,它可以接收一個AjaxOptions對象。很多人喜歡在視圖開始出以Razor代碼塊的形式創建該對象,但如果願意,直接在調用Ajax.BeginForm時內聯地創建也並非不可。
AjaxOptions對象擁有一組常用屬性,用來配置以何種形式向伺服器發送非同步請求,以及對取回的數據做哪些處理,下表列舉了這些屬性:
屬性 |
描述 |
Confirm |
在形成Ajax請求之前,設置顯示給用戶的確認視窗中的消息 |
HttpMethod |
設置用來形成請求的HTTP方法——必須是GET或Post |
InsertionMode |
指定從伺服器接受的內容以何種方式插入到HTML。有三種選擇:InsertAfter、InsertBefore和Replace(預設值) |
LoadingElementId |
指定HTML元素的ID,這是執行Ajax請求期間要顯示的HTML元素 |
LoadingElementDuration |
指定動畫的持續時間,用於顯露由LoadingElementId指定的元素 |
UpdateTargetId |
設置HTML元素的ID,從伺服器接收的內容將被插入到該元素中 |
Url |
設置所請求的伺服器端URL |
AjaxOptions還有一些屬性,能夠指定請求生命周期不同階段的回調。後面將重點介紹。
理解漸進式Ajax的工作機制
在調用Ajax.BeginForm輔助器方法時,用AjaxOptions對象指定的選項被轉換成運用於form元素的標簽屬性,下麵是一段生成的form元素結果:
<form id="form0" action="/People/GetPeopleData" method="post" data-ajax-update="#tableBody" data-ajax-mode="replace" data-ajax="true">
…
</form>
當瀏覽器載入GetPeopleData.cshtml視圖渲染的HTML頁面時,jquery.unobtrusive-ajax.js庫中的JavaScript會掃描這些HTML元素,通過考察data-ajax標簽屬性為true的元素,能夠識別出這是一個Ajax表單。
其他以data-ajax開頭的標簽屬性含有用AjaxOptions類指定的值。這些配置選項被用於配置jQuery,jQuery具有對Ajax請求進行管理的內建支持。
提示:並不是必須使用MVC框架的漸進式Ajax支持。還有其他辦法,包括直接使用jQuery。但是,建議選用一種技術,最好堅持下去,不要在同一個視圖中將MVC框架的漸進式Ajax支持與其他技術混合使用,這可能導致一些不適宜的交互影響。比如:可能會複製或丟棄Ajax請求。
設置Ajax選項
確保優雅降級
前面已經實現了Ajax的非同步請求,但這麼做也有一個問題,那就是如果用戶禁用JavaScript(或使用不支持JavaScript的瀏覽器),就不能正常工作了,此時,提交表單的一個結果就是使得瀏覽器放棄當前的HTML頁面,並用目標動作方法返回的片段代替,如下圖:
我們可以通過使用AjaxOptions.Url屬性,以便指定非同步請求的目標URL作為Ajax.BeginForm方法的參數,而不是以動作名稱作為其參數的方式解決這個問題:
@using HelperMethods.Models @model string @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData") }; } <h2>Get People</h2> <table id="tableBody"> <thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead> <tbody> @Html.Action("GetPeopleData", new { selectedRole = Model }) </tbody> </table> @using (Ajax.BeginForm(ajaxOpts)) { <div> @Html.DropDownList("selectedRole", new SelectList(new[] { "All" }.Concat(Enum.GetNames(typeof(Role))))) <button type="submit">提交</button> </div> }
通過上面的這種用法,會產生這樣的效果:如果未啟用JavaScript,則創建一個回遞給原始動作方法的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">
…
</form>
如果啟用了JavaScript,則漸進式Ajax庫會以data-ajax-url標簽屬性為目標URL,該屬性引用了子動作。如果禁用了JavaScript,則瀏覽器會使用常規表單的遞交技術,其目標URL取自action屬性,它會回遞給生成完整HTML頁面的動作方法。
在Ajax請求期間給用戶提供反饋
由於使用Ajax進行請求時,請求的動作是在後臺進行的,所以這也造成了用戶無法瞭解正在發生的事情。但是,可以使用AjaxOptions.LoadingElementId和AjaxOptions.LoadingElementDuration屬性通知用戶此刻正在執行的請求。如:
@using HelperMethods.Models @model string @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData"), // 指定 HTML 元素的 ID,這是執行 Ajax 請求期間要顯示的 HTML 元素 LoadingElementId = "loading", // 指定動畫持續的時間,用於顯露由 LoadingElementId 指定的元素 LoadingElementDuration = 1000 }; } <h2>Get People</h2> <div id="loading" class="load" style="display:none"> <p>數據載入中...</p> </div> <table id="tableBody"> <thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead> <tbody> @Html.Action("GetPeopleData", new { selectedRole = Model }) </tbody> </table> @using (Ajax.BeginForm(ajaxOpts)) { <div> @Html.DropDownList("selectedRole", new SelectList(new[] { "All" }.Concat(Enum.GetNames(typeof(Role))))) <button type="submit">提交</button> </div> }
效果如圖:
… @{ ViewBag.Title = "GetPeople"; AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData"), // 指定 HTML 元素的 ID,這是執行 Ajax 請求期間要顯示的 HTML 元素 LoadingElementId = "loading", // 指定動畫持續的時間,用於顯露由 LoadingElementId 指定的元素 LoadingElementDuration = 1000, Confirm = "Do you wish to request new data" }; } …
上面的修改補充了一個提示,它會在用戶每次提交表單時顯示(當然,實際項目中還是要慎重使用這一功能——它可能惹惱用戶哦~):
創建Ajax鏈接
除了表單外,Ajax還有非同步執行的a元素。它十分類似於Ajax表單的工作方式,下麵是在GetPeople.cshtml視圖中添加的Ajax連接:
… <div> @foreach (string role in Enum.GetNames(typeof(Role))) { <div class="ajaxLink"> @Ajax.ActionLink(role, "GetPeopleData", new { selectedRole = role }, new AjaxOptions { UpdateTargetId = "tableBody" }) </div> } </div> …
下麵是這段代碼產生的HTML的a元素片段: