Web Control 開發系列(二) 深刻解析Page的PostBack過程和IPostBackDataHandler

IPostBackDataHandler和IPostBackEventHandler對於實現一個WebControl是很是重要的,若是你的 Contro僅僅是readonly的,也就是說不會讓客戶端進行輸入和修改,那麼這兩個接口就沒有用,一旦你要和客戶端交互,那麼這兩個接口是必須掌握的。IPostBackDataHandler可讓你的Control和客戶端的輸入數據進行交互,好比TextBox,CheckBox,而 IPostBackEventHandler可讓你的Control和客戶端的動做行爲進行交互,好比Button(click行爲)。若是你既但願接收客戶端的數據,也但願接收客戶端的行爲,那麼就要同時實現這兩個接口了。
       在個人上一篇文章《頁面的生命週期》裏面,我詳細介紹了頁面生存週期的各個階段,可是對於PostBack階段介紹的並非不少,在本文裏面我將詳細補充介紹頁面生存週期的PostBack 階段,由於IPostBackDataHandler,IPostBackEventHandler僅僅發生在頁面生存週期的PostBack階段。其實咱們能夠在PostBack作不少的事情,.net Framework認爲大多數用戶都但願處理Post回來的數據和事件,因此基於這個目的,他們爲咱們設計了IPostBackDataHandler和 IPostBackEventHandler這兩個接口,這僅僅是微軟的一個設計,因此沒有什麼特別神祕的。咱們只要很好的理解他們的設計,就能讓咱們的 Control無縫的和全部基於.net Framework實現的其它Control協同工做。下面我將一步一步分析這兩個接口的實現。

1、Page是什麼?
      當在Visual Stdio裏面new一個Page的時候,生成的代碼以下:
  javascript

複製代碼
<% @ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default"  %>

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >
< head  runat ="server" >
    
< title > Untitled Page </ title >
</ head >
< body >
    
< form  id ="form1"  runat ="server" >
    
< div >
    
    
</ div >
    
</ form >
</ body >
</ html >
複製代碼


    從代碼能夠看出來,Page輸出到客戶端,它的內容區域就是在一個HTML的<form>元素。因此咱們在頁面上放的 TextBox,CheckBox,Button,還有不少的第三方的WebControl,它們都是在form元素裏面的,最後輸出到客戶端,就會變爲嵌入在<form>裏面的Html節點,若是節點爲input,這些都會變爲表單的字段,例如<Input type="button" ...>,<Input type="text" ...>,<Input type="hidden" ...>.這裏有一點值得注意的是,.net Framework經常會把ViewState,EvntTarget等一些須要在客戶端保存的數據都做爲一個type爲hidden的input元素放在form裏面。爲何這樣作呢?由於<form>元素是一個很特殊的HTML元素。下面說說form:
    form做爲Html的一個元素,它就是爲了客戶端提交數據而產生的,它有兩個很重要的屬性action和method,action屬性指明瞭處理提交的數據的應用程序的URL,而method有兩個值:POST,GET,由於瀏覽器提交數據老是使用HTTP(也有使用HTTPS)協議,而 POST,GET則是HTTP協議傳輸數據的方式,因此method指明瞭傳輸數據的方式,對於一個新的Page所生成的html代碼,form老是method=" POST"的方式提交數據(緣由也有不少,好比數據安全性比Get高):以下


html

複製代碼
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >

< html  xmlns ="http://www.w3.org/1999/xhtml"   >
< head >< title >
    Untitled Page
</ title ></ head >
< body >
    
< form  name ="form1"  method ="post"  action ="Default.aspx"  id ="form1" >
        
< div >
           
< input  type ="hidden"  name ="__VIEWSTATE"  id ="__VIEWSTATE"              value ="/wEPDwUJNzgzNDMwNTMzZGQP0LJECgTtp1lOdVaW3IZPFDdsYw=="   />
        
</ div >

        
< div >    
        
</ div >
    
</ form >
</ body >
</ html >
複製代碼


form上面全部的HTML規範定義的表單域(form field)元素,一旦具備name屬性,在form進行submit的時候,form field(例如<input type ="text"..>)裏面的數據都會自動被收集,而後按照必定的編碼方式(如何編碼?也有好多種啊,能夠在form上設置,沒空說了)進行編碼,而後發給action定義的URL進行處理。
       前面介紹了那麼多關於form的知識,就是爲了咱們更好的理解Page的postback處理過程。因此說Page的核心就是一個Html 的<form>元素,它發生提交的時候老是以Post的方式把收集到的form field的值返回。具體關於<form>元素和Http協議,各位能夠Google出不少的東西,這裏就不詳細說了。

