Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
WebForm最大的魅力大概就是它自己的一套事件處理機制了,要做一個好的Control,必須深入理解這套機制,只有這樣才可以讓我們的Control有一整套Professional的Event,而IPostBackDataHandler和IPostBackEventHandler是實現事件機制的核心接口,在我的上一篇文章(Web Control 開發系列(二) 深入解析Page的PostBack過程和IPostBackDataHandler)中已經介紹了IPostBackDataHandler的實現原理,本章主要介紹IPostBackEventHandler.最后進行總結,來看看Web Control整個事件機制的全貌。
?????? 要了解IPostBackEventHandler接口,首先就必須了解.net Framework的腳本注冊原理和一些重要的內置腳本。
?? 一、腳本注冊 ??
????? .net Framework可以在我們毫不知情的情況下,根據我們在服務端對Control屬性的設置,在ControlRender的時候,根據需要動態向客戶端注冊腳本和Hidden的<input>元素,Hidden的<input>用來在客戶端保存一些重要的信息,而腳本是用來完成一些邏輯行為的控制。我們先看看.net Framwork是如何實現腳本和Hidden <input>的.
?????
????? WebForm編程過程中,如果我們希望向客戶端輸出腳本或者一些Hidden的<input>元素,我們通常是通過 Page.ClientScript對象完成的,這個對象是一個ClientScriptManager類型的實例,我們一般(也有特殊情況)在Control.OnPreRender()方法里面調用Page.ClientScript.RegisterHiddenFiled或者Page.ClientScript.RegisterStartScript,還可以獲得一些內置的腳本,比如 Page.ClientScript.GetPostBackEventReference,這些方法的調用都會記錄一些標記數據,真正輸出到客戶端,是在Page的Render方法調用的時候,而完成輸出的是下面兩個方法:
??
?? Page.BeginFormRender 和Page.EndFormRender
???? 這兩個方法會在HtmlForm.RenderChildren里面調用,用來給<form>的開始和結束位置添加一些腳本和hidden field。具體完成的功能有:
??? * Render所有Register的Hidden Fields,同時也Render用來保存ViewState的Hidden Field
??? * Render用來保存當前 <form>的滾動位置的Hidden Field和Start Javascript
??? * Render控制當前焦點的Focus.js腳本引用語句
??? * Render用來執行回調的__doPostBack()函數,僅僅在相關標記打開的時候才會Render.
??? * Render用來執行PostBack的WebForms.js腳本引用語句。這個文件主要包含了WebForm常用的腳本,有PostBack的腳本和CallBack的腳本。
??? * Render已經注冊的腳本塊(Script Block)
??? * Renderl Client Startup Script(啟動即執行的腳本)
__doPostBack()腳本
Render出來的__doPostBack()如下:
//<![CDATA[
var?theForm?=?document.forms['form1'];
if?(!theForm)?{
????theForm?=?document.form1;
}
function?__doPostBack(eventTarget,?eventArgument)?{
????if?(!theForm.onsubmit?||?(theForm.onsubmit()?!=?false))?{
????????theForm.__EVENTTARGET.value?=?eventTarget;
????????theForm.__EVENTARGUMENT.value?=?eventArgument;
????????theForm.submit();
????}
}
//]]>
</script>
其實一旦用戶注冊了__doPostBack函數,兩個配套的Hidden字段也會同步注冊
<input?type="hidden"?name="__EVENTTARGET"?id="__EVENTTARGET"?value=""?/><input?type="hidden"?name="__EVENTARGUMENT"?id="__EVENTARGUMENT"?value=""?/>
上面的代碼就是先把Event target和Event Argument存入指定的Hidden Field中,然后調用<form>的submit方法來提交數據。這段腳本是Framework提供的最普通的引發PostBack的腳本,我們寫Control的時候可以通過Page.ClientScript.GetPostBackEventReference來獲得這個腳本(注意,這個方法有好幾個重載的版本,其中當選用了一些參數的時候,也可能獲得另一個腳本WebForm_DoPostBackWithOptions,下面將介紹)。
WebForm_DoPostBackWithOptions腳本
還有一個比較重要的腳本就是WebForm_DoPostBackWithOptions,它的作用比__doPostBack更強,比如對于支持CauseValidation屬性的Control,如Checkbox,TextBox,這些Control當CauseValidation為true的時候,它就會在onclick屬性里面Render出WebForm_DoPostBackWithOptions腳本。這樣可以在調用theForm.submit()方法之前執行當前Form的所有Validator的客戶端校驗。
????? 所以事實上,WebForm_DoPostBackWithOptions是包容__doPostBack函數的,凡是注冊了WebForm_DoPostBackWithOptions的地方,必須注冊__doPostBack,因為WebForm_DoPostBackWithOptions里面在執行完很多附加功能的代碼(如對所有的Validator進行校驗,控制Focus在沒有通過校驗的Control等)后,如果一切正常并且需要做ClientSubmit(Button當UseSumitBehavior為true和ImageButton不使用Client Script Submit,它們自己提交,因為它們自己輸出的就是可以觸發<form>提交的<input>元素),就調用__doPostBack。只有調用__doPostBack才會給__EVENTTARGET和__EVENTARGUMENT設置值,所以Button(UseSumitBehavior為true)和ImageButton引起的回傳的時候,我們觀察__EVENTTARGET和__EVENTARGUMENT,會發現都為“”。
二、使用腳本進行PostBack的Control分析
?? <input type="submit">
?? <input type="image">
Button
這是個特殊的Control,因為它們本身具備了自動調用<form>submit的能力,Button上面有一個屬性叫UseSubmitBehavior,這個屬性用來控制生成的<Input>的type是"submit"還是"button",如果為true,那么就是"submit".
1. 如果CauseValidation為false,UserSubmitBehavior為true,那么意味著僅僅進行提交,并不校驗,所以這個時候,生成的代碼是
沒有任何onclick的腳本調用。
2. 如果CauseValidation為true, UseSubmitBehavior為true,那么生成的代碼是
因為要執行校驗,所以必須調用WebForm_DoPostBackWithOptions方法。
3. 如果CauseValidation為false,UseSubmitBehavior為false,那么生成的代碼是
這個時候,因為type是“button”,它不具備submit數據的能力,所以只能用腳本幫助解決,同時不需要校驗,所以就使用__doPostBack函數,如果CauseValidataion為true,onclick里面的函數就為WebForm_DoPostBackWithOptions,因為__doPostBack不具備客戶端校驗的能力。
ImageButton
???? imageButton 比較特別的是同時實現了IPostBackDataHandler, IPostBackEventHandler, 在Asp.net2.0里面只有兩個Control同時實現了這兩個接口,一個是ImageButton,一個是HtmlInputImage。這兩個Control最后生成的都是<input type="image">.
???? 對于這個HTML元素,它在點擊的時候,會引起<form>submit數據,同時會把當前點擊的位置作為兩個表單域被<form>收集。比如:<input type="image" name="myImageButton">,那么點擊到(20,100)的時候,<form>表單域里面會多出兩個:["myImageButton.x"] = "20",["myImageButton.y"] = "100".
???? 通過上面的介紹,我們知道<input type="image">提交的數據和它的Name并不一致,根據我上一篇文章(Web Control 開發系列(二) 深入解析Page的PostBack過程和IPostBackDataHandler)的介紹,我們知道它必須使用注冊的方法才能保證page在處理Request的時候調用當前Control的IPostBackDataHandler接口。具體做法就是在PreRender的時候調用Page.RegisterRequiresPostBack(this).
??? 同上面介紹的Button一樣,當CauseValidation為true的時候,會給輸出加一個οnclick="WebForm_DoPostBackWithOptions"的屬性設置,這樣在提交前可以使用腳本進行校驗。否則就不會生成腳本。
CheckBox,TextBox,RadioButton,ListControl及其派生類 等
??? 這些Control輸出的Html元素都沒有自動submit的能力,所以這些Control普通情況下是不會引發回傳的,但是為了方便用戶,.net Framework在上面暴露了一個屬性叫AutoPostBack,一旦這個屬性為true,就表示這些Control具備了引發回傳的能力,具體怎么實現回傳呢,還是依賴于上面介紹的兩段腳本。
???? 當AutoPostBack為true,CauseValidation為true的時候就注冊WebForm_DoPostBackWithOptions. 當AutoPostBack為true,CasuseValidataion為false的時候就注冊 __doPostBack函數,同時在AutoPostBack屬性為true的時候,為了防止性能問題,一般注冊的腳本都用setTimeout(函數名,0)包起來,這樣可以認為是一個模擬的異步調用(事實JavaScript是單線程的,這樣的調用會自動進入調用隊列,等待執行,不會阻塞現在的調用)。
三、服務器端處理
????? 通過上面的分析知道,.net Framework一般是通過注冊腳本,在腳本里面調用theForm.submit()來進行提交的,這樣就形成了一次PostBack,而可以導致回傳的兩個重要的腳本也已經在上面介紹了。那么當腳本導致回傳了,服務器端是如何處理請求并引發Control的事件的呢?
????? 通過我第一篇文章(Web Control 開發系列(一) 頁面的生命周期)的介紹,我們知道在頁面Load階段結束后,如果Page.IsPostBack,我們會先進入 IPostBackDataHandler的處理階段,然后才進入IPostBackEventHandler處理階段。我們下面分析的就是 IPostBackEventHandler處理階段的邏輯。這個邏輯是通過 Page.RaisePostBackEvent(NameValueCollection postData)進入的。
????? 在這個函數的處理里面有好幾種情況:
????? 1. 其中最普通的一種處理是通過postData["__EVENTTARGET"]和postData[“__EVENTARGUMENT”]拿到相應的值,這些值都是在<form>提交前通過腳本設置上去的,然后通過Page.FindControl來找到合適的Control,這樣就可以取到Control.PostBackEventHandler,然后調用 IPostBackEventHandler.RaisePostBackEvent方法,就導致Control的服務端事件被觸發。
?????? 2. 還有一種情況,就是服務器端的Control是Button或者ImageButton,它們的提交是通過Html元素自己的能力,所以提交發生的時候,沒有任何腳本調用,自然postData["__EVENTTARGET"]和 postData[“__EVENTARGUMENT”]都為"",這個時候我們如何找到引發PostBack的Control并且調用它的 IPostBackEventHandler接口的方法呢?
?????????? 這就要利用另一種發事件的機制——注冊機制。這個機制主要通過 Page.RegisterRequiresRaiseEvent(IPostBackEventHandler control)函數實現的,這個函數在Asp.net2.0中有三個地方調用:
- ???? HtmlInputImage.LoadPostData,
- ???? ImageButton.LoadPostData,
- ???? Page.ProcessPostData
???? 這三個地方的調用都是在處理PostBackData階段,因此我們可以認為這個注冊機制最好在處理PostBackData階段使用是比較符合規范的。
???? 對于HtmlInputImage和ImageButton這兩個Control,它們都有PostBackData,而且通過注冊的方法實現了IPostBackDataHandler接口,所以在LoadPostData階段調用Page.RegisterRequiresRaiseEvent,這樣就顯式的告訴Page在PostBackEvent處理階段調用自己的IPostBackEventHandler接口,就實現了服務端Click事件的觸發。
???? 那么Page.ProcessPostData函數(在我的上一篇文章Web Control 開發系列(二) 深入解析Page的PostBack過程和IPostBackDataHandler有介紹),它會收集所有的表單提交數據,如果有和這個數據對應的Control(通過Page.FindControl查找),那么就設法調用其IPostDataHandler,如果IPostDataHandler為null,那么設法取其IPostEventHandler,如果不為null,那么就調用Page.RegisterRequiresRaiseEvent函數來注冊它。Button只實現了IPostBackEventHandler接口,沒有實現IPostBackDataHandler接口,所以就通過這種發式來觸發事件的。
???? 一旦在Page上進行了Page.RegisterRequiresRaiseEvent注冊,系統就不會關心postData["__EVENTTARGET"]和postData["__EVENTARGUMENT"]了,直接就調用注冊的IPostBackEventHandler.RaisePostBackEvent方法。
上面介紹的內容都是對Page.RaisePostBackEvent的分析:
????????{
????????????//?1.?假如已經在Page上顯式的注冊了引起PostBackEvent的Control,就直接處理
????????????if?(this._registeredControlThatRequireRaiseEvent?!=?null)
????????????{
????????????????this.RaisePostBackEvent(this._registeredControlThatRequireRaiseEvent,?null);
????????????}
????????????else
????????????{
????????????????//?這部分代碼,我自己按照Reflector反編譯的結果重新組織了,但是邏輯
????????????????//?沒有任何變化,只是方便閱讀理解
????????????????//?2.?假如沒有注冊,就查找__EVENTTARGET記錄的Control來處理
????????????????string?str?=?postData["__EVENTTARGET"];
????????????????bool?flag?=?!string.IsNullOrEmpty(str);
????????????????Control?control?=?null;
????????????????if?(flag)
????????????????{
????????????????????control?=?this.FindControl(str);
????????????????????if?((control?!=?null)?&&?(control.PostBackEventHandler?!=?null))
????????????????????{
????????????????????????string?eventArgument?=?postData["__EVENTARGUMENT"];
????????????????????????this.RaisePostBackEvent(control.PostBackEventHandler,?eventArgument);
????????????????????}
????????????????}
????????????????else?if?(this.AutoPostBackControl?==?null)
????????????????{
????????????????????//?這個AutoPostBackControl的標記設置為了不重復做Validate,后面我在講述
????????????????????//?Validation機制的時候會介紹
????????????????????this.Validate();
????????????????}
????????????}
????????}
四、Composite Control 的冒泡事件
??????????? 在Control上面有一個方法RaiseBubbleEvent,這個方法就是沿著Control Tree向上一次調用OnBubbleEvent函數,知道返回true,就推出,是一個典型的冒泡事件。Control對于OnBubbleEvent的實現是簡單的返回false,也就是說如果我們不做處理,那么事件會不停的向上冒泡知道最頂端的Page。
{
????for?(Control?control?=?this.Parent;?control?!=?null;?control?=?control.Parent)
????{
????????if?(control.OnBubbleEvent(source,?args))
????????{
????????????return;
????????}
????}
}
???? 我們知道了這個冒泡的機制,那么冒泡的源頭在哪里呢??這就是我們做Control的人要考慮的,如果我們希望我們的Control的Event支持冒泡,那么我們就應該在Control的Event發生的時候調用RaiseBubbleEvent這個函數,這樣當別人在一個復合控件里面使用我們的Control的時候,它就可以在外面接收到我們Control發的冒泡事件,目前調用了這個冒泡函數的Control有
???? 從上面,我們最值得注意的是有三個簡單Control實現了向上冒泡:Button, ImageButton, LinkButton,其它的都是一些復合Control在OnBubbleEvent里面進行二次冒泡。因此如果我們做一個復合的Control,我們可以在最外層的OnBubbleEvent函數里面監聽這個Control內部的所有的Button,ImageButton,LinkButton的事件。
五、總結
???? 所有WebForm事件的根源依賴于Form的submit()執行而引起PostBack(CallBack這里不考慮),而引起PostBack主要依賴于Html Input (type="image" or "submit")元素和腳本。
???? 然后在PostBack階段分析數據,如果數據變化可以Raise相關的Event,如果客戶端記錄了誰發了Event,也可以發Event。如果想讓Event冒泡,就call RaiseBubbleEvent
????
轉載于:https://www.cnblogs.com/mcsm/articles/1869632.html
總結
以上是生活随笔為你收集整理的Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HOWTO:用InstallShield
- 下一篇: .NET 部署-03Web Deploy