深度解析ASP.NET中的Callback機制
[2] 深度解析ASP.NET中的Callback機制
看到不少朋友最近在寫使用callback的文章,也有點手癢,也來涂鴉一下,挖掘挖掘callback的潛力。callback的一般使用方法還算簡單,直接參照msdn的幫助和范例就足夠了。但是想要真正用好、用精,或者想開發一些基于callback機制的WEB組件,那么,就要先深入了解callback的實現機制了。在本文中,Teddy將和您一起解析callback的整個調用、反饋機制,相信對于幫助您更好的使用callback,將能有一定的益處。
Callback vs ASP.NET AJAX
首先,談談ASP.NET AJAX。很多朋友可能會覺得奇怪,已經有了Callback,為什么又要出ASP.NET AJAX呢?關于這個問題,ASP.NET AJAX的作者怎么解釋,我倒沒有去調查。只不過從我個人對callback和ASP.NET AJAX的使用感受來講,覺得,callback作為一個接口和postback非常類似的實現,肯定是為了讓用戶類似使用postback來使用它。但是,它的這個類似postback的機制,應該說使用上還不是特別方便,也不易擴展,當然這是相比于其他的AJAX框架實現來說的。因此,微軟方面借鑒了許多的已有的AJAX實現,如Prototype,Backbase以及AJAX.NET,并結合ASP.NET2.0的部分特有功能,發明了這樣一個博采眾長的AJAX框架。基于ASP.NET AJAX來開發AJAX應用有多好,很難量化的來說,但至少不比其他的這些AJAX框架來的差是肯定的,加上微軟這個后臺,以及像live.com這樣的重量級站點的應用推廣,其影響當然是值得期待的。
不過,這也不是說callback實現沒一無是處了,作為程序員,我們需要有正確的態度,在正確的使用情形,使用最正確的技術。沒有哪一個框架是萬能的,是適合任何使用環境的;就像大家都在爭論那個軟件開發方法最好,CMMi,RUP,XP,AGILE~~,其實,沒有最好,最合適的才是最好的。我們最應該做的,是了解各種方案的原理和優缺點,從而,合理的使用正確的工具來解決實際問題。
Begin from Client Script
我們都知道,凡是AJAX,從底層來講,無外乎兩種實現機制:XMLHTTP以及IFRAME。在AJAX這個詞獲得廣泛關注之前,其實,基于這兩種底層實現的功能框架,或者基于這兩種技術的無刷新效果實現就已經被廣泛的使用了。當然,發展到今天,在使用接口方面,這些底層機制的細節往往被框架給隱藏了,使用接口變得越來越簡單,用戶只要調用這些簡單接口,沒有必要知道具體是怎么實現效果的了。
不過,這里我們既然是要解析callback的實現機制,那還是讓我們從一個callback調用的客戶端腳本調用開始,看看,微軟是怎么實現這個callback機制的。
1、ClientScript.GetCallbackEventReference(...)
要激發一個callback,首先,當然需要在客戶端本中發出一個調用。一個典型的調用語法如下:
function any_script_function(arg, context)
{
<%= ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context")%>;
}
</script>
ClientScript.GetCallbackEventReference(...)將根據傳入的參數返回實際的回調腳本。這個函數有多個重載版本,因此,這些參數的含義,大家可以參考MSDN。以具體的上面這段示例代碼中的參數來說:
- this表示執行回調的的服務端控件是當前這個Page,當前的Page必須實現ICallbackEventHandler接口,包括必須實現string GetCallbackResult()和void RaiseCallbackEvent(eventArgument)這兩個接口函數,這個參數也可以是指向某個WEB控件的引用,當然,這個空間也必須實現ICallbackEventHandler接口;
- "arg"是將被傳給RaiseCallbackEvent的參數eventArgument的值,可以使人以自定義格式的字符串;
- "ReceiveServerData"是當回調成功之后,處理返回內容的客戶端腳本函數的名稱,這個函數必須存在于執行回調的頁面,并且這個函數可以包含兩個參數,例如:
function ReceiveServerData(result, context)
{
}
</script>
這兩個參數,分別是回調的返回數據result,和原封不動被返回的我們激發回調時的這個context參數,當然,這兩個參數都是字符串類型的。
- "context"就不用多解釋了,記得這個參數會被原封不動的傳給指定的返回數據處理函數就行了。MSDN的官方文檔說,context一般可用來傳遞需要在客戶端的返回數據處理函數中用來調用的腳本代碼,不過實際上,你傳什么都可以,把它看成一種從客戶端回調的的激發端,到處理返回數據的接收段之間的參數傳遞通道就行了。
2、WebForm_DoCallback(...)
Ok,明白了以上代碼的含義,下面我們來看看,前面的這條“<%= ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context")%>;”在運行時會被解析成什么樣子呢?我們只要在頁面運行時察看頁面源碼就可以看到,實際上服務器幫我們生成了下面這段script代碼:
function any_script_function()
{
WebForm_DoCallback('__Page',arg,ReceiveServerData,context,null,false);
}
</script>
這段代碼是什么意思呢?很顯然的他調用了一個系統與定義的script函數:WebForm_DoCallback。我們要把這個函數找出來看看它具體為我們干了什么。在運行時的頁面源碼中,我們很容易可以找到這段腳本的出處。我們注意到有一個script,src="/TestCallbackWeb/WebResource.axd?d=HEcYmh-7_szSIu1D_mHSEw2&t=632661779991718750",這里就定義了WebForm_DoCallback。讓我們把它用flashget下載下來,將擴展名改為.js。看看源碼吧,沒有被混淆的,所以很容易看明白。我這里就只把和callback相關的部分貼出來解釋一下,見代碼中的注釋:
var __pendingCallbacks = new Array();
var __synchronousCallBackIndex = -1;
//回調主函數WebForm_DoCallback
function WebForm_DoCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync) {
//構造回調參數,回調參數包括了原來頁面上的formpostdata和我們傳遞的目標控件、eventArgument和部分驗證信息
var postData = __theFormPostData +
"__CALLBACKID=" + WebForm_EncodeCallback(eventTarget) +
"&__CALLBACKPARAM=" + WebForm_EncodeCallback(eventArgument);
if (theForm["__EVENTVALIDATION"]) {
postData += "&__EVENTVALIDATION=" + WebForm_EncodeCallback(theForm["__EVENTVALIDATION"].value);
}
//下面實例化XMLHTTP對象,如果瀏覽器支持XMLHTTP則直接用XMLHTTP 執行異步回調
var xmlRequest,e;
try {
xmlRequest = new XMLHttpRequest();
}
catch(e) {
try {
xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e) {
}
}
var setRequestHeaderMethodExists = true;
try {
setRequestHeaderMethodExists = (xmlRequest && xmlRequest.setRequestHeader);
}
catch(e) {}
var callback = new Object();
callback.eventCallback = eventCallback;
callback.context = context;
callback.errorCallback = errorCallback;
callback.async = useAsync;
//獲取對應的回調對象
var callbackIndex = WebForm_FillFirstAvailableSlot(__pendingCallbacks, callback);
if (!useAsync) {
if (__synchronousCallBackIndex != -1) {
__pendingCallbacks[__synchronousCallBackIndex] = null;
}
__synchronousCallBackIndex = callbackIndex;
}
if (setRequestHeaderMethodExists) {
xmlRequest.onreadystatechange = WebForm_CallbackComplete;
callback.xmlRequest = xmlRequest;
xmlRequest.open("POST", theForm.action, true);
xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlRequest.send(postData);
return;
}
//萬一瀏覽器不支持XMLHTTP的話,我們IFRAME方案代替,在一個隱藏的 IFRAME中執行Postback
callback.xmlRequest = new Object();
var callbackFrameID = "__CALLBACKFRAME" + callbackIndex;
var xmlRequestFrame = document.frames[callbackFrameID];
if (!xmlRequestFrame) {
xmlRequestFrame = document.createElement("IFRAME");
xmlRequestFrame.width = "1";
xmlRequestFrame.height = "1";
xmlRequestFrame.frameBorder = "0";
xmlRequestFrame.id = callbackFrameID;
xmlRequestFrame.name = callbackFrameID;
xmlRequestFrame.style.position = "absolute";
xmlRequestFrame.style.top = "-100px"
xmlRequestFrame.style.left = "-100px";
try {
if (callBackFrameUrl) {
xmlRequestFrame.src = callBackFrameUrl;
}
}
catch(e) {}
document.body.appendChild(xmlRequestFrame);
}
var interval = window.setInterval(function() {
xmlRequestFrame = document.frames[callbackFrameID];
if (xmlRequestFrame && xmlRequestFrame.document) {
window.clearInterval(interval);
xmlRequestFrame.document.write("");
xmlRequestFrame.document.close();
xmlRequestFrame.document.write('<html><body><form method="post"><input type="hidden" name="__CALLBACKLOADSCRIPT" value="t"></form></body></html>');
xmlRequestFrame.document.close();
xmlRequestFrame.document.forms[0].action = theForm.action;
var count = __theFormPostCollection.length;
var element;
for (var i = 0; i < count; i++) {
element = __theFormPostCollection[i];
if (element) {
var fieldElement = xmlRequestFrame.document.createElement("INPUT");
fieldElement.type = "hidden";
fieldElement.name = element.name;
fieldElement.value = element.value;
xmlRequestFrame.document.forms[0].appendChild(fieldElement);
}
}
var callbackIdFieldElement = xmlRequestFrame.document.createElement("INPUT");
callbackIdFieldElement.type = "hidden";
callbackIdFieldElement.name = "__CALLBACKID";
callbackIdFieldElement.value = eventTarget;
xmlRequestFrame.document.forms[0].appendChild(callbackIdFieldElement);
var callbackParamFieldElement = xmlRequestFrame.document.createElement("INPUT");
callbackParamFieldElement.type = "hidden";
callbackParamFieldElement.name = "__CALLBACKPARAM";
callbackParamFieldElement.value = eventArgument;
xmlRequestFrame.document.forms[0].appendChild(callbackParamFieldElement);
if (theForm["__EVENTVALIDATION"]) {
var callbackValidationFieldElement = xmlRequestFrame.document.createElement("INPUT");
callbackValidationFieldElement.type = "hidden";
callbackValidationFieldElement.name = "__EVENTVALIDATION";
callbackValidationFieldElement.value = theForm["__EVENTVALIDATION"].value;
xmlRequestFrame.document.forms[0].appendChild(callbackValidationFieldElement);
}
var callbackIndexFieldElement = xmlRequestFrame.document.createElement("INPUT");
callbackIndexFieldElement.type = "hidden";
callbackIndexFieldElement.name = "__CALLBACKINDEX";
callbackIndexFieldElement.value = callbackIndex;
xmlRequestFrame.document.forms[0].appendChild(callbackIndexFieldElement);
xmlRequestFrame.document.forms[0].submit();
}
}, 10);
}
//該函數在每次回調結束后會調用來檢查當前的回調列表中的回調的執行情況,如果,執行完畢的,則從列表中刪除回調對象,并刪除臨時建立的IFRAME
function WebForm_CallbackComplete() {
for (i = 0; i < __pendingCallbacks.length; i++) {
callbackObject = __pendingCallbacks[i];
if (callbackObject && callbackObject.xmlRequest && (callbackObject.xmlRequest.readyState == 4)) {
WebForm_ExecuteCallback(callbackObject);
if (!__pendingCallbacks[i].async) {
__synchronousCallBackIndex = -1;
}
__pendingCallbacks[i] = null;
var callbackFrameID = "__CALLBACKFRAME" + i;
var xmlRequestFrame = document.getElementById(callbackFrameID);
if (xmlRequestFrame) {
xmlRequestFrame.parentNode.removeChild(xmlRequestFrame);
}
}
}
}
//該函數執行我們在回調激發端指定的處理返回數據的script函數,如我們上面范例代碼中的ReceiveServerData函數
function WebForm_ExecuteCallback(callbackObject) {
var response = callbackObject.xmlRequest.responseText;
if (response.charAt(0) == "s") {
if ((typeof(callbackObject.eventCallback) != "undefined") && (callbackObject.eventCallback != null)) {
callbackObject.eventCallback(response.substring(1), callbackObject.context);
}
}
else if (response.charAt(0) == "e") {
if ((typeof(callbackObject.errorCallback) != "undefined") && (callbackObject.errorCallback != null)) {
callbackObject.errorCallback(response.substring(1), callbackObject.context);
}
}
else {
var separatorIndex = response.indexOf("|");
if (separatorIndex != -1) {
var validationFieldLength = parseInt(response.substring(0, separatorIndex));
if (!isNaN(validationFieldLength)) {
var validationField = response.substring(separatorIndex + 1, separatorIndex + validationFieldLength + 1);
if (validationField != "") {
var validationFieldElement = theForm["__EVENTVALIDATION"];
if (!validationFieldElement) {
validationFieldElement = document.createElement("INPUT");
validationFieldElement.type = "hidden";
validationFieldElement.name = "__EVENTVALIDATION";
theForm.appendChild(validationFieldElement);
}
validationFieldElement.value = validationField;
}
if ((typeof(callbackObject.eventCallback) != "undefined") && (callbackObject.eventCallback != null)) {
callbackObject.eventCallback(response.substring(separatorIndex + validationFieldLength + 1), callbackObject.context);
}
}
}
}
}
//獲取對應的回調對象
function WebForm_FillFirstAvailableSlot(array, element) {
var i;
for (i = 0; i < array.length; i++) {
if (!array[i]) break;
}
array[i] = element;
return i;
}
//再下面是一些輔助函數和與callback關系不大的函數,我就不列出來了,有興趣的朋友可以自己看看
//