2、Page的Post處理過程
       當頁面處理一個Http Post請求的時候,它會把form傳回來數據進行解碼,存入一個NameValueCollection的對象裏面,咱們能夠經過 Request.Form來觀察,這個存儲結構比較相似於Hashtable,傳入form field的name獲得它的值。有了收集回來的post數據,就能夠進行處理了。主要有兩個Post的處理過程(參見《頁面的生命週期》):一個在Init 階段結束後,另外一個在Load階段後。ProcessRequest函數的代碼片斷以下:
      
       java

複製代碼
//  1. PreInit
         this .PerformPreInit();
        
//  2. Init
         this .InitRecursive( null );
        
this .OnInitComplete(EventArgs.Empty);

        
//  對於Postback,插入下面處理
         if  ( this .IsPostBack)
        
{
            
// 加載ViewState和ControlState,進行場景恢復
            this.LoadAllState();
            
// 第一次處理PostData
            this.ProcessPostData(this._requestValueCollection, true);
        }


        
//  3. PreLoad
         this .OnPreLoad(EventArgs.Empty);
        
//  4. Load
         this .LoadRecursive();

        
//  對於Postback,插入下面處理
         if  ( this .IsPostBack)
        
{
            
// 第二次處理PostData
            this.ProcessPostData(this._leftoverPostData, false);
            
// 若是PostData表面某個Control數據發生變化,那麼RaisePostDataChanged事件
            this.RaiseChangedEvents();
            
// RaisePostBackEvent
            this.RaisePostBackEvent(this._requestValueCollection);
        }


        
this .OnLoadComplete(EventArgs.Empty);
複製代碼

   
3、IPostBackDataHandler怎麼工做的?
            這個接口有兩個方法:LoadPostData()和RaisePostDataChangedEvent(), 每每LoadPostData()會先被調用,若是返回true,那麼表明數據發生變化,RaisePostDataChangedEvent()就會被調用,這樣一個完整的Web Control的event就發出來了,例如TextBox的TextChanged就是這樣發的。

            先來分析Page頁面是如何在請求處理函數裏面來調用實現了IPostBackDataHandler接口的Control的,這個實現主要在Page的ProcessPostData函數,具體分析以下:
  web

複製代碼

private   void  ProcessPostData(NameValueCollection postData,  bool  fBeforeLoad)
        
