异步 JavaScript 和 XML (AJAX) 的简单实用入门






4.81/5 (29投票s)
AJAX(Asynchronous JavaScript and XML)的简单实用的介绍。
背景
Asynchronous JavaScript and XML(AJAX)是一项革命性的技术,它使得Web用户体验能够达到桌面应用的丰富程度。在本系列中,我将描述我学习AJAX的历程,以及一些帮助您快速入门这项简单而强大的技术的简单步骤。
引言
AJAX使Web开发人员能够通过服务器端的后台异步POST请求动态更新网页。因此,它消除了将整个页面发送回服务器并导致浏览器全局刷新的必要性。这带来了以下主要好处:
- 丰富的用户体验——Web用户不必等待整个页面加载完毕。在页面重新加载期间,他们也不会被视觉信息的丢失所干扰。AJAX还可以用于在页面的某个子部分正在被更新/从服务器加载时显示进度信息。
- 服务器可伸缩性——由于客户端和服务器之间只交换最少的信息,因此服务器可以用相同的资源(内存、CPU)服务更多的客户端。这有助于提高服务器的可伸缩性。
- 客户端效率——与重新解析和显示整个页面相比,客户端浏览器只需要更新已加载Web页面的一个子部分。
实线表示同步操作,而灰色虚线表示异步操作。
AJAX的构建块
AJAX本身并不是一项技术。它是多个协同技术的成果,这些技术在整个端到端的HTTP异步请求/响应过程中扮演着各自的角色。AJAX Web应用模型中涉及的主要构建块包括:
- 使用XHTML和CSS的基于标准的表示。
- DHTML——浏览器操作已加载Web页面的XML Document Object Model(DOM)的能力。
- JavaScript——在AJAX操作期间执行所有客户端活动的基石技术。客户端浏览器必须支持JavaScript才能支持AJAX操作。许多厂商必须支持AJAX风格的Web页面(用于桌面浏览器)以及传统的post-back Web页面(用于移动设备浏览器),以覆盖所有用户。
XMLHttpRequest
对象——浏览器必须支持XMLHttpRequest
JavaScript对象才能发起异步Web请求。不同浏览器检索此对象的方式不同。有许多已发布的shim(垫片)解释如何检索此对象。这里有一个来自Wikipedia的例子。
// Provide the XMLHttpRequest class for IE 5.x-6.x:
// Other browsers (including IE 7.x-8.x) ignore this
// when XMLHttpRequest is predefined
if (typeof(XMLHttpRequest) == "undefined") {
XMLHttpRequest =
function(){
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}
这个例子展示了如何在浏览器不支持的情况下添加同名的shim类型XMLHttpRequest
。有关XMLHttpRequest
JavaScript类的完整参考,请参阅Mozilla Developer Center页面。
我强烈建议阅读一本深入解释上述技术的书籍,以打好基础。我个人读了《Beginning AJAX with ASP.NET》,发现它对初学者来说是一个很好的起点。然而,书中后面的章节已经过时,因为它们提到的技术,特别是Microsoft Atlas,自本书发布以来已经发生了很大变化。
第一次AJAX体验
在本文开头,我曾承诺以一种非常易于理解的方式介绍AJAX。多年来,我一直回避学习这项技术,因为它当时被我认为是非标准且 hacky 的。现在,我的观点发生了改变,因为AJAX Web模型中有许多技术已被公认为行业标准,并且现在符合W3C规范。总之,让我们来看看实际操作!在Visual Studio中创建一个ASP.NET Web应用程序,您将在其中测试您的第一个AJAX代码。然后,在项目中创建一个XML文件note.xml,并用以下内容覆盖它:
<?xml version="1.0" encoding="iso-8859-1"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this
weekend!</body>
</note>
接下来,在项目中创建一个HTML文档ajax.htm,内容如下:
<html>
<head><title>First AJAX
Example</title></head>
<script type="text/javascript">
var request;
var READYSTATE_COMPLETE = 4; // Indicates completion state
// Other browsers (including IE 7.x-8.x) ignore this
// when XMLHttpRequest is predefined
if (typeof(XMLHttpRequest) == "undefined") {
XMLHttpRequest =
function() {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}
function ReadXmlFile() {
// Create XMLHttpRequest object
request = new XMLHttpRequest();
// Open an XML file
request.open('GET', 'note.xml', true);
// Attach completion handler to the request
request.onreadystatechange = DisplayXmlFile;
// Send async request to the server with null context
request.send(null);
}
function DisplayXmlFile() {
// If request is complete, show the content of response in the xmlSpan span
if (request.readyState == READYSTATE_COMPLETE) {
xmlSpan.innerText = request.responseText;
}
}
</script>
<body>
<span id="xmlSpan"></span><input
type="button" onclick="ReadXmlFile();" value="ReadXml" />
</body>
</html>
最后,在浏览器中运行应用程序并打开ajax.htm。您将看到,如果您点击“ReadXml”按钮,xmlSpan
span将用note.xml的内容填充,而无需刷新页面,如下图 3 所示。
安全提示
为了确保在线安全并防止跨站脚本攻击,浏览器将XMLHttpRequest
对象限制为只能请求与Web应用程序所属域名相同的域名下的资源。您可以在上面的示例中验证这一点。尝试请求https://w3schools.org.cn/XML/note.xml(其内容与note.xml相同),而不是请求note.xml。运行应用程序将不会允许获取XML文件。将其改回note.xml,它将再次工作。
AJAX框架
近年来,主流Web门户网站对AJAX的使用彻底改变了在线用户体验。尽管微软是第一个利用AJAX的公司,但真正抓住这项技术威力并设定在线用户体验新标准的却是Google。已经构建了许多框架来提高开发AJAX Web应用程序的开发人员生产力。这些框架可以分为以下主要类别:
- 客户端框架——帮助开发人员重用/利用客户端JavaScript代码的JavaScript库/框架。
- 服务器端框架——服务器端代码生成器/注入器,结合扩展客户端AJAX JavaScript代码的能力,以实现AJAX Web应用程序开发的可扩展性和可重用性。
每个类别都有其优点和缺点。选择一个而不是另一个取决于正在开发的应用程序的性质以及用于开发这些应用程序的平台。很多时候,开发人员也会选择不同类别的工具组合,并将它们集成在一起以实现期望的最终产品。在接下来的章节中,我将向您展示每个类别的示例。
客户端框架
已经付出了很多努力来增强JavaScript以通过可重用库支持AJAX。可能最大的努力是构思jQuery——一个强调JavaScript和HTML之间交互的轻量级库。微软已经采纳了这个框架,它现在随ASP.NET MVC Framework一起发布,并且在不久的将来将成为即将发布的Visual Studio 2010版本的一部分。让我们用jQuery重写我们之前的例子。同样,按照之前的例子进行,只是这次您的HTML页面应该包含以下内容:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>AJAX with jQuery</title>
</head>
<script src="Scripts/jquery-1.3.2.js"
type="text/javascript"></script>
<script
type="text/javascript">
$(document).ready(function() {
$("#ReadButton").click(function() {
$.ajax({
type: "GET",
url: "note.xml",
success: function(msg) {
$("#xmlSpan").attr("innerText", msg.xml);
}
});
});
});
</script>
<body>
<span id="xmlSpan"></span><input type="button" id="ReadButton"
value="ReadXml" />
</body>
</html>
这次获取note.xml的脚本非常简洁!另外,请注意,我们不需要在服务器端做任何奇怪的操作就能使其工作。我们不必创建XMLHttpRequest
对象并检查浏览器类型。同样,我们也不必声明常量来检查XMLHttpRequest
对象的状态。只需精确地订阅成功事件即可启动回调!
好的,您可能在想:上面的代码工作得很好,但如果需要HTTP POST而不是GET怎么办?以及如何在使用异步HTTP POST请求的同时执行后端业务逻辑?答案很简单。我们可以使用jQuery AJAX调用的POST版本来实现相同的功能。我们将在下面的场景中看到这种方法。
假设您想调用后端ASP.NET页面上的GetPerson
方法,该方法会返回一个自定义的Person
对象。假设这个Person
对象将在客户端进行处理并显示给用户。让我们看看如何做到这一点。
为了实现上述场景,我们必须理解后端.NET托管对象和JavaScript对象之间的序列化工作原理。JavaScript中的任何对象都可以序列化为JSON(JavaScript Object Notation)格式。同样,任何JSON对象都可以反序列化为JavaScript对象。JSON实际上是一种系统之间结构化数据交换的格式。JSON中对象表示法的BNF如下:
JSON Notation := { } | {KeyValuePair}
KeyValuePair := Key : Value | KeyValuePair, Key : Value
Key := String
Value := String | Number | JSON Notation | Array | true | false | null
Array := [ ] | [Value] | [Value, Value]
因此,如果您有一个存储FirstName
和LastName
的Person
对象,那么下面可以是一个Person
实例的JSON表示:
{PersonInstance: {FirstName:John, LastName:Rambo}}
而一个Person
对象的数组可以表示为:
{PersonList: [{FirstName:John, LastName:Rambo}, {FirstName:Austin, LastName:Powers}]}
JSON的格式非常接近JavaScript解释其对象的方式,即嵌套的键值对形式。是的,JavaScript中的一切,属性、方法、事件,都只是某种形式的键值对,因此JavaScript对象只不过是嵌套的字典!如果您遵循许多其他厂商的持久化格式,这通常是一种表示对象的非常常见的方式。(Apple在MacOS和iTunes中也使用类似的plist格式来存储属性列表!有关plist的更多信息,请查看其Wikipedia页面。)
总之,回到我们的GetPerson示例。我们将创建一个简单的ASP.NET页面,其代码隐藏如下:
using System;
using System.Web.Services;
[Serializable]
public class Person
{
public string FirstName
{get;set;}
public string LastName {get;set;}
}
public partial class Person_Page : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
[WebMethod]
public static Person GetPerson(Person person)
{
return new Person { FirstName = person.FirstName, LastName = person.LastName };
}
}
有一个名为GetPerson
的静态Web方法,它接受一个Person
类型的参数。Person
实际上包含一个人的名字和姓氏。GetPerson
然后简单地返回一个新的Person
对象,该对象具有相同的名字和姓氏。需要注意的一个重要事项是,Person
对象被标记为Serializable
属性。在CLR中,如果一个对象被标记为Serializable
属性,那么它可以被序列化成任何给定的格式/或从任何格式反序列化回一个对象。还要注意GetPerson
方法上的public static
可见性类型和WebMethod
属性。这对于向客户端Web请求公开一个可调用的方法至关重要。我们将在下面看到如何从客户端代码调用此方法。
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Person_Page.aspx.cs" Inherits="Person_Page" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>jQuery POST
Example</title>
</head>
<script
src="Scripts/jquery-1.3.2.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#GetPerson").click(function() {
var firstname = $("#txtFirstName").val();
var lastname = $("#txtLastName").val();
content = "{'person':{'FirstName':'" + firstname +
"', 'LastName':'" + lastname + "'}}";
$.ajax({
type: "POST",
url: "Person_Page.aspx/GetPerson",
data: content,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg)
{
var fullName = msg.d.LastName + ", " + msg.d.FirstName;
$("#lblFullName").text(fullName);
}
});
});
});
</script>
<body>
<form id="form1" runat="server">
<div>
First Name: <input type="text" id="txtFirstName" />
Last Name: <input type="text" id="txtLastName" />
<br />
<input type="button" id="GetPerson" value="GetPerson"></input>
<span id = "lblFullName"></span>
</div>
</form>
</body>
</html>
这里有两个文本框,txtFirstName
和txtLastName
,分别用于捕获人的名字和姓氏。然后,有一个GetPerson按钮,它向页面的代码隐藏发送AJAX请求,并尝试调用Web方法GetPerson
,通过一个内容字符串变量传入从txtFirstName
和txtLastName
字段构建的JSON。一旦从服务器收到响应,lblFullName
span的文本将被更新,以“LastName, FirstName”的格式显示全名。下面是运行上述代码时的截图:
可以看到,当CLR接收到POST请求时,它会查询HTTP ContentType
字段的值。由于它被指定为application/json,它会将JSON中的Person
对象反序列化为实际的Person
实例,并且还将GetPerson
返回的Person
对象序列化回JSON。同时请注意客户端脚本在content string对象中的JSON字符串构建。我们在对象中指定键为‘person’,这与GetPerson
Web方法中Person
对象的正式参数名相同。这允许在调用期间绑定参数,以便正确地将值传递给Web方法。
服务器端框架
服务器端框架动态生成并将AJAX JavaScript片段注入到服务器处理的表单中,因此开发人员在每次想要在其Web应用程序中实现AJAX风格的功能时,无需编写JavaScript。我们已经在上面看到JSON和JavaScript之间有很好的对应关系,一个可以映射到另一个,反之亦然。基于这个事实,服务器端框架会动态生成与后端数据结构对应的代理JavaScript对象。它们还注入调用后端方法并与客户端浏览器之间传递代理的代码。
有许多流行的服务器端AJAX框架,例如Microsoft ASP.NET AJAX、AjaxPro、ComfortASP.NET和MagicAjax.NET。此外,微软还启动了一个社区项目来构建更多的服务器端控件,通过一个名为ASP.NET AJAX Control Toolkit的社区项目,使Web体验更接近桌面体验(尽管上次我检查时,这个工具包存在不少bug,有些控件,如AutoComplete,完全失效)。我还必须提到,Microsoft ASP.NET AJAX服务器框架还包含一个名为Microsoft AJAX library(名字真够混乱的)的客户端JavaScript库,它非常模仿托管语言中许多OO特性,如继承、接口等。Microsoft AJAX库可以从http://www.asp.net/ajax/downloads/下载。
然而,必须认识到使用各种服务器端框架的严重影响。大多数时候,这些限制并没有被很好地记录下来。一个常见的限制是ASP.NET项目中使用了冲突的HTTP处理程序和HTTP模块。HTTP模块和处理程序是在ASP.NET处理应用程序管道中的请求之前执行的HTTP请求处理程序。服务器端框架通常利用这些组件通过JavaScript标签将代码注入到HTML响应中。但是,如果存在多个服务器框架(可能用于AJAX以外的其他目的),那么它们可能会发生冲突,并通过“窃取”HTTP请求首先破坏其他框架提供的功能。
服务器端框架的另一个主要限制是它们执行异步HTTP请求的方式。例如,ASP.NET AJAX框架严重依赖于ASP.NET ViewState对象。因此,无法使用此框架从Web用户控件(通常称为部分视图或*.ascx)发出请求。它也不与ASP.NET MVC框架兼容。因此,在框架上进行背景研究并确保它与您Web应用程序中正在使用的现有框架兼容非常重要。
让我们看一个使用服务器端框架的例子。在本例中,我将使用AjaxPro框架。您可以从其网站免费下载AjaxPro框架。要使用此框架,请创建一个ASP.NET Web应用程序,并简单地将下载的程序集(AjaxPro.dll或AjaxPro2.dll,取决于您从网站下载的版本)引用到您的项目中。
接下来,在web.config中添加以下行(注意:如果您已经有一个httpHandlers
部分,只需添加下面的‘add
’标签):
<httpHandlers>
<add verb="POST,GET" path="ajaxpro/*.ashx"
type="AjaxPro.AjaxHandlerFactory,AjaxPro.2"/>
</httpHandlers>
上面的代码意味着,对于ajaxpro文件夹内的扩展名为ashx的资源的任何POST/GET请求,都将被转发到AjaxPro.2.dll程序集中的AjaxPro.AjaxHandlerFactory
HTTP处理程序。
接下来,我们将向项目中添加一个名为AjaxPro_Page的新ASP.NET页面。为了将此页面连接到AjaxPro HTTP处理程序,我们需要做的第一件事是通过页面Page_Load
方法中的AjaxPro.Utility.RegisterTypeForAjax
调用来注册它,如下所示:
protected void Page_Load(object sender, EventArgs e)
{
AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxPro_Page));
}
下一步是定义将在页面内由客户端代码调用的方法。在本例中,我们将定义一个GetTime
方法,它将返回服务器时间给客户端。重要的是用AjaxMethod
属性标记此方法,以便AjaxPro框架可以生成该方法的“代理”代码。下面是GetTime
方法的定义:
[AjaxMethod]
public DateTime GetTime()
{
return DateTime.Now;
}
此时,服务器端代码部分已完成。完整的类应该如下所示:
using System;
using AjaxPro;
public partial class AjaxPro_Page : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
AjaxPro.Utility.RegisterTypeForAjax(typeof(AjaxPro_Page));
}
[AjaxMethod]
public DateTime GetTime()
{
return DateTime.Now;
}
}
让我们在浏览器中运行此页面,看看客户端生成了什么。在浏览器中运行此页面并查看此页面的源代码。您将找到以下三个script
标签,它们是由我们在web.config中注册的AjaxPro.AjaxHandlerFactory
HTTP处理程序注入的:
<script type="text/javascript" src="/blogtest/ajaxpro/prototype.ashx"></script>
<script type="text/javascript" src="/blogtest/ajaxpro/core.ashx"></script>
<script type="text/javascript" src="/blogtest/ajaxpro/converter.ashx"></script>
<script type="text/javascript" src="/blogtest/ajaxpro/AjaxPro_Page,App_Web_hghu6cj1.ashx">
</script>
第一个脚本基本上是启用AjaxPro在后期生成的代码中使用的OO概念(如继承和虚拟函数)的代码。因此,它定义了所有AjaxPro客户端代码从中继承的基原型。第二个脚本生成与IFrame
、请求队列和通用请求/响应逻辑相关的代码。第三个脚本基本上是用于复杂.NET类型(如System.Collections.Specialized.NameValueCollection
、System.Data.DataSet
、System.Data.DataTableDataTable
、ProfileBaseConverter
和IDictionaryConverter
)的JSON序列化器/反序列化器。这使得您可以使用这些复杂数据类型作为.NET方法的返回类型,并从AjaxPro生成的代理代码中无缝使用它们。
最后一个脚本对应于为您代码隐藏生成的代理。请注意,App_Web_*.ashx处理程序的实际结尾是随机的,并且可能不以hghu6cj1结尾,就像我的情况一样。如果您导航到该URL并查看代码的源代码,您会找到类似以下代码的内容:
AjaxPro_Page_class = function() {};
Object.extend(AjaxPro_Page_class.prototype, Object.extend(new
AjaxPro.AjaxClass(), {
GetTime: function() {
return
this.invoke("GetTime", {}, this.GetTime.getArguments().slice(0));
},
url: '/blogtest/ajaxpro/AjaxPro_Page,App_Web_hghu6cj1.ashx'
}));
AjaxPro_Page = new AjaxPro_Page_class();
在这里,我们可以清楚地看到AjaxPro生成了一个名为AjaxPro_Page_class
的JavaScript类,并定义了GetTime
方法的调用代码。它所做的具体操作是定义一个名为AjaxPro_Page_class
的空类,然后将GetTime
方法添加到其原型中。这样,此类的任何实例或其子类都可以通过原型链访问GetTime
方法。最后,它声明了一个名为AjaxPro_Page
(与我们的服务器代码隐藏类同名)的AjaxPro_Page_class
实例,以便我们的脚本可以使用它来调用代码隐藏上的方法。
现在,我们需要将客户端代码连接起来,以便从AjaxPro_Page
调用GetTime
方法。很简单。只需将AjaxPro_Page.aspx中的代码替换为以下内容:
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="AjaxPro_Page.aspx.cs" Inherits="AjaxPro_Page" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>AjaxPro Example</title>
</head>
<script src="Scripts/jquery-1.3.2.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#cmdServerTime").click(function() {
AjaxPro_Page.GetTime(function(result) {
$("#lblServerTime").text(result.value.toString());
});
});
});
</script>
<body>
<form id="form1" runat="server">
<div>
Server Time: <span id="lblServerTime"></span>
<input type="button" id="cmdServerTime" value="GetServerTime" />
</div>
</form>
</body>
</html>
此代码基本上声明了一个id
为lblServerTime
的span
和一个id
为cmdServerTime
的Button
。在页面加载的jQuery中,它将cmdServerTime
的Click
事件连接到调用AjaxPro_Page.GetTime
方法,该方法反过来连接到一个匿名方法,该方法将span
的文本替换为从GetTime
方法返回的服务器时间值。运行它,您将看到与下面截图相似的结果:
处理冲突的HTTP处理程序
正如我之前提到的,有时多个HTTP处理程序在服务器上可能会发生冲突,导致您的应用程序无法按预期工作。因此,可能需要进行适当的解析,以使所有处理程序和谐地工作。例如,如果您尝试将AjaxPro框架与ASP.NET MVC应用程序一起使用,您的应用程序将无法开箱即用,因为MVC框架有一个强大的URL路由引擎,它会捕获所有传入的请求(请注意,在MVC框架中,视图也是不存在的资源),包括对ajaxpro/不存在文件夹的所有资源请求(参见上面的web.config)。因此,我们需要在ASP.NET MVC URL路由引擎中对此规则进行排除。Global.asax.cs中的以下代码将实现此目的:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("ajaxpro/{resource}.ashx");
:
}
调试支持
与任何应用程序开发一样,调试支持对于跟踪代码中的执行点是必不可少的。我在Internet Explorer和Mozilla Firefox中测试和调试我的Web应用程序。要在IE中启用调试支持,您可以按照Jonathan Boutelle博客中的说明进行操作。IE调试的一个优点是(我个人将Firefox用于所有其他目的),您可以利用Visual Studio调试器,而无需下载和学习另一个调试器来调试JavaScript。对于Mozilla Firefox,我使用Venkman JavaScript Debugger,并强烈推荐它。
结论
我希望我能够从初学者的角度提供对AJAX Web开发的良好见解。这里还有许多未涵盖的工具,但最重要的是玩转它们,找到最适合您需求的组合。我也有自己的博客http://akaila.serveblog.net,我在那里发布所有此类文章以及更多内容。当然,如果您学到了一些东西,就分享一些!编码愉快!