原創(chuàng)|其它|編輯:郝浩|2008-07-16 11:06:50.000|閱讀 1135 次
概述:深入解析Page的PostBack過(guò)程和IPostBackDataHandler
# 界面/圖表報(bào)表/文檔/IDE等千款熱門(mén)軟控件火熱銷(xiāo)售中 >>
IPostBackDataHandler和IPostBackEventHandler對(duì)于實(shí)現(xiàn)一個(gè)WebControl是非常重要的,如果你的 Contro僅僅是readonly的,也就是說(shuō)不會(huì)讓客戶端進(jìn)行輸入和修改,那么這兩個(gè)接口就沒(méi)有用,一旦你要和客戶端交互,那么這兩個(gè)接口是必須掌握的。IPostBackDataHandler可以讓你的Control和客戶端的輸入數(shù)據(jù)進(jìn)行交互,比如TextBox,CheckBox,而 IPostBackEventHandler可以讓你的Control和客戶端的動(dòng)作行為進(jìn)行交互,比如Button(click行為)。如果你既希望接收客戶端的數(shù)據(jù),也希望接收客戶端的行為,那么就要同時(shí)實(shí)現(xiàn)這兩個(gè)接口了。
在我的上一篇文章《頁(yè)面的生命周期》里面,我詳細(xì)介紹了頁(yè)面生存周期的各個(gè)階段,但是對(duì)于PostBack階段介紹的并不是很多,在本文里面我將詳細(xì)補(bǔ)充介紹頁(yè)面生存周期的PostBack 階段,因?yàn)镮PostBackDataHandler,IPostBackEventHandler僅僅發(fā)生在頁(yè)面生存周期的PostBack階段。其實(shí)我們可以在PostBack做很多的事情,.net Framework認(rèn)為大多數(shù)用戶都希望處理Post回來(lái)的數(shù)據(jù)和事件,所以基于這個(gè)目的,他們?yōu)槲覀冊(cè)O(shè)計(jì)了IPostBackDataHandler和 IPostBackEventHandler這兩個(gè)接口,這僅僅是微軟的一個(gè)設(shè)計(jì),所以沒(méi)有什么特別神秘的。我們只要很好的理解他們的設(shè)計(jì),就能讓我們的 Control無(wú)縫的和所有基于.net Framework實(shí)現(xiàn)的其它Control協(xié)同工作。下面我將一步一步分析這兩個(gè)接口的實(shí)現(xiàn)。
一、Page是什么?
當(dāng)在Visual Stdio里面new一個(gè)Page的時(shí)候,生成的代碼如下:
<%@ 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>
從代碼可以看出來(lái),Page輸出到客戶端,它的內(nèi)容區(qū)域就是在一個(gè)HTML的<form>元素。所以我們?cè)陧?yè)面上放的 TextBox,CheckBox,Button,還有很多的第三方的WebControl,它們都是在form元素里面的,最后輸出到客戶端,就會(huì)變?yōu)榍度朐?lt;form>里面的Html節(jié)點(diǎn),如果節(jié)點(diǎn)為input,這些都會(huì)變?yōu)楸韱蔚淖侄危?lt;Input type="button" ...>,<Input type="text" ...>,<Input type="hidden" ...>.這里有一點(diǎn)值得注意的是,.net Framework常常會(huì)把ViewState,EvntTarget等一些需要在客戶端保存的數(shù)據(jù)都作為一個(gè)type為hidden的input元素放在form里面。為什么這樣做呢?因?yàn)?lt;form>元素是一個(gè)很特殊的HTML元素。下面說(shuō)說(shuō)form:
form作為Html的一個(gè)元素,它就是為了客戶端提交數(shù)據(jù)而產(chǎn)生的,它有兩個(gè)很重要的屬性action和method,action屬性指明了處理提交的數(shù)據(jù)的應(yīng)用程序的URL,而method有兩個(gè)值:POST,GET,因?yàn)闉g覽器提交數(shù)據(jù)總是使用HTTP(也有使用HTTPS)協(xié)議,而 POST,GET則是HTTP協(xié)議傳輸數(shù)據(jù)的方式,所以method指明了傳輸數(shù)據(jù)的方式,對(duì)于一個(gè)新的Page所生成的html代碼,form總是method=" POST"的方式提交數(shù)據(jù)(原因也有很多,比如數(shù)據(jù)安全性比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規(guī)范定義的表單域(form field)元素,一旦具有name屬性,在form進(jìn)行submit的時(shí)候,form field(例如<input type ="text"..>)里面的數(shù)據(jù)都會(huì)自動(dòng)被收集,然后按照一定的編碼方式(如何編碼?也有好多種啊,可以在form上設(shè)置,沒(méi)空說(shuō)了)進(jìn)行編碼,然后發(fā)給action定義的URL進(jìn)行處理。
前面介紹了那么多關(guān)于form的知識(shí),就是為了我們更好的理解Page的postback處理過(guò)程。所以說(shuō)Page的核心就是一個(gè)Html 的<form>元素,它發(fā)生提交的時(shí)候總是以Post的方式把收集到的form field的值返回。具體關(guān)于<form>元素和Http協(xié)議,各位可以Google出很多的東西,這里就不詳細(xì)說(shuō)了。
二、Page的Post處理過(guò)程
當(dāng)頁(yè)面處理一個(gè)Http Post請(qǐng)求的時(shí)候,它會(huì)把form傳回來(lái)數(shù)據(jù)進(jìn)行解碼,存入一個(gè)NameValueCollection的對(duì)象里面,我們可以通過(guò) Request.Form來(lái)觀察,這個(gè)存儲(chǔ)結(jié)構(gòu)比較類(lèi)似于Hashtable,傳入form field的name得到它的值。有了收集回來(lái)的post數(shù)據(jù),就可以進(jìn)行處理了。主要有兩個(gè)Post的處理過(guò)程(參見(jiàn)《頁(yè)面的生命周期》):一個(gè)在Init 階段結(jié)束后,另一個(gè)在Load階段后。ProcessRequest函數(shù)的代碼片段如下:
// 1. PreInit
this.PerformPreInit();
// 2. Init
this.InitRecursive(null);
this.OnInitComplete(EventArgs.Empty);
// 對(duì)于Postback,插入下面處理
if (this.IsPostBack)
{
// 加載ViewState和ControlState,進(jìn)行場(chǎng)景恢復(fù)
this.LoadAllState();
// 第一次處理PostData
this.ProcessPostData(this._requestValueCollection, true);
}
// 3. PreLoad
this.OnPreLoad(EventArgs.Empty);
// 4. Load
this.LoadRecursive();
// 對(duì)于Postback,插入下面處理
if (this.IsPostBack)
{
// 第二次處理PostData
this.ProcessPostData(this._leftoverPostData, false);
// 如果PostData表面某個(gè)Control數(shù)據(jù)發(fā)生變化,那么RaisePostDataChanged事件
this.RaiseChangedEvents();
// RaisePostBackEvent
this.RaisePostBackEvent(this._requestValueCollection);
}
this.OnLoadComplete(EventArgs.Empty);
三、IPostBackDataHandler怎么工作的?
這個(gè)接口有兩個(gè)方法:LoadPostData()和RaisePostDataChangedEvent(), 往往LoadPostData()會(huì)先被調(diào)用,如果返回true,那么代表數(shù)據(jù)發(fā)生變化,RaisePostDataChangedEvent()就會(huì)被調(diào)用,這樣一個(gè)完整的Web Control的event就發(fā)出來(lái)了,例如TextBox的TextChanged就是這樣發(fā)的。
先來(lái)分析Page頁(yè)面是如何在請(qǐng)求處理函數(shù)里面來(lái)調(diào)用實(shí)現(xiàn)了IPostBackDataHandler接口的Control的,這個(gè)實(shí)現(xiàn)主要在Page的ProcessPostData函數(shù),具體分析如下:
private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad)
{
// 1. 用一個(gè)全局變量_changedPostDataConsumers來(lái)保存PostData發(fā)生
// 變化的Control所有這些Control都要調(diào)用RaistPostDataChangedEvent()
if (this._changedPostDataConsumers == null)
{
this._changedPostDataConsumers = new ArrayList();
}
// 2. postData保存的是Form上的表單字段的value,可以通過(guò)表單字段的name的索引
if (postData != null)
{
foreach (string str in postData)
{
// 對(duì)于系統(tǒng)定義的表單字段,直接跳過(guò),例如:__VIEWSTATE
if ((str == null) || IsSystemPostField(str))
{
continue;
}
// 獲得這個(gè)表單字段對(duì)應(yīng)的Control
Control control = this.FindControl(str);
if (control == null)
{
// 3. 這個(gè)標(biāo)記為true,代表是在Load階段前的調(diào)用,為false代表是
// 在Load階段后的調(diào)用其實(shí)這只是防止有些Control在Load階段前
// 還沒(méi)有創(chuàng)建,所以在Load階段后進(jìn)行再一次調(diào)用而第二次調(diào)用
// 處理的數(shù)據(jù)都是本次調(diào)用所無(wú)法處理的數(shù)據(jù),本次成功處理的
// Control,第二次調(diào)用都不會(huì)繼續(xù)處理了。
if (fBeforeLoad)
{
if (this._leftoverPostData == null)
{
this._leftoverPostData = new NameValueCollection();
}
this._leftoverPostData.Add(str, null);
}
continue;
}
// 程序走到這里,control不為null,因?yàn)槿绻麨閚ull,上面就continue了
// 4. 取control.PostBackDataHandler或者PostBackEventHandler可以理
// 解為把control as為IPostBackDataHandler 或者 IPostDataEventHandler
// (注:真實(shí)邏輯還取adaper,但僅僅是為了Adapter機(jī)制,我們這里不用考慮)
IPostBackDataHandler postBackDataHandler = control.PostBackDataHandler;
if (postBackDataHandler == null)
{
// 5. 如果無(wú)法取到PostBackDataHandler,但是可以取得PostBackEventHandler,
// 那么注冊(cè)它。這個(gè)操作導(dǎo)致在后面的RaisePostBackEvent()函數(shù)會(huì)調(diào)用
// 這個(gè)control的IPostBackEventHandler.RaisePostBackEvent()
if (control.PostBackEventHandler != null)
{
this.RegisterRequiresRaiseEvent(control.PostBackEventHandler);
}
}
else
{
// 6. postBackDataHandler不為null的時(shí)候,就調(diào)用它的LoadPostData()函數(shù),
// 如果返回結(jié)果為true,那么把該control加入_changedPostDataConsumers
// (見(jiàn)注釋1),這樣在后面的RaiseChangedEvent里面就會(huì)依次從集合
// _changedPostDataConsumers里面取出control,然后調(diào)用
// control.RaisePostDataChangedEvent()
if ((postBackDataHandler != null) &&
postBackDataHandler.LoadPostData(str, this._requestValueCollection))
{
this._changedPostDataConsumers.Add(control);
}
// 7. 如果這里處理了,就從_controlsRequiringPostBack集合從刪除當(dāng)前control
// 的id,避免二次處理,實(shí)際上本函數(shù)就是處理兩個(gè)集合,一個(gè)是傳入的postData
// 集合,另一個(gè)就是下面這個(gè)_controlsRequiringPostBack集合。這個(gè)集合里面的
// control都是通過(guò)page的RegisterRequiresPostBack(Control control)方法注冊(cè)
// 進(jìn)去的,這個(gè)集合會(huì)作為ControlState的一個(gè)附加字段存儲(chǔ),這樣
// LoadAllState的時(shí)候可以很好恢復(fù)。(見(jiàn)注釋8)
if (this._controlsRequiringPostBack != null)
{
this._controlsRequiringPostBack.Remove(str);
}
}
}
}
// 8. 下面開(kāi)始處理_controlsRequiringPostBack集合里面的control
ArrayList list = null;
if (this._controlsRequiringPostBack != null)
{
foreach (string str2 in this._controlsRequiringPostBack)
{
Control control2 = this.FindControl(str2);
if (control2 != null)
{
// (見(jiàn)注釋4)
IPostBackDataHandler handler2 = control2.PostBackDataHandler;
if (handler2 == null)
{
throw new HttpException(SR.GetString("Postback_ctrl_not_found", new object[] { str2 }));
}
// (見(jiàn)注釋6),對(duì)于PostBackData變化的Control加入
// _changedPostDataConsumers集合
if (handler2.LoadPostData(str2, this._requestValueCollection))
{
this._changedPostDataConsumers.Add(control2);
}
continue;
}
else
{
// control2為null,所以無(wú)法處理,加入集合,等待Load階段后的調(diào)用處理(見(jiàn)注釋3)
if (fBeforeLoad)
{
if (list == null)
{
list = new ArrayList();
}
list.Add(str2);
}
}
}
this._controlsRequiringPostBack = list;
}
}
對(duì)于Page注冊(cè)的_controlsRequiringPostBack是如何保持到ControlState的,可以參考下面的代碼片段:
private void SaveAllState()
{
if (this._needToPersistViewState)
{
// 1. 把ControlState存儲(chǔ)到dictionary里面
.
// 2. 把注冊(cè)的需要PostBack處理的Control的id集合加入到用來(lái)保存ControlState
// 的dictionary里面
if ((this._registeredControlsThatRequirePostBack != null) && (this._registeredControlsThatRequirePostBack.Count > 0x0))
{
dictionary.Add("__ControlsRequirePostBackKey__", this._registeredControlsThatRequirePostBack);
}
// 3. 收集ViewState
.
// 4. 把所有的State序列化到Page頁(yè)面的hidden字段
.
}
}
通過(guò)上面的代碼,我這里做一個(gè)小結(jié):如果要寫(xiě)一個(gè)實(shí)現(xiàn)IPostBackDataHandler的Control,除了實(shí)現(xiàn)接口本身外,還必須做到下面兩種方法的一種,才可以順利完成任務(wù):
第一種:該Control Render出來(lái)的元素本身就是一個(gè)表單域(form field),而且表單域的name和control的id保持一致,這樣,Page在拿到表單域的數(shù)據(jù)后,可以通過(guò)name調(diào)用FindControl來(lái)找到相應(yīng)的Control,然后如果Control.PostBackDataHandler 不為null,就進(jìn)入調(diào)用入口。
第二種:該Control存放數(shù)據(jù)的表單域的name和該control的id并沒(méi)有對(duì)應(yīng)的關(guān)系,所以就需要在PreRender的時(shí)候(也可以在其它階段,如Load等,不過(guò)大部分是在PreRender里面做),調(diào)用Page.RegisterRequiresPostBack(Control control) 方法,傳入this作為參數(shù),這樣也可以保證Page會(huì)遍歷所有注冊(cè)過(guò)的Control,然后進(jìn)入IPostBackDataHandler的調(diào)用入口。
綜上所述,PostBackData,就是在客戶端的一個(gè)數(shù)據(jù)緩存,當(dāng)用戶在客戶端修改的時(shí)候,都是修改的數(shù)據(jù)緩存,不會(huì)和服務(wù)器通信,只有當(dāng)form submit的時(shí)候,一次PostBack發(fā)生,然后緩存的數(shù)據(jù)會(huì)被form收集并傳輸?shù)椒?wù)器端,服務(wù)器端就調(diào)用IPostBackDataHandler來(lái)處理傳回的數(shù)據(jù)。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@ke049m.cn
文章轉(zhuǎn)載自:博客園