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"
%>
![](http://static.javashuo.com/static/loading.gif)
<!
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
![](http://static.javashuo.com/static/loading.gif)
<
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"
>
![](http://static.javashuo.com/static/loading.gif)
<
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
>
![](http://static.javashuo.com/static/loading.gif)
<
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);
![](http://static.javashuo.com/static/loading.gif)
//
對於Postback,插入下面處理
if
(
this
.IsPostBack)
{
// 加載ViewState和ControlState,進行場景恢復
this.LoadAllState();
// 第一次處理PostData
this.ProcessPostData(this._requestValueCollection, true);
}
![](http://static.javashuo.com/static/loading.gif)
//
3. PreLoad
this
.OnPreLoad(EventArgs.Empty);
//
4. Load
this
.LoadRecursive();
![](http://static.javashuo.com/static/loading.gif)
//
對於Postback,插入下面處理
if
(
this
.IsPostBack)
{
// 第二次處理PostData
this.ProcessPostData(this._leftoverPostData, false);
// 若是PostData表面某個Control數據發生變化,那麼RaisePostDataChanged事件
this.RaiseChangedEvents();
// RaisePostBackEvent
this.RaisePostBackEvent(this._requestValueCollection);
}
![](http://static.javashuo.com/static/loading.gif)
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();
}
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
// 2. postData保存的是Form上的表單字段的value,能夠經過表單字段的name的索引
if (postData != null)
{
foreach (string str in postData)
{
// 對於系統定義的表單字段,直接跳過,例如:__VIEWSTATE
if ((str == null) || IsSystemPostField(str))
{
continue;
}
![](http://static.javashuo.com/static/loading.gif)
// 得到這個表單字段對應的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了
![](http://static.javashuo.com/static/loading.gif)
// 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);
}
![](http://static.javashuo.com/static/loading.gif)
// 7. 若是這裏處理了,就從_controlsRequiringPostBack集合從刪除當前control
// 的id,避免二次處理,實際上本函數就是處理兩個集合,一個是傳入的postData
// 集合,另外一個就是下面這個_controlsRequiringPostBack集合。這個集合裏面的
// control都是經過page的RegisterRequiresPostBack(Control control)方法註冊
// 進去的,這個集合會做爲ControlState的一個附加字段存儲,這樣
// LoadAllState的時候能夠很好恢復。(見註釋8)
if (this._controlsRequiringPostBack != null)
{
this._controlsRequiringPostBack.Remove(str);
}
}
}
}
![](http://static.javashuo.com/static/loading.gif)
// 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 }));
}
![](http://static.javashuo.com/static/loading.gif)
// (見註釋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裏面
.
![](http://static.javashuo.com/static/loading.gif)
// 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來處理傳回的數據。緩存