我喜欢第三方的控件。没有什么比能简单地拖放一些东西到页面上就能投入工作更节省时间了。对于我的个人网页,我使用DotNetNuke(http://www.dotnetnuke.com)作为站点门户,并且我也在该站点中使用了已购买的几个第三方的DNN模块。当然,我大概也可以自己来编写。由于DNN良好的文档和易用性,我自信自己编写也可以非常流畅地完成。但这需要时间,我很难抽出时间来自己写。因此如果有一个现成的解决方案可以帮助我节省时间,免于调试的痛苦,我百分之百会使用它。
这就是我近来对Web上到处可见的各种Ajax库入迷的原因。我们完全有能力花费时间来编写必要的JavaScript和C#类文件,从而拥有自己的可再分发的动态类库,但是说实在的,谁有大把时间来做这些事情呢?如果其他开发者,例如Michael Schwarz和Jason Diamond肯做这样的苦差事,那么我们为什么不能利用他们已经完成的工作,而把精力集中于站点设计、构造和可用性这些更有意思的方面呢?对整个基础架构的操心越少越好。这并不意味着我不愿意去领会幕后到底发生了什么事情。
实现Ajax库应该对它提供的功能有相当程度的理解。本章将创建一个小应用程序,它使用一个Ajax库,并且我们还要仔细研究在库内实际起作用的那些代码。了解这个流程可以引导你扩展现有产品,甚至从头开始构建新的产品。
8.1 库
如前所述,在Web上有许多现成的库可以使用,基本上是免费的或者费用很低。我越来越感兴趣的一个库是Jason Diamond的Anthem库。他的库对于我们的示例应用程序的实现和解剖目的具有下列一些好处。
q 开源:Jason将代码贡献给公共领域了。
q 只有一个文件:所有东西就是一个Anthem.dll文件。
q 容易理解:该代码格式易于阅读。
q 可扩展的:可以修改并且自己调用它。
你需要下载两个包,一个是Jason网站上的Anthem文件,位于http://anthem-dot-net.sourceforge.net;另一个是本章所用的示例应用。
在开始示例应用之前,我们需要首先编译Anthem库为一个可用的DLL。
安装库
下载了项目文件之后,你需要将它们解压到一个工作目录,如图8-1所示。
我们将稍后细说组成DLL的各种文件,但是现在,我们先来编译DLL,以便可以立刻开始一个简单Ajax应用:
(1) 在Visual Studio中,打开Anthem-2005.csproj文件,可以在Anthem-2005文件夹中找到它。
(2) 将Build配置从Debug转换为Release。
(3) 右击项目名并且选择Build。
(4) DLL现在应该出现在/bin/Release文件夹中,如图8-2所示。
我们现在可以通过Web应用使用Anthem.dll了。
|

|

|
|
图8-1 Anthem文件位置 |
图8-2 Anthem项目文件 |
8.2 应用
那么到底这个网站要完成什么任务呢?好,我们先来看一下最后的产品,如图8-3所示。
我们有一个很简单的应用、一个文本框和一个按钮。用户输入其姓名再单击该按钮,就会返回一个个性化的问候。然而这个个性化问候是建立在服务器端代码之上而不是通过JavaScript完成的。这个应用程序的事件序列可以概括描述如下:
(1) 首先使网页是Ajax感知的。
(2) 按钮的onclick事件被路由到一个JavaScript函数。
(3) JavaScript函数调用一个预先建立的服务器端Anthem方法。
(4) 客户端代码创建一个由“Hello”和姓名组成的字符串,并且将该字符串返回给客户。
(5) JavaScript函数处理该返回数据。
(6) 弹出一个警告框,并显示编译好的问候。
以上是发生在我们这个简单应用中的全过程。但是由于使用了Ajax,这里有更多东西需要理解。
我们的网站文件结构具有所有必备的东西,如图8-4所示。
|

|

|
|
图8-3 简单Ajax应用 |
图8-4 示例应用程序的文件结构 |
可以看到,有一个Bin文件夹被添加到该网站中。在其中有我们引用的Anthem.dll文件。正是该类库封装了我们所需的所有Ajax功能。我们待会儿再来研究它,先来看一下ASP.NET页面。
我们的HTML相当普通:



在开始描述流程之前,我们再来看一下服务器端代码:
必须对我们的Ajax感知的方法加上前缀[Ajax.Method()]。 | |
前面已经提到,在事件序列概述中,我们必须首先向Anthem库注册这个页面。page_load事件负责为我们处理这件事情:

完成这个之后,还需要声明所有的服务器端方法为Ajax感知的:
通过添加[Anthem.Method]属性到函数中,我们通知库,希望这个函数可以被异步进程调用。
在客户端,可以通过Anthem_InvokePage Method调用我们的方法:

注意SayHello函数名是Anthem函数的第一个参数。第二个参数由我们提供(作为一个数组)。客户端回调函数作为第三个参数提供。
你可以回忆一下在第3章的XmlHttpRequest例子中,我们从异步工作中接收一个回调。并且在回调中,我们操纵响应值:

至此可见,当前的流程非常类似于我们前面利用XmlHttpRequest对象的那个演示。这个例子与前面例子最大的不同在于我们将XmlHttpRequest流程的大部分封装到了Anthem.dll之中。
该库完成3件核心的事情:
q 生成并且维护XmlHttpRequest对象。
q 通过适当的参数调用服务器端代码。
q 解析和返回不同的数据类型。
当然,你若仔细阅读Anthem代码,会发现该库具有很强的功能。可以用一本书来描述整个库,这里我们只用一章来展示该库的主要类属性和方法。
8.3 研究Anthem库
我确信你已经发现,领会新项目的最好办法就是在调试方式下研究其源代码。我将逐步深入,并且描述发生在Anthem Manager类中的大部分活动。
在那么做之前,我们需要把Anthem 2005项目加到这个解决方案中。
在File菜单下面,选择Open,然后选择Project/Solution。
在Anthem-2005目录中,找到Anthem-2005.csproj文件。记得在添加该项目之前切换Add to Solution按钮。
添加完Anthem项目之后,你可以访问其源代码,同时也可以调试该Web应用。
首先在页面注册语句上设置一个断点,如图8-5所示。

图8-5 设置第一个断点
在运行该应用的时候,我们发现Register方法是到库的入口点,它最终将我们引导到在Anthem项目中建立的Anthem.Manager.cs中的实际的类的注册方法。


我们将提供网页作为Register函数的一个参数。
我们的注册方法要完成一项基础任务。它将Anthem Manager类实例化为控件,添加到页面的控件集合中,如果我们深入AddManager函数就会看到:


AddManager首先查看是否它已经添加了适当的类到页面控件的列表中;如果没有,就添加它以供随后使用。
然后添加PreRender函数作为EventHandler到Anthem Manager中。它本质上指向正确的函数调用的执行路径。如果你调用了服务器端函数,这个代码将指向适当的页面级方法的编译器:




然后该实例化了的manager被添加到页面项目:

还应该注意到manager.RegisterPageScript(page)在AddManager过程中作为最后的一步来调用。这很重要,它将添加一些动态生成的JavaScript:

我用斜体字标出了动态的JavaScript代码,为的是把它们从C#代码中区别开来。
动态地添加这些代码是因为,我们需要通过只有在运行时间可用的信息来声明一些JavaScript变量。

随后库将使用这些变量来正确地路由动态调用。
正是这个时候,注意到当前页面的一些东西正在构建中,你还将注意到我们在页面中添加了一个外部JavaScript文件:
Anthem.js
如果打开Anthem.js文件,我们会发现这是一个相当大的文件,它封装了所有我们需要的客户端功能:







如果下面两个参数都不在请求中,ASP.NET 1.1 将不会触发任何事件, 所以请确保它们总是在请求中。 | |










上述代码中有些是相当眼熟的,实际上它类似于前面在第3章讨论过的XmlHttpRequest的工作原理。需要从中了解的是请求和响应过程是在这里为我们处理的。
加入外部JavaScript文件是执行序列中的最后一行代码,因此如果我们按F5继续调试进程,即可以看到等待输入的Web应用,如图8-6所示。

图8-6 等待输入的应用
为了演示的目的,输入一些数据到文本字段中,并且单击Say Hello按钮。你现在应该是被带回到Page_Load事件:
然而,在这个过程中,你将注意到页面进程的一些有趣的细节。如前所述,我们被发送到Register函数,在那里我们将用已更新的页面控件(即,我们现在修改了的文本框的值)来重新组建管理器类。
如果你细看AddManager函数中,通过观察Locals窗口,你可以注意到有几个属性已经被修改了;最值得注意的是,IsCallBack现在反映了当前调用状态,如图8-7所示。
我们最后来看PreRender函数,在那里服务器端方法将被执行(如前所述)。当前的页面和方法数据现在可以使用,如图8-8所示。
|

|

|
|
图8-7 回调过程研究 |
图8-8 PreRender过程 |
我们可以很快地识别出SayHello函数名为methodName,并且我们希望通过InvokeMethod() 调用来异步地执行,你可以在PreRender函数中发现它:

在执行目标代码以前,我们显然需要抽出服务器端函数要用的所有参数。我们用Convert- Parameters()函数来进行:


既然我们已经有了执行实际的Anthem方法需要的适当的参数,现在可以调用Invoke方法了,它将通过运行服务器端函数、接着手动切换到客户端回调函数来完成异步进程。

万一TargetInvocationExceptions真的发生异常,该方法将抛出它的InnerException属性。 | |


在执行val = methodInfo.Invoke(target, parameters)之后,应用程序将完成异步进程,我们将看到那个简陋的警告框,如图8-3所示。
但我们以前不是曾讨论过,Jason Diamond的库可以从库内传递和操纵.NET数据类型(DataSets、DataTables等)的吗?是的,幸亏我们还记得。当研究示例代码的时候,有一个事件我们没有在其上停留,那就是重载的close方法:

我们对WriteResult调用很感兴趣,那是回调函数的返回值所做出的(你应该记得,来自SayHello的字符串)。正是在WriteResult之内,我们发现返回类型分析,仔细考虑一下,这里正是你可以通过附加自己的分析方法来扩展该库的地方。Jason很周到地包含了.NET开发者通常都会用到的大多数的类型。我们可以在连接到WriteResult的相关方法中发现它们:

如果在格式化结果值时抛出了异常,那么我们需要放弃已经编写好了的代码,从解决错误消息重新开始。 | |









你还应该注意到StringBuilder组装JSON文本为传递回客户端的默认文本。
例如,下一章将构建一个DataTable并且传递它回到客户端来进行解释。我们先调用DataTable解析器:
最终,它为我们创建了一个表达DataTable的JSON对象。


转换DataSet也采用同样的模式:
前面的代码将会加工JSON对象为:

Anthem库已经提供现成的这些数据变换,更重要的是,它们可以被修改来适应Ajax可能在当前应用程序中所担当的任何角色。
8.4 小结
本章讨论了许多.NET开发者都可以使用的一个Ajax库。我鼓励你亲自体验这个产品,优化并且扩展它来满足你的项目需要。此外,请确保随时访问Jason Diamond的博客,因为我肯定他将持续更新该库,不断推出新的功能。
在下一章,我们将实际上开始使用Ajax库;构建一个示例应用,它会仿制一些近年来非常吸引公众眼球的流行的Ajax Web控件。我们已经了解了,可以构建真正的动态Web内容了。 |