原創|其它|編輯:郝浩|2008-07-16 11:06:50.000|閱讀 1129 次
概述:深入解析Page的PostBack過程和IPostBackDataHandler
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
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協同工作。下面我將一步一步分析這兩個接口的實現。
一、Page是什么?
當在Visual Stdio里面new一個Page的時候,生成的代碼如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "//www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="//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高):如下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "//www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="//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出很多的東西,這里就不詳細說了。
二、Page的Post處理過程
當頁面處理一個Http Post請求的時候,它會把form傳回來數據進行解碼,存入一個NameValueCollection的對象里面,我們可以通過 Request.Form來觀察,這個存儲結構比較類似于Hashtable,傳入form field的name得到它的值。有了收集回來的post數據,就可以進行處理了。主要有兩個Post的處理過程(參見《頁面的生命周期》):一個在Init 階段結束后,另一個在Load階段后。ProcessRequest函數的代碼片段如下:
// 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);
三、IPostBackDataHandler怎么工作的?
這個接口有兩個方法:LoadPostData()和RaisePostDataChangedEvent(), 往往LoadPostData()會先被調用,如果返回true,那么代表數據發生變化,RaisePostDataChangedEvent()就會被調用,這樣一個完整的Web Control的event就發出來了,例如TextBox的TextChanged就是這樣發的。
先來分析Page頁面是如何在請求處理函數里面來調用實現了IPostBackDataHandler接口的Control的,這個實現主要在Page的ProcessPostData函數,具體分析如下:
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來處理傳回的數據。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:博客園