最近花了一些时间,将微软Asp.Net官方的Ajax视频全部看了一遍,视频大多都很短,8至15分钟的居多,有讲述AjaxControlToolkit中控件用法的,也有讲述Asp.Net Ajax常见的应用场景和技巧的。
本文介绍了使用Asp.Net Ajax做开发时两种最常见的与服务端进行交互(客户端请求服务端执行逻辑,服务端返回结果)的开发模式。第一种我姑且称为UpdatePanel模式,第二种称为Web Service(WCF Service)模式。
开始前的一些准备
对于这些文章,我假设大家都已经安装好了Asp.Net Ajax Extension 和 Asp.Net Ajax Control ToolKit 这两个组件。其中Asp.Net Ajax Extension已经包含在了.Net Framework 3.5中,而Ajax Control Toolkit可以去这个位置下载:http://www.codeplex.com/AjaxControlToolkit/Release/ProjectReleases.aspx?ReleaseId=16488 。因为我使用的是VS2008,所以Ajax Extension无需安装,而Ajax Control Toolkit我安装到了GAC(Global Assembly Cache,全局程序集缓存)中,因此文章所附代码的Bin目录不会包含任何的dll组件。如果你想运行代码,可以像我一样将Ajax Control Toolkit安装到GAC中,或者针对自己的情况(VS2005或者VS2008,私有程序集部署还是GAC部署)对代码进行一些简单的修改和配置。
如果你想安装到GAC中,假设你将AjaxControlToolkit.dll拷贝到了“C:\”下,那么可以打开“VS2008命令提示符”,然后输入下面的命令,按回车:
gacutil -i C:\AjaxControlToolkit.dll
除此以外,还有两点需想要说明。如果你想要在页面的CodeBehind中使用AjaxControlToolkit中定义的类型,那么需要在Web.config中进行一下配置,假设你和我一样采用的是GAC部署,那么Web.Config的设置为:
<system.web> <compilation debug="false"> <assemblies> <add assembly="AjaxControlToolkit, Version=3.0.20820.37372, Culture=neutral, PublicKeyToken=28F01B0E84B6D53E"/> <!- 其余略 --> </compilation> </system.web>
在VS2008(VS2005)中,你可以将AjaxControlToolkit安装到工具箱(Toolbox)中,但是在安装好以后,当你向页面拖放一个控件时,控件默认的前缀是cc1,并且会在页面顶部自动生成一行控件的声明,类似于这样:
// 自动在页面顶部产生的声明 <%@ Register Assembly="AjaxControlToolkit, Version=3.0.20820.37372, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" Namespace="AjaxControlToolkit" TagPrefix="cc1" %> // 页面中控件的样式 <cc1:AutoCompleteExtender> ... </cc1:AutoCompleteExtender>
这样让人感觉页面很不清爽,除此以外,cc1也没有任何的含义。为了解决这个问题,我们也可以在Web.Config进行一下设置:
<system.web> <pages> <controls> <add assembly="AjaxControlToolkit, Version=3.0.20820.37372, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" namespace="AjaxControlToolkit" tagPrefix="ajaxControlToolkit" /> <!-- 其余略 --> </controls> </pages> <system.web>
如果你和我一样经过上面三个步骤的设置的话,那么在Web站点Bin目录中不会有任何的程序集,另外页面顶部也不会再有控件的声明,同时,拖放控件到页面中时,它的代码将是这样子的:
<ajaxControlToolkit:AutoCompleteExtender> ... </ ajaxControlToolkit:AutoCompleteExtender>
本文以及所有Asp.Net Ajax相关的文章,都假设你采用了和我相同的配置。
Asp.Net Ajax - UpdatePanel模式
现在考虑一个最简单的范例,页面上放置一个Label控件、一个Button控件,当我们点击Button控件的时候,将Label控件的文本更新为当前时间,这里的关键是更新时间的代码位于服务端,而非使用Javascript在客户端来完成。尽管这里服务端的代码仅仅是更新一下时间,但在实际中却可以执行任何的服务端操作。
UpdatePanel是是大家熟悉的一种方式了,即是在页面拖放一个UpdatePanel,将需要用Ajax方式进行更新的控件放在UpdatePanel之内,在本例中是Label控件。可以将Button控件也放置在UpdatePanel之内,也可以不放置。如果UpdatePanel内不放置Button控件,则需要设置UpdatePanel的Triggers节点,其中包括一个ControlID属性和EventName属性,用于指定哪个控件的哪个事件可以触发了一个PostBack。本例中ControlID自然是Button的ID,而EventName则为Click。也就是说当Button的Click事件触发时,进行PostBack操作。下面是aspx页面的主要代码:
<asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager>
当前时间:
<asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:Label ID="Label1" runat="server" Text="[未设置]"></asp:Label> </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" /> </Triggers> </asp:UpdatePanel> <br /> <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="更新时间" />
而在后置代码中,我们只需要像平常的Asp.Net开发一样,编写Button控件的Click事件处理程序就可以了:
protected void Button1_Click(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToLongTimeString(); }
OK,现在一切都已经就绪了,如果你运行这个页面,并且点击Button,会看到Label的值变为了最新的时间,而且没有因为PostBack所产生的页面闪动,即是人们常说的无刷新更新页面。这可能是实现一个Asp.Net Ajax的最简单范例了。但是它的问题是什么呢?当我们点击Button的时候,在服务端执行了一个完整的Asp.Net 页面生命周期,和你不使用UpdatePanel更新页面没有任何的区别。可以做一个测试,在页面在拖放一个Label,ID为Label2,然后在Page_Load中写入下面代码:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Label2.Text = DateTime.Now.ToLongTimeString(); } else { Label2.Text = DateTime.Now.ToLongTimeString(); } }
然后在Page_Load一行设置断点,接下来运行调试,会发现每次你点击Button按钮的时候,都会运行else{...}中的语句,说明每次页面都会执行Page_Load方法。这说明使用这种方式时,服务器端的开销是比较大的。这里还可以发现一个有趣的现象,尽管服务器执行了为Label2.Text赋值的语句,但是页面上Label2却并没有更新。如果想要更新它,那么需要将它也放置到UpdatePanel中,这里我们可以在页面上重新拖放一个UpdatePanel,然后把Label2放置进去。然后我们再点击Button,会发现Label1和Label2都进行了更新。这里又引出了一个有趣的问题:回想一下前面,我们只为第一个UpdatePanel设置了Triggers节点,而并没有为后来新添的UpdatePanel设置Triggers节点,但是对一个UpdatePanel的更新也会影响到另一个。有的时候这种情况是我们所需要的,但更多时候不是,我们可能希望对于Label2的更新由其他控件的其他事件触发。此时,可以将第二个UpdatePanel的UpdateMode属性设为“Conditional”,就避免了受到其他UpdatePanel提交的影响,这个值默认为“Always”。
下面是此时Aspx页面的代码:
<!-- 上面相同 --> <hr /><br /> <asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:Label ID="Label2" runat="server" Text="[未设置]"></asp:Label> </ContentTemplate> </asp:UpdatePanel>
以上这些就是一种最常见的Asp.Net Ajax开发模式了,我们看到它如何实现,也看到了它的缺陷:每次客户端的操作,都会在服务端执行一次完整的页面生命周期,加重了服务器的负担,同时客户端和服务端的通信过程中也会传递完整的http协议内容,增大了网络流量。我们也应该看到它的优点:实现起来非常的简单,操作上基本等同于通常的Asp.Net开发,所使用的控件也为Asp.Net服务器控件(Server Control,这里相对于HTML控件而言)。
Asp.Net Ajax - Web Service模式
还有一种方式就是Web Service模式了,客户端不再提交页面,而只是发送Web Service请求,并对收到应答进行处理。由于这里采用了异步方式,所以客户端在发送WebService请求之后无需等待。采用这种方式服务端不会执行生命周期,往返的数据量也减到了最小。但缺点就是需要手动编写一些代码。我们来一步步看下如何完成,因为WCF是下一代Windows平台通信的基础,集成了Web Service和Remoting这两大技术,所以我们采用WCF来创建服务。
首先选择“添加新项”,然后选择“启用了AJAX的WCF服务”,输入名称SimpleService,这样会在站点中添加一个SimpleService.svc文件,在App_Code中创建一个SimpleService.cs,还会在Web.Config中添加相关的配置。我们只需要改动一下App_Code中的SimpleService.cs下的代码:
public class SimpleService { [OperationContract] public string GetCurrentDate(string clientValue) { string rtn = "Server Time :" + DateTime.Now.ToLongTimeString() + "<br />"; rtn += "Client Value(round trip): " + clientValue; return rtn; } }
SimpleService还用一些特性修饰了,我将它取消掉了以节省空间。方法接受一个字符串clientValue,然后获取服务器时间,最后返回clientValue。这段代码看上去没有什么特别之处,但是注意到我在Client Value旁加了一个括号,写着“round trip”,对于Ajax程序来说,这个值由客户端发送,最后再返还给客户端,进行了一趟由客户端到服务端,再到客户端的周游。
为了要让javascript可以调用这个Web服务,我们需要在aspx页面中对它进行注册,拖放也一个ScriptManager到页面上,然后向下面这样进行设置:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="~/SimpleService.svc" /> </Services> </asp:ScriptManager>
接下来我们新建一个Pattern2.aspx页面,在上面拖放三个HTML标记。一个span,一个input(Button),一个input(Text)。注意,是客户端HTML标记,不含有runat="server"的,从这里已经可以看到一个很大的不同,我们使用的是客户端HTML控件。接着在input(Button)上双击,会自动生成Javascript脚本,此时aspx页面的主要代码如下:
<input id="txtSample" type="text" style="width:120px" /><br /> <span id="spnTime">[未设置]</span><br /> <br /> <input id="Button1" type="button" value="更新时间" onclick="return Button1_onclick()" />
最关键是接下来要编写的javascript代码,我先将它贴出来,然后再进行解释:
<script language="javascript" type="text/javascript"> function Button1_onclick() { var context = "Callback Values"; // 传给回调函数 var clientValue = $get("txtSample").value; // 获得TextBox的值 SimpleService.GetCurrentDate(clientValue, OnComplete, OnFailed, context); return true; } function OnComplete(args, context){ alert(context); var span = $get("spnTime"); span.innerHTML = args; } function OnFailed(args){ alert("更新日期失败!"); } </script>
首先看这个Button1_onclick()方法,我们先声明了一个context,这个content很类似于在C#的委托变量上调用BeginInvoke()方法时的最后一个参数,这个值用于传递给回调函数,以方便进行一些处理。接着我们获取了input(Text)中输入的值,保存在了clientValue中。然后调用了Web服务,其中第一个参数就是上面定义的GetCurrentDate()时的参数,我们传入了clientValue,第二个参数是OnComplete是成功时的回调函数,第三个是OnFailed是失败时的回调函数,最后一个参数我们传递了context,可以将它交由回调函数处理。因为是异步操作,所以没有在这里获取GetCurrentDate()方法返回值,而是通过回调函数OnComplete的参数对返回值进行了传递。
接下来看OnComplete()函数,其实我们最需要搞清楚的就是它的两个参数:第一个args即为Web服务方法GetCurrentDate()的返回值;而第二个参数,即为调用GetCurrentDate()时传递的最后一个参数。在方法内部,我们使用alert()显示了context的值,随后将Web服务的返回值显示在了span中。OnFailed()仅仅是提示用户更新失败。
接下来在页面上点击Button,应该可以看到下面的效果:
我们再次在Page_Load的位置设置断点,然后启动调试,会发现当我们使用这种方式时,点击Button服务端并没有再次执行页面生命周期,而参与客户端/服务端往返的数据也是最少量的(仅往返必需数据),因此,虽然采用这种方式我们需要编写一定量的javascript代码,但是却能够显著地提高站点的性能。
总结
这篇文章简单的讲述了使用Asp.Net Ajax进行开发时常见的两种方式,使用UpdatePanel + 服务器控件;或者是使用 Web Service + HTML标记 + Javascript,并且这两种方式的实现方式和效果做了简要的说明。