Client Callback 是ASP.NET 2.0新增的一个特性。简单的说,就是在不刷新页面的情况下,用javascript向服务器端传递参数、调用服务器端的方法、并且得到服务器端的返回值进行处理。
1> Why Client Callback
HTTP是无状态的协议。在HTTP协议之上开发的项目,常常需要从客户端调用服务器端的方法、执行服务器端的代码、从服务器端获取数据。这些都首先需要提交当前的页面,并且产生一次Postback过程。每次Postback都会让客户端的浏览器重新Render当前的页面。
这样的事件模型下,开发员不得不小心地维护客户端各控件的当前状态以保证它们不会丢失、不得不小心地维护ViewState的状态以避免出现不一致的情况。同时,庞大的ViewState也会造成页面的冗杂、下载速度变慢。另一方面,重新Render页面当然会耗费浏览器的时间来处理。如果页面很“重”——比如有很多的DHTML或者很多的控件——这当然会增加客户的等待时间,而频繁的等待常常会让客户不耐烦。最后,页面的频繁刷新恐怕会把所有人的眼睛弄花。
因此,从客户端代码(javascript)里调用服务器端的方法,以避免页面重新Rener和频繁刷新就是很有必要了。
2> XMLHTTP
在ASP.NET 2.0之前,我们实现这一功能的方式主要是XMLHTTP。Client Callback的底层实现机制也是XMLHTTP。XMLHTTP是在客户端详服务器端发送请求并且得到服务器端返回值的一种古老而经典的技术,比如下面这个例子。
function getXmlHttp ( )
{
var XmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");
XmlHttp.Open("GET", "http://www.yahoo.com", false);
XmlHttp.Send();
var homesource = XmlHttp.responseText ;
document.all.aa.innerHTML = homesource ;
}
</script>
<body id="bodyTag">
<input type="button" name="btnyahoo" value="click 4 yahoo" onclick="getXmlHttp();">
<div id="aa" name="aa"></div>
</body>
当点击button时,触发的javascript函数会创建一个HTTP的请求,并发送给指定的URL。接收到服务器端的响应之后,把收到的信息展现出来,我们就可以在自己的网页中看到yahoo的页面了。
这一句XmlHttp.Open("GET", "http://www.yahoo.com", false);的第一个参数标识HTTP请求的方式是“GET”或者“POST”。第二个参数是请求发送目的的指定URL,可以在URL中包括以“?”标识以“&”连接的查询字符串。第三个参数是标识页面的请求与页面的响应是否同步,即是否应该等待服务器端的响应之后再进行页面的下一步响应。此处设为false,既不等待服务器端的响应,以异步方式处理。
接下来的一句XmlHttp.Send();是发送刚刚生成的HTTP请求。我们可以将要传给服务器端的参数填入Send函数的参数里,在服务器端进行解析。
接受的信息不仅仅是ResponseText的信息。如果把XmlHttp.responseText 改成XmlHttp.GetAllResponseHeaders;,我们就可以看到服务器发回的所有HTTP头信息了:)
[
注意:增加XMLHttpRequest读取中文网页时返回乱码的解决办法
XMLHttpRequest 默认是用UTF-8 传递数据。当服务端的返回数据是UTF-8编码的时候,它工作得很好(开发web应用,当服务端和客户端以及数据库统一使用UTF-8编码可以有效的避免乱码问题)。如果服务端设置了正确的Content-Type Response Header以及编码信息,那么XmlHttpRequest也可以正确工作。
可是当使用XMLHttpRequest读取中文网页内容时, 如果服务端的程序没有设置Content-Type Response Header,或者Header没有设置编码类型,那么我们访问responseText属性的时候就可能遭遇乱码。如以下代码用XMLHttpRequest获取雅虎中国网站的星座站首页:
xmlhttp = getXMLHttpRequest();
var url = "http://cn.astrology.yahoo.com/";;
xmlhttp.open("GET", url, true);
xmlhttp.onreadystatechange = function(){
if (xmlhttp.readyState == 4)
if (xmlhttp.status == 200)
alert(xmlhttp.responseText);
};
xmlhttp.send(null);
纵使yahoo中国这样专业的网站,对web标准的支持还很不彻底,弹出的html源码中充斥不符合web标准的html标签,当然还有已预见的乱码。
同样遗憾的是,FireFox 和 IE 的解决方法也是南辕北辙
FireFox
FireFox 的XMLHttpRequest对象支持overrideMimeType方法,可以指定返回数据的编码类型,利用该方法可以解决中文乱码,前面的代码修改如下:
xmlhttp = getXMLHttpRequest();
var url = "http://cn.astrology.yahoo.com/";;
xmlhttp.open("GET", url, true);
xmlhttp.overrideMimeType("text/html;charset=gb2312");//设定以gb2312编码识别数据
xmlhttp.onreadystatechange = function(){
if (xmlhttp.readyState == 4)
if (xmlhttp.status == 200)
alert(xmlhttp.responseText);
};
xmlhttp.send(null);
Internet Explorer
IE不支持overrideMimeType方法,并且只能用一种很蹩脚的方法来解决,此时需要引入一个杂交的函数:
function gb2utf8(data){
var glbEncode = [];
gb2utf8_data = data;
execScript("gb2utf8_data = MidB(gb2utf8_data, 1)", "VBScript");
var t=escape(gb2utf8_data).replace(/%u/g,"").replace(/(.{2})(.{2})/g,"%$2%$1").replace(/%([A-Z].)%(.{2})/g,"@$1$2");
t=t.split("@");
var i=0,j=t.length,k;
while(++i<j) {
k=t[i].substring(0,4);
if(!glbEncode[k]) {
gb2utf8_char = eval("0x"+k);
execScript("gb2utf8_char = Chr(gb2utf8_char)", "VBScript");
glbEncode[k]=escape(gb2utf8_char).substring(1,6);
}
t[i]=glbEncode[k]+t[i].substring(4);
}
gb2utf8_data = gb2utf8_char = null;
return unescape(t.join("%"));
}xmlhttp = getXMLHttpRequest();
var url = "http://cn.astrology.yahoo.com/";;
xmlhttp.open("GET", url, true);
xmlhttp.onreadystatechange = function(){
if (xmlhttp.readyState == 4)
if (xmlhttp.status == 200)
alert(gb2utf8(xmlhttp.responseBody)); //注意这里要用responseBody
};
xmlhttp.send(null);
gb2utf8函数直接解析XMLHttpRequest返回的二进制数据,其中要利用execScript方法来执行VBScript的函数。所以说是一个杂交的函数。感谢blueidea论坛 提供的算法。
虽然有了解决的办法,但形式丑陋,而且不符合web标准。所以应该在编程中尽量避免,如果是开发web应用,应尽量使用UTF-8编码,或者在服务端设置正确的编码信息。至于以上范例,有盗取其他网站内容的嫌疑,更是不为提倡。
gb2utf8函数确实好用!
]
3> Client Callback in ASP.NET 2.0
ASP.NET 2.0中新增了ICallbackEventHandler接口和Page.GetCallbackEventHandler方法来实现Client Callback的调用。
ICallbackEventHandler接口只有一个方法需要实现:string RaiseCallbackEvent(string eventArgument);这个方法就是在服务器端处理客户端调用请求的方法。参数eventArgument是从客户端传递的参数,返回值则是需要返回给客户端的处理结果。
Page.GetCallbackEventHandler方法共有三个重载方法,但都大同小异。这个方法的作用是生成一段在初始化Callback时供客户端调用的javascript代码段。以下面这种重载实现为例:
public string GetCallbackEventReference(System.Web.UI.Control control, string argument, string clientCallback, string context, string clientErrorCallback);
第一个参数是实现了ICallbackEventHandler接口的控件,.Net Framework将在内部调用该控件实现的RaiseCallbackEvent的方法。一般该参数为Page本身。第二个参数是从客户端代码中需要传递到服务器段的参数的变量名。第三个参数是处理服务器端返回给客户端处理结果后,客户端处理返回值的函数名。第四个参数是context变量的变量名。最后一个参数是,在回调过程中如果在服务器端发生异常,在客户端接受异常信息并处理该信息的函数名。
对于客户端的实现,需要在客户端代码中定义传参的变量,定义context变量,并且以<%...%>的方式访问服务器端变量,以调用服务器段的Page.GetCallbackEventHandler()方法。当调用Page.GetCallbackEventHandler方法时,客户端代码中的<%...%>部分会被替换成对__doCallback()客户端函数的调用。__doCallback函数是.Net Framework 2.0内置的一个javascript函数,作用是产生一段通过XMLHTTP方式对服务器端代码的调用。__doCallback函数的参数是同服务器端的GetCallbackEventHandler被调用时的参数完全一致。
客户端的context变量是一个很有趣的东西。在整个回调过程中,context变量都被浏览器缓存起来,而且不被发送到服务器端。context变量是由开发员设计的,它的作用是在多次回调间对不同的回调起标识作用。在客户端的处理服务器处理结果返回值的函数里,只需要判断context变量的自定义属性,就可以得到关于回调的标识信息。
客户端的实现包括:若干个初始化回调的函数、一个处理回调返回值的函数、和一个处理回调异常的函数。第二个函数的两个参数分别是服务器端返回值的字符串和context变量。第三个函数的参数分别是服务器端的异常信息和context变量。
Client Callback的调用顺序,首先通过调用GetCallbackEventHandler将客户端代码里的<%...%>表达式转化为__doCallback函数的调用。第二步,初始化回调时,通过__doCallback将要传递的参数发送给服务器端的RaiseCallbackEvent方法。第三步,回调结束,RaiseCallbackEvent的返回值传到客户端代码的对应函数处理。如果回调有异常,则通过客户端的错误处理函数处理。
4> Client Callback小结
不是所有的浏览器都支持Client Callback,因此可能需要使用.Net Framework 2.0新增的两个属性:SupportsCallback和SupportsXmlHttp。这两个属性都在Request.Browser下面。现在当然所有的浏览器对这两个属性的返回值都是一样的,或者全true,或者全false。之所以要设这两个属性,是为了将来的扩展。也许,以后的Client Callback不是用XmlHttp实现的呢:)
Client Callback通过XmlHttp,其实是对服务器的另一个同名页面的请求和处理。因此,使用Client Callback的时候要很小心。首先不要在服务器端代码里试图取控件的值,因为这是在另一个页面里处理,此时取得的不是当前页面的当前值。也就是说,不要使用从客户端传递的参数以外的任何值。其次,由于整个回调过程都没有将当前的表单提交,因此,要小心的维护页面的ViewState或者Session值。