客户端回调
Client Callback 是ASP.NET 2.0新增的一個(gè)特性。簡(jiǎn)單的說(shuō),就是在不刷新頁(yè)面的情況下,用javascript向服務(wù)器端傳遞參數(shù)、調(diào)用服務(wù)器端的方法、并且得到服務(wù)器端的返回值進(jìn)行處理。
?
1>?? Why Client Callback
?
HTTP是無(wú)狀態(tài)的協(xié)議。在HTTP協(xié)議之上開(kāi)發(fā)的項(xiàng)目,常常需要從客戶端調(diào)用服務(wù)器端的方法、執(zhí)行服務(wù)器端的代碼、從服務(wù)器端獲取數(shù)據(jù)。這些都首先需要提交當(dāng)前的頁(yè)面,并且產(chǎn)生一次Postback過(guò)程。每次Postback都會(huì)讓客戶端的瀏覽器重新Render當(dāng)前的頁(yè)面。
?
這樣的事件模型下,開(kāi)發(fā)員不得不小心地維護(hù)客戶端各控件的當(dāng)前狀態(tài)以保證它們不會(huì)丟失、不得不小心地維護(hù)ViewState的狀態(tài)以避免出現(xiàn)不一致的情況。同時(shí),龐大的ViewState也會(huì)造成頁(yè)面的冗雜、下載速度變慢。另一方面,重新Render頁(yè)面當(dāng)然會(huì)耗費(fèi)瀏覽器的時(shí)間來(lái)處理。如果頁(yè)面很“重”——比如有很多的DHTML或者很多的控件——這當(dāng)然會(huì)增加客戶的等待時(shí)間,而頻繁的等待常常會(huì)讓客戶不耐煩。最后,頁(yè)面的頻繁刷新恐怕會(huì)把所有人的眼睛弄花。
?
因此,從客戶端代碼(javascript)里調(diào)用服務(wù)器端的方法,以避免頁(yè)面重新Rener和頻繁刷新就是很有必要了。
?
2>?? XMLHTTP
?
在ASP.NET 2.0之前,我們實(shí)現(xiàn)這一功能的方式主要是XMLHTTP。Client Callback的底層實(shí)現(xiàn)機(jī)制也是XMLHTTP。XMLHTTP是在客戶端詳服務(wù)器端發(fā)送請(qǐng)求并且得到服務(wù)器端返回值的一種古老而經(jīng)典的技術(shù),比如下面這個(gè)例子。
????functiongetXmlHttp?(?)
????{
????????varXmlHttp?=?newActiveXObject("Msxml2.XMLHTTP.4.0");
????????XmlHttp.Open("GET",?"http://www.yahoo.com",?false);
????????XmlHttp.Send();
????????varhomesource?=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>
當(dāng)點(diǎn)擊button時(shí),觸發(fā)的javascript函數(shù)會(huì)創(chuàng)建一個(gè)HTTP的請(qǐng)求,并發(fā)送給指定的URL。接收到服務(wù)器端的響應(yīng)之后,把收到的信息展現(xiàn)出來(lái),我們就可以在自己的網(wǎng)頁(yè)中看到y(tǒng)ahoo的頁(yè)面了。
?
這一句XmlHttp.Open("GET", "http://www.yahoo.com", false);的第一個(gè)參數(shù)標(biāo)識(shí)HTTP請(qǐng)求的方式是“GET”或者“POST”。第二個(gè)參數(shù)是請(qǐng)求發(fā)送目的的指定URL,可以在URL中包括以“?”標(biāo)識(shí)以“&”連接的查詢字符串。第三個(gè)參數(shù)是標(biāo)識(shí)頁(yè)面的請(qǐng)求與頁(yè)面的響應(yīng)是否同步,即是否應(yīng)該等待服務(wù)器端的響應(yīng)之后再進(jìn)行頁(yè)面的下一步響應(yīng)。此處設(shè)為false,既不等待服務(wù)器端的響應(yīng),以異步方式處理。
?
接下來(lái)的一句XmlHttp.Send();是發(fā)送剛剛生成的HTTP請(qǐng)求。我們可以將要傳給服務(wù)器端的參數(shù)填入Send函數(shù)的參數(shù)里,在服務(wù)器端進(jìn)行解析。
?
?????? 接受的信息不僅僅是ResponseText的信息。如果把XmlHttp.responseText 改成XmlHttp.GetAllResponseHeaders;,我們就可以看到服務(wù)器發(fā)回的所有HTTP頭信息了:)
[
注意:增加XMLHttpRequest讀取中文網(wǎng)頁(yè)時(shí)返回亂碼的解決辦法
XMLHttpRequest?默認(rèn)是用UTF-8?傳遞數(shù)據(jù)。當(dāng)服務(wù)端的返回?cái)?shù)據(jù)是UTF-8編碼的時(shí)候,它工作得很好(開(kāi)發(fā)web應(yīng)用,當(dāng)服務(wù)端和客戶端以及數(shù)據(jù)庫(kù)統(tǒng)一使用UTF-8編碼可以有效的避免亂碼問(wèn)題)。如果服務(wù)端設(shè)置了正確的Content-Type?Response?Header以及編碼信息,那么XmlHttpRequest也可以正確工作。?
可是當(dāng)使用XMLHttpRequest讀取中文網(wǎng)頁(yè)內(nèi)容時(shí),?如果服務(wù)端的程序沒(méi)有設(shè)置Content-Type?Response?Header,或者Header沒(méi)有設(shè)置編碼類型,那么我們?cè)L問(wèn)responseText屬性的時(shí)候就可能遭遇亂碼。如以下代碼用XMLHttpRequest獲取雅虎中國(guó)網(wǎng)站的星座站首頁(yè):??
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中國(guó)這樣專業(yè)的網(wǎng)站,對(duì)web標(biāo)準(zhǔn)的支持還很不徹底,彈出的html源碼中充斥不符合web標(biāo)準(zhǔn)的html標(biāo)簽,當(dāng)然還有已預(yù)見(jiàn)的亂碼。?
同樣遺憾的是,FireFox?和?IE?的解決方法也是南轅北轍?
FireFox?
FireFox?的XMLHttpRequest對(duì)象支持overrideMimeType方法,可以指定返回?cái)?shù)據(jù)的編碼類型,利用該方法可以解決中文亂碼,前面的代碼修改如下:??
xmlhttp?=?getXMLHttpRequest();?
var?url?=?"http://cn.astrology.yahoo.com/";;?
xmlhttp.open("GET",?url,?true);?
xmlhttp.overrideMimeType("text/html;charset=gb2312");//設(shè)定以gb2312編碼識(shí)別數(shù)據(jù)?
xmlhttp.onreadystatechange?=?function(){?
if?(xmlhttp.readyState?==?4)?
????if?(xmlhttp.status?==?200)?
????????alert(xmlhttp.responseText);?
};?
xmlhttp.send(null);?
Internet?Explorer?
IE不支持overrideMimeType方法,并且只能用一種很蹩腳的方法來(lái)解決,此時(shí)需要引入一個(gè)雜交的函數(shù):??
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函數(shù)直接解析XMLHttpRequest返回的二進(jìn)制數(shù)據(jù),其中要利用execScript方法來(lái)執(zhí)行VBScript的函數(shù)。所以說(shuō)是一個(gè)雜交的函數(shù)。感謝blueidea論壇?提供的算法。?
雖然有了解決的辦法,但形式丑陋,而且不符合web標(biāo)準(zhǔn)。所以應(yīng)該在編程中盡量避免,如果是開(kāi)發(fā)web應(yīng)用,應(yīng)盡量使用UTF-8編碼,或者在服務(wù)端設(shè)置正確的編碼信息。至于以上范例,有盜取其他網(wǎng)站內(nèi)容的嫌疑,更是不為提倡。??
gb2utf8函數(shù)確實(shí)好用!
]?
3>?? Client Callback in ASP.NET 2.0
?
ASP.NET 2.0中新增了ICallbackEventHandler接口和Page.GetCallbackEventHandler方法來(lái)實(shí)現(xiàn)Client Callback的調(diào)用。
?
ICallbackEventHandler接口只有一個(gè)方法需要實(shí)現(xiàn):string RaiseCallbackEvent(string eventArgument);這個(gè)方法就是在服務(wù)器端處理客戶端調(diào)用請(qǐng)求的方法。參數(shù)eventArgument是從客戶端傳遞的參數(shù),返回值則是需要返回給客戶端的處理結(jié)果。
?
Page.GetCallbackEventHandler方法共有三個(gè)重載方法,但都大同小異。這個(gè)方法的作用是生成一段在初始化Callback時(shí)供客戶端調(diào)用的javascript代碼段。以下面這種重載實(shí)現(xiàn)為例:
public?string?GetCallbackEventReference(System.Web.UI.Control?control,?string?argument,?string?clientCallback,?string?context,?string?clientErrorCallback);
第一個(gè)參數(shù)是實(shí)現(xiàn)了ICallbackEventHandler接口的控件,.Net Framework將在內(nèi)部調(diào)用該控件實(shí)現(xiàn)的RaiseCallbackEvent的方法。一般該參數(shù)為Page本身。第二個(gè)參數(shù)是從客戶端代碼中需要傳遞到服務(wù)器段的參數(shù)的變量名。第三個(gè)參數(shù)是處理服務(wù)器端返回給客戶端處理結(jié)果后,客戶端處理返回值的函數(shù)名。第四個(gè)參數(shù)是context變量的變量名。最后一個(gè)參數(shù)是,在回調(diào)過(guò)程中如果在服務(wù)器端發(fā)生異常,在客戶端接受異常信息并處理該信息的函數(shù)名。
?
對(duì)于客戶端的實(shí)現(xiàn),需要在客戶端代碼中定義傳參的變量,定義context變量,并且以<%...%>的方式訪問(wèn)服務(wù)器端變量,以調(diào)用服務(wù)器段的Page.GetCallbackEventHandler()方法。當(dāng)調(diào)用Page.GetCallbackEventHandler方法時(shí),客戶端代碼中的<%...%>部分會(huì)被替換成對(duì)__doCallback()客戶端函數(shù)的調(diào)用。__doCallback函數(shù)是.Net Framework 2.0內(nèi)置的一個(gè)javascript函數(shù),作用是產(chǎn)生一段通過(guò)XMLHTTP方式對(duì)服務(wù)器端代碼的調(diào)用。__doCallback函數(shù)的參數(shù)是同服務(wù)器端的GetCallbackEventHandler被調(diào)用時(shí)的參數(shù)完全一致。
?
客戶端的context變量是一個(gè)很有趣的東西。在整個(gè)回調(diào)過(guò)程中,context變量都被瀏覽器緩存起來(lái),而且不被發(fā)送到服務(wù)器端。context變量是由開(kāi)發(fā)員設(shè)計(jì)的,它的作用是在多次回調(diào)間對(duì)不同的回調(diào)起標(biāo)識(shí)作用。在客戶端的處理服務(wù)器處理結(jié)果返回值的函數(shù)里,只需要判斷context變量的自定義屬性,就可以得到關(guān)于回調(diào)的標(biāo)識(shí)信息。
?
客戶端的實(shí)現(xiàn)包括:若干個(gè)初始化回調(diào)的函數(shù)、一個(gè)處理回調(diào)返回值的函數(shù)、和一個(gè)處理回調(diào)異常的函數(shù)。第二個(gè)函數(shù)的兩個(gè)參數(shù)分別是服務(wù)器端返回值的字符串和context變量。第三個(gè)函數(shù)的參數(shù)分別是服務(wù)器端的異常信息和context變量。
??????? Client Callback的調(diào)用順序,首先通過(guò)調(diào)用GetCallbackEventHandler將客戶端代碼里的<%...%>表達(dá)式轉(zhuǎn)化為_(kāi)_doCallback函數(shù)的調(diào)用。第二步,初始化回調(diào)時(shí),通過(guò)__doCallback將要傳遞的參數(shù)發(fā)送給服務(wù)器端的RaiseCallbackEvent方法。第三步,回調(diào)結(jié)束,RaiseCallbackEvent的返回值傳到客戶端代碼的對(duì)應(yīng)函數(shù)處理。如果回調(diào)有異常,則通過(guò)客戶端的錯(cuò)誤處理函數(shù)處理。
?
4>?? Client Callback小結(jié)
?
不是所有的瀏覽器都支持Client Callback,因此可能需要使用.Net Framework 2.0新增的兩個(gè)屬性:SupportsCallback和SupportsXmlHttp。這兩個(gè)屬性都在Request.Browser下面?,F(xiàn)在當(dāng)然所有的瀏覽器對(duì)這兩個(gè)屬性的返回值都是一樣的,或者全true,或者全false。之所以要設(shè)這兩個(gè)屬性,是為了將來(lái)的擴(kuò)展。也許,以后的Client Callback不是用XmlHttp實(shí)現(xiàn)的呢:)
?
Client Callback通過(guò)XmlHttp,其實(shí)是對(duì)服務(wù)器的另一個(gè)同名頁(yè)面的請(qǐng)求和處理。因此,使用Client Callback的時(shí)候要很小心。首先不要在服務(wù)器端代碼里試圖取控件的值,因?yàn)檫@是在另一個(gè)頁(yè)面里處理,此時(shí)取得的不是當(dāng)前頁(yè)面的當(dāng)前值。也就是說(shuō),不要使用從客戶端傳遞的參數(shù)以外的任何值。其次,由于整個(gè)回調(diào)過(guò)程都沒(méi)有將當(dāng)前的表單提交,因此,要小心的維護(hù)頁(yè)面的ViewState或者Session值。
總結(jié)
- 上一篇: 为什么每个人都应该尝试Ubuntu下篇
- 下一篇: 企业应该如何选型ERP?