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()以下:javascript
其實一旦用戶註冊了__doPostBack函數,兩個配套的Hidden字段也會同步註冊html
上面的代碼就是先把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,會發現都爲「」。
2、使用腳本進行PostBack的Control分析java
Button
這是個特殊的Control,由於它們自己具有了自動調用<form>submit的能力,Button上面有一個屬性叫UseSubmitBehavior,這個屬性用來控制生成的<Input>的type是"submit"仍是"button",若是爲true,那麼就是"submit".
1. 若是CauseValidation爲false,UserSubmitBehavior爲true,那麼意味着僅僅進行提交,並不校驗,因此這個時候,生成的代碼是web
沒有任何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的時候,會給輸出加一個onclick="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是單線程的,這樣的調用會自動進入調用隊列,等待執行,不會阻塞如今的調用)。
3、服務器端處理
經過上面的分析知道,.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中有三個地方調用:異步
這三個地方的調用都是在處理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的分析:函數
4、Composite Control 的冒泡事件
在Control上面有一個方法RaiseBubbleEvent,這個方法就是沿着Control Tree向上一次調用OnBubbleEvent函數,知道返回true,就推出,是一個典型的冒泡事件。Control對於OnBubbleEvent的實現是簡單的返回false,也就是說若是咱們不作處理,那麼事件會不停的向上冒泡知道最頂端的Page。post
咱們知道了這個冒泡的機制,那麼冒泡的源頭在哪裏呢??這就是咱們作Control的人要考慮的,若是咱們但願咱們的Control的Event支持冒泡,那麼咱們就應該在Control的Event發生的時候調用RaiseBubbleEvent這個函數,這樣當別人在一個複合控件裏面使用咱們的Control的時候,它就能夠在外面接收到咱們Control發的冒泡事件,目前調用了這個冒泡函數的Control有
從上面,咱們最值得注意的是有三個簡單Control實現了向上冒泡:Button, ImageButton, LinkButton,其它的都是一些複合Control在OnBubbleEvent裏面進行二次冒泡。所以若是咱們作一個複合的Control,咱們能夠在最外層的OnBubbleEvent函數裏面監聽這個Control內部的全部的Button,ImageButton,LinkButton的事件。
5、總結
全部WebForm事件的根源依賴於Form的submit()執行而引發PostBack(CallBack這裏不考慮),而引發PostBack主要依賴於Html Input (type="image" or "submit")元素和腳本。
而後在PostBack階段分析數據,若是數據變化能夠Raise相關的Event,若是客戶端記錄了誰發了Event,也能夠發Event。若是想讓Event冒泡,就call RaiseBubbleEvent性能