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
代码来代替。