{
            
// 1. 用一個全局變量_changedPostDataConsumers來保存PostData發生
            
//    變化的Control全部這些Control都要調用RaistPostDataChangedEvent()
            if (this._changedPostDataConsumers == null)
            
{
                
this._changedPostDataConsumers = new ArrayList();
            }



            
// 2. postData保存的是Form上的表單字段的value,能夠經過表單字段的name的索引
            if (postData != null)
            
{
                
foreach (string str in postData)
                
{
                    
// 對於系統定義的表單字段,直接跳過,例如:__VIEWSTATE
                    if ((str == null|| IsSystemPostField(str))
                    
{
                        
continue;
                    }


                    
// 得到這個表單字段對應的Control
                    Control control = this.FindControl(str);
                    
if (control == null)
                    
{
                        
// 3. 這個標記爲true,表明是在Load階段前的調用,爲false表明是
                        
//    在Load階段後的調用其實這只是防止有些Control在Load階段前
                        
//    尚未建立,因此在Load階段後進行再一次調用而第二次調用
                        
//    處理的數據都是本次調用所沒法處理的數據,本次成功處理的
                        
//     Control,第二次調用都不會繼續處理了。
                        if (fBeforeLoad) 
                        
{
                            
if (this._leftoverPostData == null)
                            
{
                                
this._leftoverPostData = new NameValueCollection();
                            }

                            
this._leftoverPostData.Add(str, null);
                        }

                        
continue;
                    }

                    
                    
// 程序走到這裏,control不爲null,由於若是爲null,上面就continue了

                    
// 4. 取control.PostBackDataHandler或者PostBackEventHandler能夠理
                    
//    解爲把control as爲IPostBackDataHandler 或者 IPostDataEventHandler
                    
//    (注:真實邏輯還取adaper,但僅僅是爲了Adapter機制,咱們這裏不用考慮)
                    IPostBackDataHandler postBackDataHandler = control.PostBackDataHandler;
                    
if (postBackDataHandler == null)
                    
{
                        
// 5. 若是沒法取到PostBackDataHandler,可是能夠取得PostBackEventHandler,
                        
//    那麼註冊它。這個操做致使在後面的RaisePostBackEvent()函數會調用
                        
//    這個control的IPostBackEventHandler.RaisePostBackEvent()                        
                        if (control.PostBackEventHandler != null)
                        
{
                            
this.RegisterRequiresRaiseEvent(control.PostBackEventHandler);
                        }

                    }

                    
else
                    
{
                        
// 6. postBackDataHandler不爲null的時候,就調用它的LoadPostData()函數,
                        
//    若是返回結果爲true,那麼把該control加入_changedPostDataConsumers
                        
//   (見註釋1),這樣在後面的RaiseChangedEvent裏面就會依次從集合
                        
//    _changedPostDataConsumers裏面取出control,而後調用
                        
//    control.RaisePostDataChangedEvent()
                        if ((postBackDataHandler != null&& 
                            postBackDataHandler.LoadPostData(str, 
this._requestValueCollection))
                        
{
                            
this._changedPostDataConsumers.Add(control);
                        }


                        
// 7. 若是這裏處理了,就從_controlsRequiringPostBack集合從刪除當前control
                        
//    的id,避免二次處理,實際上本函數就是處理兩個集合,一個是傳入的postData
                        
//    集合,另外一個就是下面這個_controlsRequiringPostBack集合。這個集合裏面的
                        
//    control都是經過page的RegisterRequiresPostBack(Control control)方法註冊
                        
//    進去的,這個集合會做爲ControlState的一個附加字段存儲,這樣
                        
//    LoadAllState的時候能夠很好恢復。(見註釋8)
                        if (this._controlsRequiringPostBack != null)
                        
{
                            
this._controlsRequiringPostBack.Remove(str);
                        }

                    }

                }

            }


            
// 8. 下面開始處理_controlsRequiringPostBack集合裏面的control
            ArrayList list = null;
            
if (this._controlsRequiringPostBack != null)
            
{
                
foreach (string str2 in this._controlsRequiringPostBack)
                
{
                    Control control2 
= this.FindControl(str2);
                    
if (control2 != null)
                    
{
                        
// (見註釋4)
                        IPostBackDataHandler handler2 = control2.PostBackDataHandler;
                        
if (handler2 == null)
                        
{
                            
throw new HttpException(SR.GetString("Postback_ctrl_not_found"new object[] { str2 }));
                        }


                        
// (見註釋6),對於PostBackData變化的Control加入
                        
//  _changedPostDataConsumers集合
                        if (handler2.LoadPostData(str2, this._requestValueCollection))
                        
{
                            
this._changedPostDataConsumers.Add(control2);
                        }

                        
continue;
                    }

                    
else
                    
{
                        
// control2爲null,因此沒法處理,加入集合,等待Load階段後的調用處理(見註釋3)
                        if (fBeforeLoad)
                        
{
                            
if (list == null)
                            
{
                                list 
= new ArrayList();
                            }

                            list.Add(str2);
                        }

                    }

                }

                
this._controlsRequiringPostBack = list;
            }

        }
複製代碼


   對於Page註冊的_controlsRequiringPostBack是如何保持到ControlState的,能夠參考下面的代碼片斷:瀏覽器

複製代碼
        private   void  SaveAllState()
        
{
            
if (this._needToPersistViewState)
            
{
                
// 1. 把ControlState存儲到dictionary裏面
                .

                
// 2. 把註冊的須要PostBack處理的Control的id集合加入到用來保存ControlState
                
//    的dictionary裏面
                if ((this._registeredControlsThatRequirePostBack != null&& (this._registeredControlsThatRequirePostBack.Count > 0x0))
                
{
                    dictionary.Add(
"__ControlsRequirePostBackKey__"this._registeredControlsThatRequirePostBack);
                }

                
                
// 3. 收集ViewState
                .
                
// 4. 把全部的State序列化到Page頁面的hidden字段
                .
            }

        }
複製代碼

  經過上面的代碼,我這裏作一個小結:若是要寫一個實現IPostBackDataHandler的Control,除了實現接口自己外,還必須作到下面兩種方法的一種,才能夠順利完成任務:
  第一種:該Control Render出來的元素自己就是一個表單域(form field),並且表單域的name和control的id保持一致,這樣,Page在拿到表單域的數據後,能夠經過name調用FindControl來找到相應的Control,而後若是Control.PostBackDataHandler 不爲null,就進入調用入口。
 第二種:該Control存放數據的表單域的name和該control的id並無對應的關係,因此就須要在PreRender的時候(也能夠在其它階段,如Load等,不過大部分是在PreRender裏面作),調用Page.RegisterRequiresPostBack(Control control) 方法,傳入this做爲參數,這樣也能夠保證Page會遍歷全部註冊過的Control,而後進入IPostBackDataHandler的調用入口。
   綜上所述,PostBackData,就是在客戶端的一個數據緩存,當用戶在客戶端修改的時候,都是修改的數據緩存,不會和服務器通訊,只有當form submit的時候,一次PostBack發生,而後緩存的數據會被form收集並傳輸到服務器端,服務器端就調用IPostBackDataHandler來處理傳回的數據。緩存

相關文章
相關標籤/搜索