ASP.NET 2.0客户端回调的实现分析(转载)
开发人员使用JavaScript的一个主要原因就是可以避免回发过程中带来的页面刷新。例如我们可以根据用户的需要使用Treeview控件来展开和折叠相应的数据节点。当你展开一个节点时,该Treeview控件将会利用JavaScript读取服务器上的子节点信息,然后平滑无刷新地插入这些新节点。如果没有使用JavaScript的话,Treeview控件将会因为页面的回发而重新构建。不但用户会发现因页面刷新而带来的延迟,而且页面极有可能回复到原来的状态,即丢失前面所展开的那些子节点信息。对于服务器端来说,因为每次回发的过程中都要处理大量的视图状态(View State)信息,这也会严重影响程序的整体执行性能。
以前我们使用的JavaScript示例几乎都是自包含的,即它们通常是为了完成一些特殊的显示效果(例如弹出一个新的页面窗口),而没有和服务器端代码进行信息的交互。如果你也想构建一个类似的无刷新页面的话,你首先必须调用服务器端的一个特定的方法,等待服务器响应后就会将请求的信息传递到客户端,从而避免了回发这个过程。为了实现这个方案,你首先需要对如何将客户端脚本和服务器端代码进行通讯有个大致的了解。尽管有许多中方法可以实现这两者间的交互(例如调用Web服务),但是由于受到特定的浏览器和平台的限制,它们的实现是还是有一定的难度的。而在ASP.NET 2.0中,引入了一个称为"客户端回调"的功能,利用这个内建的解决方案我们可以轻松实现客户端脚本和服务器端代码间的交互,从而避免了页面因回发带来的频繁刷新。
客户端回调本质上就是指通过前端的客户端脚本向服务器端传递相应的数据参数,服务器端再以接受到的参数进行查询和处理,最后将结果回传到客户端进行显示。虽然这样的过程不是一种创举,但是对于许多开发者来说这在某种思维上还是无法理解的,因为JavaScript的内存管理和.NET CLR的内存管理是不同的进程,而且管理的空间上也截然不同,所以彼此间无法直接参照也没有直接进行交互的方式,而客户端回调却是实现客户端和服务器端进行沟通的方法之一,又因为它是在客户端触发的,所以这就应该是"客户端回调"命名的由来吧!
创建一个简单的客户端回调
为了在ASP.NET中展示一个客户端回调的实例,首先我们将概述客户端回调间的交互过程是如何实现的。下面是基本的步骤:
1. 在某时刻激活一个JavaScript事件,从而触发客户端回调。
2. 触发客户端回调发生后,服务器端的一个方法将被执行。该方法有一个固定的模式――它接受的是一个字符串参数,并且返回的也是一个字符串参数。
3. 一旦页面接受到来自服务器端方法的响应结果后,它就可以利用JavaScript修改一些和用户界面有关的信息(例如显示在页面上显示返回的结果)
对于开发人员来说,底层的交互过程是非常复杂的,ASP.NET则将交互的处理过程进行了抽象化,这样使得开发人员可以直接建立表层的客户端回调,而无需考虑底层的操作是如何实现的。
下面的实例中,页面中放置了一个文本框,一个提交按钮和一个标签。文本框是用来接受用户的输入信息,在单击提交按钮后将把文本框中输入的信息在标签上进行实时的显示。注意,在输入信息后单击提交按钮时,并没有像以前传统的提交方式那样重新对页面进行构建和刷新。图1-1为该实例的效果图。
实例的效果图 实例的效果图 |
创建基本的页面
按照上图的布局在工具栏的"标准"标签中拖拽出一个TextBox控件,一个Label控件到主窗体上。然后在"HTML"标签中拖拽出一个InputButton的HTML按钮,注意,该按钮不是我们经常使用的服务器端控件,而是一个HTML元素。在按钮中添加一个onclick事件,点击该按钮这将向服务器端发出回调请求,这个onclick事件的具体实现细节将在以后的过程中加以说明。得到的初始页面代码如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CallBackExample.aspx.cs" Inherits="CallBackExample" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>客户端回调</title> </head> <body> <form id="form1" runat="server"> <div> 请输入信息:<asp:TextBox ID="txtEnter" runat="server"></asp:TextBox> <input id="btnSubmit" type="button" value="提交" onclick="CallServer(txtEnter,lblShow)"/> <br /> <asp:Label ID="lblShow" runat="server"></asp:Label> </div> </form> </body> </html> |
执行回调
为了实现客户端回调,你的页面逻辑代码中必须实现一个ICallbackEventHandler接口。代码如下:
public partial class CallBackExample : System.Web.UI.Page, System.Web.UI.ICallbackEventHandler {… …} |
ICallbackEventHandler接口定义了两个方法,RaiseCallbackEvent()从浏览器接受一个字符串作为事件参数,即该方法接受客户端JavaScript使传递的参数,注意它是首先触发的。接下来触发的就是GetCallbackResult()方法,它将所得到的结果传回给客户端的JavaScript,JavaScript再将结果更新到页面。
本例中的RaiseCallbackEvent()中的参数为我们在文本框的输入信息。为了表明它是从服务器返回的,我们加了一些说明性的文字。然后用GetCallbackResult()方法中将结果传回到客户端。完整的页面逻辑代码如下:
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class CallBackExample : System.Web.UI.Page, System.Web.UI.ICallbackEventHandler { //定义一个字符串,回调的结果信息将保存在该字符串中 private string result; //引发回调事件处理 public void RaiseCallbackEvent(string eventArgument) { //"eventArgument"为从客户端的JavaScript传递过来的参数 result = "从服务器端返回的内容:" + eventArgument; } //回传回调结果 public string GetCallbackResult() { return result; } } |
编写客户端脚本
客户端脚本主要是用来在服务器端和客户端之间进行信息的交互,就拿本例来说,我们在前面的页面逻辑代码中使用了一个名为eventArgument参数,这是怎么实现参数的传递的呢?我们将在后面的章节进行讨论,现在在页面中添加如下的JavaScript函数代码
function CallServer(inputcontrol,context) { //回调还没有处理完全时其预先加载的显示值 context.innerHTML = "加载中......"; //为你在文本框中输入的信息,并且arg在这里就是将其值传递到 //RaiseCallbackEvent(String eventArgument)方法对应的eventArgument中 arg = inputcontrol.value; //获取一个对客户端函数的引用;调用该函数时,将启动一个对服务器端事件的客户端回调。 <%= ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context")%>; } |
我们在上述的JavaScript函数代码引用了一个ClientScript.GetCallbackEventReference(……)方法,该方法实现的是什么功能呢?下面是摘自MSDN2上的对ClientScript.GetCallbackEventReference(……)的详细说明。
public string GetCallbackEventReference (Control control,string argument,string clientCallback,string context)
参数:
参数 | 作用 |
control | 处理客户端回调的服务器 Control。该控件必须实现 ICallbackEventHandler 接口并提供 RaiseCallbackEvent 方法。 |
argument | 从客户端脚本传递一个参数到服务器端的RaiseCallbackEvent 方法。 |
clientCallback | 一个客户端事件处理程序的名称,该处理程序接收服务器端事件返回的结果。 |
context | 启动回调之前在客户端的客户端脚本信息。脚本的结果传回给客户端事件处理程序。 |
返回值 | 调用客户端回调的客户端函数的名称。 |
下面是ClientScriptManager.GetCallbackEventReference 方法的重载列表
名称 | 说明 |
ClientScriptManager.GetCallbackEventReference (Control, String, String, String) | 获取一个对客户端函数的引用;调用该函数时,将启动一个对服务器端事件的客户端回调。此重载方法的客户端函数包含指定的控件、参数、客户端脚本和上下文。 |
ClientScriptManager.GetCallbackEventReference (Control, String, String, String, Boolean) | 获取一个对客户端函数的引用;调用该函数时,将启动一个对服务器端事件的客户端回调。此重载方法的客户端函数包含指定的控件、参数、客户端脚本、上下文和布尔值。 |
ClientScriptManager.GetCallbackEventReference (Control, String, String, String, String, Boolean) | 获取一个对客户端函数的引用;调用该函数时,将启动一个对服务器端事件的客户端回调。此重载方法的客户端函数包含指定的控件、参数、客户端脚本、上下文、错误处理程序和布尔值。 |
ClientScriptManager.GetCallbackEventReference (String, String, String, String, String, Boolean) | 获取一个对客户端函数的引用;调用该函数时,将启动一个对服务器端事件的客户端回调。此重载方法的客户端函数包含指定的目标、参数、客户端脚本、上下文、错误处理程序和布尔值。 我们就整个程序作个系统的说明,并且列出前台的页面代码和后台的逻辑代码,这样可以使得你对程序有个直观的理解。 |
后台代码 CallBackExample.aspx.cs
代码说明:
若要从客户端成功运行服务器代码,而不执行回发,必须在服务器页代码中实现合适的接口。为此我们在第12行代码中对 ICallbackEventHandler 接口进行声明。第15和19行创建了两个服务器端的代码回调方法。第15行的"RaiseCallbackEvent()"方法中的"eventArgument"字符串参数是来什么地方呢?转到前台页面的第10行代码,arg就相当于传递给"RaiseCallbackEvent()"方法的实参。第19行的"GetCallbackResult()"方法则将通过"RaiseCallbackEvent()"方法得到的结果返回给客户端,即结果"result"最终被传递到前台页面的第15代码所示的"ReceiveServerData()"方法的"result"参数中。
前台代码 CallBackExample.aspx
代码说明:
为了向服务器页发送回调和接收结果这两个功能,我们在前台页面中定义了2个客户端脚本函数。第7行所示的"CallServer()"函数实现的就是发送回调的功能,注意发送回调的函数实际是在服务器端实现的,这是因为真正实现发送回调的是第11行的"ClientScript.GetCallbackEventReference()"方法,而"CallServer()"函数只是对"ClientScript.GetCallbackEventReference()"方法的引用,并提供一些必要的参数。
现在我们来详细讲解下这些客户端函数的具体实现细节,我们通过单击页面第25和26行所声明的按钮后,将触发OnClick事件。
在"CallServer()"中将文本框和标签作为参数传递到第7行相应的JavaScript函数后,"inputcontrol"和"context"就如同成为文本框和标签控件形参数。第9行代码表示没有接受到回调结果前"context"将显示一个"加载中……"的信息,直到回调完成后才用第17行的代码将回调的结果用"context"重新进行显示。第10行代码表示将文本框的输入值赋予"arg",并且"arg"在第12行代码的ClientScript.GetCallbackEventReference()方法中作为对应的第2个参数。第一个参数用"this"表示对本页面的引用,因为ClientScript.GetCallbackEventReference()方法也是在CallBackExample.aspx实现的。第三个参数表示接受回调结果的客户端函数,它在这和第15行代码所实现的ReceiveServerData()函数相匹配(注意函数名称必须是一致的,否则会导致出错),回调的结果将通过"context"进行显示。第四个参数"context"用于从服务器端返回的上下文,因为ClientScript.GetCallbackEventReference()方法是在服务器端执行的,在该方法中原本传递的"context"内容为一个"加载中……"的信息,回调返回后"context"在第17行中被重新进行改写,如果没有对上下文的引用的话,你可以将该参数设置为"null"。
读取数据库信息的客户端回调程序
本程序是一个实现读取Northwind数据库的Emlpoyees信息,为此你必须先保证Northwind数据库存在。下图是Emlpoyees表的内容。
在文本框输入要查找的用户名后,接着点击"回调"按钮就会发生客户端回调。这是查找到用户的显示结果
这是查找到用户的显示结果 |
用户不存在的显示信息:
用户不存在的显示信息 |
后台代码:ClientCallbacksSimple.aspx.cs
01 using System; 02 using System.Data; 03 using System.Configuration; 04 using System.Collections; 05 using System.Web; 06 using System.Web.Security; 07 using System.Web.UI; 08 using System.Web.UI.WebControls; 09 using System.Web.UI.WebControls.WebParts; 10 using System.Web.UI.HtmlControls; 11 using System.Data.SqlClient; 12 public partial class ClientCallbacksSimple : System.Web.UI.Page, 13 13 System.Web.UI.ICallbackEventHandler 14 { 15 protected string strUserInfo; //保存读取的用户信息 16 //引发回调事件 17 public void RaiseCallbackEvent(string txtFirstName) 18 { 19 if (txtFirstName != null) 20 { 21 SqlConnection conn = new SqlConnection("data source=localhost;initial 22 catalog=Northwind;integrated security=SSPI"); 23 conn.Open(); 24 SqlCommand cmd = new SqlCommand("select EmployeeID,FirstName,City,Address 25 25 from Employees where FirstName=@FirstName", conn); 26 cmd.Parameters.Add("@FirstName", SqlDbType.NVarChar, 10).Value = txtFirstName; 27 SqlDataReader dr = cmd.ExecuteReader(); 28 if (dr.Read()) 29 { 30 strUserInfo = "员工代号:" + dr["EmployeeID"] + "\r\n"; 31 strUserInfo += "姓名:" + dr["FirstName"] + "\r\n"; 32 strUserInfo += "居住城市:" + dr["City"] + "\r\n"; 33 strUserInfo += "地址:" + dr["Address"].ToString().Replace("\r\n","")+ "\r\n"; 34 strUserInfo += "服务器查询时间:" + DateTime.Now.ToLongTimeString(); 35 } 36 else 37 { 38 if (String.IsNullOrEmpty(txtFirstName)) 39 { 40 strUserInfo = "请输入姓名"; 41 } 42 else 43 { 44 strUserInfo = "查无此人"; 45 } 46 } 47 cmd.Dispose(); 48 dr.Dispose(); 49 conn.Dispose(); 50 } 51 } 52 //回传回调结果 53 public string GetCallbackResult() 54 { 55 return strUserInfo; //回传员工的基本信息 56 } 57 } |
代码说明:在RaiseCallbackEvent()方法中,传递了一个从前台页面文本框的输入数据作为其参数,即所要从数据库查询的用户名。第28-34行代码的功能为读取用户的详细信息,并且将用户信息保存在一个字符串strUserInfo中。如果查找不到相应的用户,则返回一些出错信息,见代码36-45。GetCallbackResult()方法回发回调结果,即保存用户信息的字符串。
前台代码:ClientCallbacksSimple.aspx
01 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="ClientCallbacksSimple.aspx.cs" 02 Inherits="ClientCallbacksSimple" %> 03 <html xmlns="http://www.w3.org/1999/xhtml" > 04 <head runat="server"> 05 <title>读取数据库信息的客户端回调程序</title> 06 <script type="text/JavaScript"> 07 function OnCallback(strUserInfo,context) 08 { 09 Results.innerText = strUserInfo; 10 } 11 </script> 12 </head> 13 <body> 14 <form id="form1" runat="server"> 15 <div> 16 姓名:<input id="txtUserName" type="text" /> 17 <input id="btnCallback" type="button" value="回调" onclick="<%= 18 ClientScript.GetCallbackEventReference(this,"document.form1.txtUserName.value", 19 "OnCallback",null) %>" /> 20 <br /> 21 <div ID="Results" style="background-color: pink"></div> 22 </div> 23 </form> 24 </body> 25 </html> |
代码说明:这个程序与第一个程序的最大不同就是前台页面上有些细微的区别。如代码17-19所示,我们将发送回调的ClientScript.GetCallbackEventReference()方法直接写在了按钮的单击事件中,这也是一种可行且简捷的方式。ClientScript.GetCallbackEventReference()方法的3个参数为"OnCallback",表示回调完成后将回调结果返回给客户端的OnCallback()脚本函数,在此回调结果strUserInfo将作为该函数的一个参数在页面进行显示,如代码9所示。由于我们在此没有用到上下文的联系,所以ClientScript.GetCallbackEventReference()方法的4个参数为"null",但是OnCallback()脚本函数还是要保留该"context"参数,因为这是接受回调结果的客户端函数的固定格式。
小结:
注意所有的异步技术如本文所探讨的Callback客户端回调,以及微软新推出的Atlas框架,都不再使用传统的Postback。因此客户端在呈现由服务器端返回的数据时,浏览器下方将看不到一闪而过的绿色状态条,并且异步过程只传送和接受少量的数据,而非Postback过程中传递的整个ViewState状态,因此程序在执行性能上有了较大的提高。希望读者耐心理解和练习上述的两个实例,只有通过自己的实践才能理解客户端回调的精髓。