深度解析ASP.NET2.0中的Callback機制
callback的一般使用方法還算簡單,直接參照msdn的幫助和范例就足夠了。但是想要真正用好、用精,或者想開發一些基于callback機制的WEB組件,那么,就要先深入了解callback的實現機制了。在本文中,Teddy將和您一起解析callback的整個調用、反饋機制,相信對于幫助您更好的使用callback,將能有一定的益處。
Callback vs Atlas
首先,談談Atlas。很多朋友可能會覺得奇怪,已經有了Callback,為什么又要出Atlas呢?關于這個問題,Atlas的作者怎么解釋,我倒沒有去調查。只不過從我個人對callback和atlas的使用感受來講,覺得,callback作為一個接口和postback非常類似的實現,肯定是為了讓用戶類似使用postback來使用它。但是,它的這個類似postback的機制,應該說使用上還不是特別方便,也不易擴展,當然這是相比于其他的AJAX框架實現來說的。因此,微軟方面借鑒了許多的已有的AJAX實現,如Prototype,Backbase以及AJAX.NET,并結合ASP.NET2.0的部分特有功能,發明了這樣一個博采眾長的AJAX框架。基于Atlas來開發AJAX應用有多好,很難量化的來說,但至少不比其他的這些AJAX框架來的差是肯定的,加上微軟這個后臺,以及像live.com這樣的重量級站點的應用推廣,其影響當然是值得期待的。
不過,這也不是說callback實現沒一無是處了,作為程序員,我們需要有正確的態度,在正確的使用情形,使用最正確的技術。沒有哪一個框架是萬能的,是適合任何使用環境的;就像大家都在爭論那個軟件開發方法最好,CMMi,RUP,XP,AGILE'',其實,沒有最好,最合適的才是最好的。我們最應該做的,是了解各種方案的原理和優缺點,從而,合理的使用正確的工具來解決實際問題。
Begin from Client Script
我們都知道,凡是AJAX,從底層來講,無外乎兩種實現機制:XMLHTTP以及IFRAME。在AJAX這個詞獲得廣泛關注之前,其實,基于這兩種底層實現的功能框架,或者基于這兩種技術的無刷新效果實現就已經被廣泛的使用了。當然,發展到今天,在使用接口方面,這些底層機制的細節往往被框架給隱藏了,使用接口變得越來越簡單,用戶只要調用這些簡單接口,沒有必要知道具體是怎么實現效果的了。
不過,這里我們既然是要解析callback的實現機制,那還是讓我們從一個callback調用的客戶端腳本調用開始,看看,微軟是怎么實現這個callback機制的。
1、ClientScript.GetCallbackEventReference(...)
要激發一個callback,首先,當然需要在客戶端本中發出一個調用。一個典型的調用語法如下:
ClientScript.GetCallbackEventReference(...)將根據傳入的參數返回實際的回調腳本。這個函數有多個重載版本,因此,這些參數的含義,大家可以參考MSDN。以具體的上面這段示例代碼中的參數來說:
- this表示執行回調的的服務端控件是當前這個Page,當前的Page必須實現ICallbackEventHandler接口,包括必須實現string GetCallbackResult()和void RaiseCallbackEvent(eventArgument)這兩個接口函數,這個參數也可以是指向某個WEB控件的引用,當然,這個空間也必須實現ICallbackEventHandler接口;
- "arg"是將被傳給RaiseCallbackEvent的參數eventArgument的值,可以使人以自定義格式的字符串;
- "ReceiveServerData"是當回調成功之后,處理返回內容的客戶端腳本函數的名稱,這個函數必須存在于執行回調的頁面,并且這個函數可以包含兩個參數,例如:
這兩個參數,分別是回調的返回數據result,和原封不動被返回的我們激發回調時的這個context參數,當然,這兩個參數都是字符串類型的。
- "context"就不用多解釋了,記得這個參數會被原封不動的傳給指定的返回數據處理函數就行了。MSDN的官方文檔說,context一般可用來傳遞需要在客戶端的返回數據處理函數中用來調用的腳本代碼,不過實際上,你傳什么都可以,把它看成一種從客戶端回調的的激發端,到處理返回數據的接收段之間的參數傳遞通道就行了。
2、WebForm_DoCallback(...)
Ok,明白了以上代碼的含義,下面我們來看看,前面的這條“<%= ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context")%>;”在運行時會被解析成什么樣子呢?我們只要在頁面運行時察看頁面源碼就可以看到,實際上服務器幫我們生成了下面這段script代碼:
這段代碼是什么意思呢?很顯然的他調用了一個系統與定義的script函數:WebForm_DoCallback。我們要把這個函數找出來看看它具體為我們干了什么。在運行時的頁面源碼中,我們很容易可以找到這段腳本的出處。我們注意到有一個script,src="/TestCallbackWeb/WebResource.axd?d=HEcYmh-7_szSIu1D_mHSEw2&t=632661779991718750",這里就定義了WebForm_DoCallback。讓我們把它用flashget下載下來,將擴展名改為.js。看看源碼吧,沒有被混淆的,所以很容易看明白。我這里就只把和callback相關的部分貼出來解釋一下,見代碼中的注釋:
從以上代碼我們可以很明白的看到,系統判斷您的瀏覽器是否支持XMLHTTP或IFRAME,如果至少支持其中之一,則用相應的方法執行回調,否則當然就是提示錯誤了。回調的時候,采用post的方式,異步post到當前頁面,然后等待回調結束,此時,由我們指定的返回數據處理script函數來處理返回的數據。
看到這里,我還不知道服務端怎么處理這個根據傳過來的參數解析、執行,并返回數據的過程。但是,我們已經知道,WebForm_DoCallback(...)將會將當前頁面的web控件的信息都post回去,這就意味著,我們在服務端有可能可以訪問到這些web控件的value,這還不錯,方便了我們處理當前數據。另一方面,eventArgument既然是一個任意格式的字符串參數,我們肯定要在服務段自己解析它的。
Serverside Callback Operation & Render
好了,那么接下來就讓我們來看看在服務端,ASP.NET都為我們做了些什么。
首先,我們知道,當前的Page是必須實現ICallbackEventHandler這個接口的,也就是其包含的兩個函數:string GetCallbackResult()和void RaiseCallbackEvent(eventArgument)。根據MSDN的文檔,我們知道,在一個callback被post到服務端時,Page將會首先將post回來的form data綁定到當前頁面的服務端web控件,接著判斷本次post是callback還是postback,如果是postpost,那么自然是原來的那個機制;
如果是callback,則將回調用觸發本次callback的控件(在本例中,我們在激發這個callback時,第一個參數指定的是this也就是當前的Page,那么這里當前的Page就是這個觸發控件)的RaiseCallbackEvent(eventArgument),當然,eventArgument也將會正確的傳過來,在這個函數的實現代碼里我們可以對這個參數進行解析處理,并在某個地方,存儲我們準備返回的數據,或者待處理的已經被解析出來的參數。
接著,系統將調用string GetCallbackResult(),在這個函數的實現代碼中,我們可以直接返回我們在RaiseCallback函數中存儲的準備返回的數據,或者根據待處理的已經被解析出來的參數處理這些參數,并返回結果。這個返回的字符串,自然將以腳本的形式被render回客戶端。被返回的腳本細節如下(反編譯Page.RenderCallback()的源碼),我們可以看到,返回的結果除了我們需要的結果數據和相應的腳本,沒有多余的數據,因此,callback的執行效率應該說還是不錯的:
另外,才發現原來System.Web.UI.Utils這個類中有那么多有用的方便的函數如QuateJScriptString(),以前一直自己手寫這樣功能的函數呢''真傻呀''
Conclusion
至此,我們已經基本上清楚明白callback的前臺幕后了。如果您對服務段的處理過程的細節還覺得不夠,您也可以自行反編譯Page對象,看看其實現代碼的細節,還是很有意思的。
總體而言,我們發現,callback無論是兼容性(XMLHTTP或IFRAME我想大多數瀏覽器都支持吧),還是性能(沒有返回不需要的數據),還是使用的便利性(因為ASP.NET幫我們綁定了頁面上的當前的Web控件的數據,這就意味著我們可以在callback后的服務端,象postback時一樣來寫代碼,也方便我們移植原來的postback的代碼到callback方式的代碼)都是非常優秀的。我們也完全可以擴展現有的控件,或者寫我們自己的控件以支持這樣的callback效果,并且,混合使用callback控件和原來的postback方式的控件也是非常可靠和容易的。這對我們升級原來的基于postback為主的代碼,是非常有利的,如果用Atlas來做同樣的代碼升級和與postback方式的控件混合使用,我可以跟您說,會有很多問題。不信你自己可以試試。
Callback vs Atlas
首先,談談Atlas。很多朋友可能會覺得奇怪,已經有了Callback,為什么又要出Atlas呢?關于這個問題,Atlas的作者怎么解釋,我倒沒有去調查。只不過從我個人對callback和atlas的使用感受來講,覺得,callback作為一個接口和postback非常類似的實現,肯定是為了讓用戶類似使用postback來使用它。但是,它的這個類似postback的機制,應該說使用上還不是特別方便,也不易擴展,當然這是相比于其他的AJAX框架實現來說的。因此,微軟方面借鑒了許多的已有的AJAX實現,如Prototype,Backbase以及AJAX.NET,并結合ASP.NET2.0的部分特有功能,發明了這樣一個博采眾長的AJAX框架。基于Atlas來開發AJAX應用有多好,很難量化的來說,但至少不比其他的這些AJAX框架來的差是肯定的,加上微軟這個后臺,以及像live.com這樣的重量級站點的應用推廣,其影響當然是值得期待的。
不過,這也不是說callback實現沒一無是處了,作為程序員,我們需要有正確的態度,在正確的使用情形,使用最正確的技術。沒有哪一個框架是萬能的,是適合任何使用環境的;就像大家都在爭論那個軟件開發方法最好,CMMi,RUP,XP,AGILE'',其實,沒有最好,最合適的才是最好的。我們最應該做的,是了解各種方案的原理和優缺點,從而,合理的使用正確的工具來解決實際問題。
Begin from Client Script
我們都知道,凡是AJAX,從底層來講,無外乎兩種實現機制:XMLHTTP以及IFRAME。在AJAX這個詞獲得廣泛關注之前,其實,基于這兩種底層實現的功能框架,或者基于這兩種技術的無刷新效果實現就已經被廣泛的使用了。當然,發展到今天,在使用接口方面,這些底層機制的細節往往被框架給隱藏了,使用接口變得越來越簡單,用戶只要調用這些簡單接口,沒有必要知道具體是怎么實現效果的了。
不過,這里我們既然是要解析callback的實現機制,那還是讓我們從一個callback調用的客戶端腳本調用開始,看看,微軟是怎么實現這個callback機制的。
1、ClientScript.GetCallbackEventReference(...)
要激發一個callback,首先,當然需要在客戶端本中發出一個調用。一個典型的調用語法如下:
<script language="javascript" type="text/javascript"> 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"是當回調成功之后,處理返回內容的客戶端腳本函數的名稱,這個函數必須存在于執行回調的頁面,并且這個函數可以包含兩個參數,例如:
<script type="text/javascript"> function ReceiveServerData(result, context) {} </script> |
這兩個參數,分別是回調的返回數據result,和原封不動被返回的我們激發回調時的這個context參數,當然,這兩個參數都是字符串類型的。
- "context"就不用多解釋了,記得這個參數會被原封不動的傳給指定的返回數據處理函數就行了。MSDN的官方文檔說,context一般可用來傳遞需要在客戶端的返回數據處理函數中用來調用的腳本代碼,不過實際上,你傳什么都可以,把它看成一種從客戶端回調的的激發端,到處理返回數據的接收段之間的參數傳遞通道就行了。
2、WebForm_DoCallback(...)
Ok,明白了以上代碼的含義,下面我們來看看,前面的這條“<%= ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context")%>;”在運行時會被解析成什么樣子呢?我們只要在頁面運行時察看頁面源碼就可以看到,實際上服務器幫我們生成了下面這段script代碼:
<script language="javascript" type="text/javascript"> 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相關的部分貼出來解釋一下,見代碼中的注釋:
//用于存放所有未完成的callback對象的數組__pendingCallbacks 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關系不大的函數,我就不列出來了,有興趣的朋友可以自己看看 // |
從以上代碼我們可以很明白的看到,系統判斷您的瀏覽器是否支持XMLHTTP或IFRAME,如果至少支持其中之一,則用相應的方法執行回調,否則當然就是提示錯誤了。回調的時候,采用post的方式,異步post到當前頁面,然后等待回調結束,此時,由我們指定的返回數據處理script函數來處理返回的數據。
看到這里,我還不知道服務端怎么處理這個根據傳過來的參數解析、執行,并返回數據的過程。但是,我們已經知道,WebForm_DoCallback(...)將會將當前頁面的web控件的信息都post回去,這就意味著,我們在服務端有可能可以訪問到這些web控件的value,這還不錯,方便了我們處理當前數據。另一方面,eventArgument既然是一個任意格式的字符串參數,我們肯定要在服務段自己解析它的。
Serverside Callback Operation & Render
好了,那么接下來就讓我們來看看在服務端,ASP.NET都為我們做了些什么。
首先,我們知道,當前的Page是必須實現ICallbackEventHandler這個接口的,也就是其包含的兩個函數:string GetCallbackResult()和void RaiseCallbackEvent(eventArgument)。根據MSDN的文檔,我們知道,在一個callback被post到服務端時,Page將會首先將post回來的form data綁定到當前頁面的服務端web控件,接著判斷本次post是callback還是postback,如果是postpost,那么自然是原來的那個機制;
如果是callback,則將回調用觸發本次callback的控件(在本例中,我們在激發這個callback時,第一個參數指定的是this也就是當前的Page,那么這里當前的Page就是這個觸發控件)的RaiseCallbackEvent(eventArgument),當然,eventArgument也將會正確的傳過來,在這個函數的實現代碼里我們可以對這個參數進行解析處理,并在某個地方,存儲我們準備返回的數據,或者待處理的已經被解析出來的參數。
接著,系統將調用string GetCallbackResult(),在這個函數的實現代碼中,我們可以直接返回我們在RaiseCallback函數中存儲的準備返回的數據,或者根據待處理的已經被解析出來的參數處理這些參數,并返回結果。這個返回的字符串,自然將以腳本的形式被render回客戶端。被返回的腳本細節如下(反編譯Page.RenderCallback()的源碼),我們可以看到,返回的結果除了我們需要的結果數據和相應的腳本,沒有多余的數據,因此,callback的執行效率應該說還是不錯的:
private void RenderCallback() { bool flag1 = !string.IsNullOrEmpty(this._requestValueCollection["__CALLBACKLOADSCRIPT"]); try { string text1 = null; if (flag1) { text1 = this._requestValueCollection["__CALLBACKINDEX"]; if (string.IsNullOrEmpty(text1)) { throw new HttpException(SR.GetString("Page_CallBackInvalid")); } for (int num1 = 0; num1 < text1.Length; num1++) { if (!char.IsDigit(text1, num1)) { throw new HttpException(SR.GetString("Page_CallBackInvalid")); } } this.Response.Write("<script>parent.__pendingCallbacks["); this.Response.Write(text1); this.Response.Write("].xmlRequest.responseText=""); } if (this._callbackControl != null) { string text2 = this._callbackControl.GetCallbackResult(); if (this.EnableEventValidation) { string text3 = this.ClientScript.GetEventValidationFieldValue(); this.Response.Write(text3.Length.ToString(CultureInfo.InvariantCulture)); this.Response.Write('|'); this.Response.Write(text3); } else { this.Response.Write('s'); } this.Response.Write(flag1 ? Util.QuoteJScriptString(text2) : text2); } if (flag1) { this.Response.Write("";parent.__pendingCallbacks["); this.Response.Write(text1); this.Response.Write("].xmlRequest.readyState=4;parent.WebForm_CallbackComplete();</script>"); } } catch (Exception exception1) { this.Response.Clear(); this.Response.Write('e'); if (this.Context.IsCustomErrorEnabled) { this.Response.Write(SR.GetString("Page_CallBackError")); return; } this.Response.Write(flag1 ? Util.QuoteJScriptString(HttpUtility.HtmlEncode(exception1.Message)) : HttpUtility.HtmlEncode(exception1.Message)); } } |
另外,才發現原來System.Web.UI.Utils這個類中有那么多有用的方便的函數如QuateJScriptString(),以前一直自己手寫這樣功能的函數呢''真傻呀''
Conclusion
至此,我們已經基本上清楚明白callback的前臺幕后了。如果您對服務段的處理過程的細節還覺得不夠,您也可以自行反編譯Page對象,看看其實現代碼的細節,還是很有意思的。
總體而言,我們發現,callback無論是兼容性(XMLHTTP或IFRAME我想大多數瀏覽器都支持吧),還是性能(沒有返回不需要的數據),還是使用的便利性(因為ASP.NET幫我們綁定了頁面上的當前的Web控件的數據,這就意味著我們可以在callback后的服務端,象postback時一樣來寫代碼,也方便我們移植原來的postback的代碼到callback方式的代碼)都是非常優秀的。我們也完全可以擴展現有的控件,或者寫我們自己的控件以支持這樣的callback效果,并且,混合使用callback控件和原來的postback方式的控件也是非常可靠和容易的。這對我們升級原來的基于postback為主的代碼,是非常有利的,如果用Atlas來做同樣的代碼升級和與postback方式的控件混合使用,我可以跟您說,會有很多問題。不信你自己可以試試。
編輯推薦:ASP.NET初學者入門最佳實踐 |