Ajax 与 jQuery
摘自《ASP.NET MVC 4 in Action》一章
![]() |
ASP.NET MVC 4 in Action
Jeffrey Palermo, Jimmy Bogard, Eric Hexter, Matthew Hinze, and Jeremy Skinner jQuery 已迅速成为最流行的 JavaScript 库之一,因为它提供了简单而强大的机制来与 HTML DOM 进行交互。本文基于《ASP.NET MVC 4 in Action》的第 12 章,作者们讨论了使用 jQuery 的基础知识,以及如何使用它来向服务器发出异步请求,这些请求可以由 ASP.NET MVC 进行处理。您可以从 manning.com 购买《ASP.NET MVC 4 in Action》的印刷版或电子书,享受 40% 的折扣。在 manning.com 结账时,请使用促销代码 12aspcp。 |
在 Web 应用程序中使用 JavaScript 变得越来越重要,因为人们越来越关注提供丰富的客户端体验。不幸的是,使用原始 JavaScript 可能是一个要求很高的过程。不同的浏览器有不同的特性和限制,这可能使编写跨浏览器 JavaScript 成为一个相当复杂的过程(例如,Internet Explorer 在向元素附加事件方面使用的机制与其他浏览器不同)。除此之外,导航和操作 HTML DOM[1] 可能冗长且复杂。这就是 JavaScript 库的用武之地。
如今有许多流行的 JavaScript 库(包括 jQuery、Prototype、MooTools 和 Dojo),它们都旨在简化 JavaScript 的使用,并帮助规范化跨浏览器 JavaScript 功能。对于相关的示例,我们将使用 John Resig 于 2006 年首次发布的开源 jQuery 库(https://jqueryjs.cn)。
jQuery 已迅速成为最流行的 JavaScript 库之一,因为它提供了简单而强大的机制来与 HTML DOM 进行交互。事实上,jQuery 的流行程度已经很高,以至于微软已经为其代码库贡献了多个功能,并为其提供了官方支持,同时它也作为 ASP.NET MVC 默认项目模板的一部分发布。
在本文中,我们将首先了解 jQuery 的基础知识,以及如何使用它向服务器发出异步请求,这些请求可以由 ASP.NET MVC 处理。然后,我们将探讨如何使用渐进增强来确保没有启用脚本的用户仍然可以使用我们的网站。最后,我们将了解如何使用 jQuery 以异步方式将表单数据提交回服务器。
jQuery 基础
在使用 jQuery 时,您主要使用 jQuery 对象(主要使用 $ 别名),它可以根据上下文执行各种不同的操作。例如,要使用 jQuery 查找页面上的所有 <div /> 元素并将 CSS 类添加到每个元素,我们可以使用以下代码行:
$(‘div’).addClass(‘foo’);
当您将字符串传递给 $ 函数时,jQuery 会将其视为 CSS 选择器,并尝试在页面中查找任何匹配该选择器的元素。在这种情况下,它将找到页面中的所有 <div /> 元素。同样,调用 $(‘#foo’) 将查找 ID 为 foo 的元素,而调用 $(‘table.grid td’) 将查找嵌套在具有 grid 类的表中所有 <td /> 元素。
调用此函数的结果是另一个 jQuery 对象实例,它包装了匹配选择器的底层 DOM 元素。因为它返回另一个 jQuery 实例,所以您可以继续链式调用 jQuery 方法,这些方法允许您以非常简洁的方式对 DOM 元素执行复杂操作。在本例中,我们调用 addClass 方法,该方法将指定的 CSS 类添加到包装集中的每个元素(在本例中,是页面中的所有 <div /> 元素)。
您也可以以类似的方式为元素附加事件。如果我们想在点击按钮时显示消息框,一种方法可以将 JavaScript 内联放在 onclick 事件中:
<button id="myButton" onclick="alert(‘I was clicked!’)"> Click me! </button>
这种方法的缺点是它将代码与标记混合在一起。这会影响应用程序的可维护性,并使逻辑难以遵循。使用 jQuery,我们可以从外部为按钮的 click 事件附加事件处理程序。
<button id="myButton">Click me!</button> <script type="text/javascript"> $(‘button#myButton’).click(function() { alert(‘I was clicked!’); }); </script>
这次,我们在页面中引入一个脚本元素来包含我们的 JavaScript 代码,并告诉 jQuery 查找 ID 为 myButton 的任何 <button /> 元素,并在点击按钮时运行一个函数。在这种情况下,浏览器将简单地显示一条指示按钮已被点击的消息。
这种方法称为非侵入式 JavaScript。通过将站点的标记与其行为(代码)分开,有助于提高可维护性,并使代码流更容易理解。
同样,我们可以为元素附加事件,也可以为整个页面附加 ready 事件。当页面 DOM 结构加载完成后,就会触发此事件,这是与 HTML 元素交互的最早可能时间点。因此,最好将所有事件绑定和其他 jQuery 代码都包含在 ready 处理程序中。
$(document).ready(function() { $(‘button#myButton’).click(function() { alert(‘Button was clicked!’); }); });
这里的结果与上一个示例完全相同,但更安全,因为我们确保在附加事件处理程序到按钮之前 DOM 已加载。
这些核心概念应该足以让您理解以下示例。要更深入地了解 jQuery,您可以阅读《jQuery in Action, Second Edition》一书。
使用 jQuery 发起 Ajax 请求
为了演示如何使用 jQuery 发起 Ajax 请求,我们将首先创建一个新的 ASP.NET MVC 3 项目,使用默认的 Internet Application 模板,并添加一个简单的控制器。此控制器有两个操作。它们都将渲染视图——一个名为 Index,另一个名为 PrivacyPolicy。
Index 操作将包含一个超链接,当点击该超链接时,它将向服务器发起请求以获取隐私政策,然后将内容加载到我们的 Index 页面中。期望的结果如图 1 所示。
此控制器的代码如列表 1 所示。
列表 1 一个简单的控制器
public class CustomAjaxController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult PrivacyPolicy()
{
return PartialView(); #1
}
}
请注意,我们从 PrivacyPolicy 操作 (#1) 返回的是部分视图而不是视图,以确保站点的布局不应用于该视图。这样做是为了确保布局页面中包含的周围装饰(例如菜单)不包含在我们操作返回的标记中。
PrivacyPolicy 部分视图包含一些非常基础的标记:<h2>我们的隐私承诺</h2> ...隐私政策在此处...
Index 视图的内容如列表 2 所示。
列表 2 包含脚本引用的 Index 视图
<script type="text/javascript" |#1 src="@Url.Content("~/scripts/jquery-1.5.2.js")"></script> |#1 <script type="text/javascript" |#2 src="@Url.Content("~/scripts/AjaxDemo.js")"></script> |#2 @Html.ActionLink("Show the privacy policy", |#3 "PrivacyPolicy", null, new { id = "privacyLink" }) |#3 <div id="privacy"></div> #4 #1 Reference jQuery script #2 Reference our demo code #3 Link to action #4 Container for results
我们首先包含对 jQuery 脚本的引用 (#1)。新创建的 MVC 3 项目会自动使用 NuGet 包包含最新版本的 jQuery,这使得在有新版本可用时更新 jQuery 非常容易。在撰写本文时,jQuery 1.5.2 是最新版本,相应的脚本位于 Scripts 子目录中。我们将路径包装在对 Url.Content 的调用中,而不是使用绝对路径,以确保在运行时正确解析路径,无论网站是在网站的根目录还是子目录中运行。
其次,我们还有一个脚本引用 (#2),它指向一个名为 AjaxDemo.js 的自定义 JavaScript 文件,我们尚未创建。此文件将包含我们的自定义 jQuery 代码。
接下来,我们声明一个标准的 ASP.NET MVC action link (#3)。参数依次是超链接的文本、我们想要链接到的操作(在本例中是我们的 PrivacyPolicy 操作)、任何其他路由参数(在本例中没有,所以我们可以传递 null)以及最后一个匿名类型,它指定其他 HTML 属性(在本例中,我们只是给链接一个 ID)。
最后,我们有一个 ID 为 privacy 的 div (#4),这是我们的隐私政策在 Ajax 请求触发后将被插入的位置。
现在,我们可以在 Scripts 目录中创建 _AjaxDemo.js_ 文件。在此文件中,我们可以添加一些 jQuery 代码来拦截 privacyPolicy 链接的点击事件,如列表 3 所示。
列表 3 AjaxDemo.js 文件中的自定义 jQuery 代码
$(document).ready(function () { #1 $(‘#privacyLink’).click(function (event) { #2 event.preventDefault(); #3 var url = $(this).attr(‘href’); #4 $(‘#privacy’).load(url); #5 }); });
我们首先创建一个文档就绪处理程序 (#1),该处理程序将在 DOM 加载后调用。在此处理程序内部,我们告诉 jQuery 查找 ID 为 privacyLink 的链接,并将其的 click 事件附加一个函数 (#2)。
click 处理程序接受对事件的引用作为参数。我们调用此对象上的 preventDefault
方法来阻止链接的默认行为(即,跳转到链接的 href 属性中指定的页面)。相反,我们提取 href 属性的值 (#4) 并将其存储在一个名为 url 的变量中。
事件处理程序的最后一行发出实际的 Ajax 请求 (#5)。这行代码告诉 jQuery 在页面上查找 ID 为 privacy 的元素(它引用了我们在列表 2 中创建的 <div /> 元素),然后将我们从链接中提取的 URL 的内容加载到该元素中。此 load 方法在内部创建 Ajax 请求,异步调用 URL,并将响应插入 DOM。
当我们运行应用程序并点击链接时,我们应该会看到隐私政策被插入到页面中。如果您使用 Firefox 浏览器并且还安装了 FireBug 扩展(来自 http://getfirebug.com),您可以轻松地看到正在进行的 Ajax 请求,如图 1 所示。
这是一个非侵入式 JavaScript 的例子——所有的 JavaScript 代码都放在页面之外的一个单独文件中。
渐进增强
前面的示例还说明了另一种称为渐进增强的技术。渐进增强意味着我们从基本功能(在本例中是简单的超链接)开始,然后在其之上添加附加行为(我们的 Ajax 功能)。这样,如果用户在其浏览器中禁用了 JavaScript,该链接将优雅地降级为原始行为,而是将用户发送到隐私政策页面,而无需使用 Ajax,如图 2 所示。
不幸的是,这个页面看起来不太好。我们目前将此页面渲染为部分视图,以便去除额外的页面装饰(由我们的应用程序的布局添加),以便它可以轻松地由我们的 Ajax 请求插入到 DOM 中。但是,在禁用 JavaScript 的情况下,最好继续包含页面布局和相关的样式。幸运的是,我们可以轻松修改 PrivacyPolicy 操作来处理这种情况,如列表 4 所示。
列表 4 使用 IsAjaxRequest 修改操作行为
public ActionResult PrivacyPolicy()
{
if(Request.IsAjaxRequest())
#1
{
return PartialView();
}
return View();
}
#1 Check if invoked through Ajax
PrivacyPolicy
操作现在通过调用控制器 Request 属性上的 IsAjaxRequest
扩展方法来检查操作是否是通过 Ajax 请求的 (#1)。如果返回 true,则该操作是由 Ajax 请求调用的,在这种情况下,视图应被渲染为部分视图;但是,如果页面不是由 Ajax 请求调用的,则返回一个普通视图。
现在,当我们禁用 JavaScript 并点击链接时,页面将使用正确的布局进行渲染,如图 3 所示。
使用 Ajax 提交表单数据
我们看到了如何利用 jQuery 在点击链接时从服务器检索数据,但我们还可以更进一步,通过异步提交表单将数据发送到服务器。为了说明这一点,我们将扩展我们之前的示例,在页面上显示评论列表,用户可以在其中添加评论。此页面的最终结果如图 4 所示。
首先,我们将一些评论集合添加到控制器中的一个静态字段中。当请求 Index 操作时,此评论列表将传递给视图。我们还将添加另一个操作(名为 AddComment),该操作允许用户向此列表添加评论。扩展后的控制器如列表 5 所示。
列表 5 引入 AddComment 操作
public class CustomAjaxController : Controller
{
private static List<string> _comments |#1
= new List<string>(); |#1
public ActionResult Index()
{
return View(_comments); #2
}
[HttpPost]
public ActionResult AddComment(string comment) #3
{
_comments.Add(comment); #4
if (Request.IsAjaxRequest())
{
ViewBag.Comment = comment; #5
return PartialView();
}
return RedirectToAction("Index"); #6
}
}
#1 Holds list of comments
#2 Sends to comments to view
#3 Accepts comment as parameter
#4 Stores new comment
#5 Sends comment to view
#6 Redirects to index action
我们首先在控制器中创建一个字符串列表,其中将包含一些评论 (#1)。这些评论作为模型的 (#2) 传递给我们的 Index 视图。我们还添加了一个名为 AddComment 的新操作,它接受一个评论作为参数 (#3),并且还带有 HttpPost 属性,以确保此操作只能作为表单 post 的结果被调用。
在此操作内部,它将评论添加到评论列表 (#4) 中,然后如果操作是通过 Ajax 请求调用的,则将其传递给 ViewBag 中的部分视图 (#5)。如果用户禁用了 JavaScript,则操作会重定向回 Index 操作,导致页面完全刷新 (#6)。
注意
此示例不是线程安全的,因为它将数据存储在静态集合中。在实际应用程序中,应避免使用此技术——更好的方法是将数据存储在数据库中。但是,为了简单起见,此示例不使用数据库。
AddComment 操作返回的部分视图只需将评论渲染在列表项中。
<li>@ViewBag.Comment</li>
接下来,我们可以修改 Index 视图以显示当前评论列表,并添加一个表单以允许用户提交新评论。更新后的视图如列表 6 所示。
列表 6 带有用于添加评论的表单的 Index 视图
@model IEnumerable<string> #1 <script type="text/javascript" src="@Url.Content("~/scripts/jquery-1.5.2.js")"></script> <script type="text/javascript" src="@Url.Content("~/scripts/AjaxDemo.js")"></script> <h4>Comments</h4> <ul id="comments"> |#2 @foreach (var comment in Model) { |#2 <li>@comment</li> |#2 } |#2 </ul> |#2 <form method="post" id="commentForm" |#3 action="@Url.Action("AddComment")"> |#3 @Html.TextArea("Comment", new { rows = 5, cols = 50 }) <br /> <input type="submit" value="Add Comment" /> </form> #1 Specifies strong type for view #2 Generates list of comments #3 Defines form to add comment
我们修改后的 Index 视图版本首先指定它是强类型的 (#1),类型为 IEnumerable<string>,这对应于从控制器传递到视图的评论列表。在此之后,它仍然引用我们的 jQuery 和 AjaxDemo 脚本文件。
我们现在还包括一个评论的无序列表 (#2),它通过循环遍历评论列表并将它们作为列表项写出而构建。
最后,我们包含一个表单 (#3),该表单发布到我们的 AddComment 操作,并包含一个文本区域,用户可以在其中添加评论。
此时,如果我们运行页面并提交表单,评论将被添加到列表中,但它会强制页面完全刷新才能显示更新后的评论。最后一步是修改 AjaxDemo.js 文件中的 jQuery 代码以通过 Ajax 提交表单,如列表 7 所示。
列表 7 通过 Ajax 提交表单
@model IEnumerable<string> #1 <script type="text/javascript" src="@Url.Content("~/scripts/jquery-1.5.2.js")"></script> <script type="text/javascript" src="@Url.Content("~/scripts/AjaxDemo.js")"></script> <h4>Comments</h4> <ul id="comments"> |#2 @foreach (var comment in Model) { |#2 <li>@comment</li> |#2 } |#2 </ul> |#2 <form method="post" id="commentForm" |#3 action="@Url.Action("AddComment")"> |#3 @Html.TextArea("Comment", new { rows = 5, cols = 50 }) <br /> <input type="submit" value="Add Comment" /> </form> #1 Specifies strong type for view #2 Generates list of comments #3 Defines form to add comment
与链接的示例一样,我们首先声明一个将在 DOM 加载时调用的函数。在此内部,我们告诉 jQuery 查找 ID 为 commentForm
的表单,并在提交表单时将其事件处理程序附加到它 (#1),再次调用 event.preventDefault
以确保表单不会被提交。相反,我们通过调用表单元素的 jQuery 序列化方法将表单内容序列化为一个字符串 (#2)。此字符串仅包含 URL 编码的键/值对,表示表单中的字段。在这种情况下,如果我们输入的文本是“hello world”,序列化后的表单数据将包含值 Comment=hello+world。
现在我们已经有了表单的内容作为字符串,可以通过 Ajax 发布。首先,我们查看表单操作以确定在哪里提交数据,并将其存储在一个名为 url 的变量中 (#3)。接下来,我们可以使用 jQuery 的 post 方法将此数据发送回服务器。post 函数接受几个参数:第一个是要发布数据的 URL,第二个是要发送的数据,第三个是在服务器发送响应后将调用的回调函数。
在这种情况下,服务器将返回我们的 AddComment 部分视图,其中包含包装在列表项中的评论,然后我们使用 jQuery 的 append 方法将其附加到评论列表的末尾 (#4)。
现在,当我们访问页面并添加评论时,我们可以在 FireBug 中看到正在发送 Ajax 请求,并且结果被添加到列表中,如图 4 所示。
由于 JavaScript 将函数用作对象,因此 this 关键字指向什么并不总是显而易见的,因为它是上下文敏感的。
在列表 7 中,因为 this 是在事件处理程序内部引用的,所以它指向事件触发的元素(在本例中是表单)。
摘要
Ajax 是当今 Web 应用程序的重要技术。有效利用它意味着大多数用户将看到更快的与 Web 服务器交互,但它不会阻止 JavaScript 被禁用的用户访问该站点。这有时被称为渐进增强。不幸的是,使用原始 JavaScript,这种技术很麻烦且容易出错。使用 jQuery 等 JavaScript 库,您可以更具生产力。
以下是您可能感兴趣的其他 Manning 图书
![]() |
ASP.NET 4 in Practice
|
![]() |
ASP.NET MVC 2 in Action |
![]() |
jQuery in Action, Second Edition
|
[1] DOM 代表“文档对象模型”,是表示页面中所有元素的对象的层次结构。