ASP.NET框架 資料回發與事件回發

iDotNetSpace發表於2009-11-26
經常在網上的論壇看到有人問Page類的方法RegisterRequiresPostBack有什麼用,它是做什麼的呢?

簡短的官方解釋

MSDN對它的解釋是將控制元件註冊為要求在頁面回發至伺服器時進行回發處理的控制元件,說真的我知道這段話想描述些什麼,但是你知道怎麼用它,在哪裡用嗎?

尋找回發真相

首先ASP.NET框架規定,凡是要進行資料回發的控制元件都要實現IPostBackDataHandler 介面,它有兩個方法LoadPostData、RaisePostDataChangedEvent,該介面方法將可以進行回發資料處理,並可以引發任何回發資料已更改的事件.還有就是要在頁生命週期的 Page_PreRender 事件中或該事件之前向頁面註冊控制元件.

為什麼要這裡註冊呢?好,那我們看看篇頭提到的Page. RegisterRequiresPostBack這個方法的原始碼就會明白了.



public void RegisterRequiresPostBack(Control control)

{

// Fail if the control is not an IPostBackDataHandler (VSWhidbey 184483) 指定控制元件必須實現IPostBackDataHandler 介面

if (!(control is IPostBackDataHandler)) {

IPostBackDataHandler dataHandler = control._adapter as IPostBackDataHandler;

if (dataHandler == null)

throw new HttpException(SR.GetString(SR.Ctrl_not_data_handler));

}

if (_registeredControlsThatRequirePostBack == null)

_registeredControlsThatRequirePostBack = new ArrayList();

//這裡將需要註冊回發的控制元件標識儲存到陣列列表中.

_registeredControlsThatRequirePostBack.Add(control.UniqueID);

}



另外在Page類的方法SaveAllState中有這麼一段程式碼,判斷上面的陣列列表是否為空,然後將其儲存到檢視狀態,以便頁面回發時再讀回來.

if (_registeredControlsThatRequirePostBack != null && _registeredControlsThatRequirePostBack.Count > 0)

{

...省略若干程式碼

if (controlStates == null)

{

controlStates = new HybridDictionary();

}

controlStates.Add(PageRegisteredControlsThatRequirePostBackKey, _registeredControlsThatRequirePostBack);

}

這樣需要回發的控制元件標識就會儲存到檢視狀態中去了.

現在你明白為什麼需要在 Page_PreRender事件之前註冊回發控制元件了吧,因為在Page_PreRender事件之後就是要寫入檢視狀態了.

如果不在Page_PreRender事件之前註冊該控制元件需要回發,那麼檢視狀態中就不會有該控制元件標識,結果在頁面回發時,頁面將不能觸發該控制元件的回發事件.當然了,不是絕對, 類似於TextBox這樣的表單域控制元件,是不需要註冊的,框架內部會自動找到它,後面會提及框架是如何做的.

那頁面是怎麼樣找到該控制元件並觸發該控制元件的回發呢?再看Page:: LoadAllState私有方法原始碼,它從檢視狀態中恢復了要求回發的控制元件列表:

private void LoadAllState()

{

...省略讀取檢視狀態若干程式碼

if (controlStates != null)

{


//這裡將那些需要回發的控制元件標識又都讀了回來,在SaveAllState方法中存入的.

_controlsRequiringPostBack = (ArrayList)controlStates[PageRegisteredControlsThatRequirePostBackKey];

//變數_registeredControlsRequiringControlState是呼叫Control的方法AddedControl時建立的.

//向Controls集合中新增物件時都會呼叫AddedControl方法.

//AddedControl方法中會呼叫 Page::RegisterRequiresControlState(Control control)方法,由它真正的建立.

if (_registeredControlsRequiringControlState != null)

{

foreach (Control ctl in _registeredControlsRequiringControlState) {

//讀回控制元件的檢視狀態

ctl.LoadControlStateInternal(controlStates[ctl.UniqueID]);

}

}

} ...

}

頁面在執行回發時Page要處理回發資料會用到下面方法:

private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad);

比如說一個TextBox回發了,它是實現IPostBackDataHandler介面的類,Page就會執行TextBox控制元件的LoadPostData的方法.

如果方法返回值為真,它將會呼叫控制元件本身實現IPostBackDataHandler介面的另一個方法RaisePostDataChangedEvent觸發回發資料更改事件.



好,到這裡我們分析Textbox這樣的表單域控制元件在沒有執行Page::RegisterRequiresPostBack方法進行註冊,怎麼還能進行資料回發呢?

因為它是表單元素,它始終會被回發,ProcessPostData會自動處理這樣的表單控制元件(隱藏域控制元件也是),但像Checkbox則不行,雖然它也是表單元素,但是當它不是被選中狀態時,它是不會被回發的,也就是說伺服器取不到它的值.



但如果是你的一個自定義(非表單域)控制元件(如一個Label的子類),就必須在OnPreRender方法之前註冊這個控制元件需要回發;

讓我們寫一個簡單的例子,瞭解回發的整個過程:

using System;

using System.Collections.Generic;

using System.Text;



namespace CustomWebControls

{

public class LabelPostData : System.Web.UI.WebControls.Label, System.Web.UI.IPostBackDataHandler

{

protected override void OnPreRender(EventArgs e)

{

base.OnPreRender(e);

this.Page.RegisterRequiresPostBack(this);

}



#region IPostBackDataHandler 成員



bool System.Web.UI.IPostBackDataHandler.LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)

{

return true;

}



void System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent()

{

System.Console.Write(this.Page.Request.Form.ToString()); //輸出當前表單窗體的字串形式

}



#endregion

}

}

上面程式碼始終會輸出當前表單窗體的字串形式.



如何做到回發事件

控制元件要處理回發事件,它必須實現IPostBackEventHandler介面.

在處理回發事件時,比如LinkButton的單擊事件回發,它會在隱藏域__EVENTTARGET中加入自己的id的.這樣Page的RaisePostBackEvent方法就會根據該隱藏域中的id找到LinkButton,然後執行它的IPostBackEventHandler介面方法RaisePostBackEvent.

但是類似像ImageButton,並不是依靠IPostBackEventHandler,它是在IPostBackDataHandler 介面LoadPostData方法中判斷是否有它的表單域名稱(UniqueID)回發,如果有則執行Page.RegisterRequiresRaiseEvent進行回發事件註冊,被註冊的物件的介面IPostBackEventHandler::RaisePostBackEvent方法會被執行.



注意Page.ProcessPostData函式要執行兩遍,一次在頁面生存週期的Load事件之前(為靜態控制元件處理回發事件,動態控制元件的回發資料會做為引數傳給該函式,在第二次呼叫時用到.).

第二次在頁面生存期Load之後(為動態控制元件處理回發事件).

為什麼要執行兩遍呢?因為第一次載入檢視狀態後,就馬上執行了ProcessPostData,不會有其它的干撓(保證頁面的完整),比如開發人員在Page_Load寫一些其它程式碼.第二次執行它可以處理在Page.Load之中生成物件的回發事件,給足了開發人員面子.



在下面的對ProcessPostData方法整體的註解中,大家會了解到ASP.NET框架在處理回發資料的原理;

//處理回發資料方法

private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad) {

if (_changedPostDataConsumers == null)

_changedPostDataConsumers = new ArrayList();

// identify controls that have postback data

//處理表單域控制元件,例如Text,Submit,前面說了TextBox控制元件內部沒有執行Page::RegisterRequiresPostBack方法,但仍能處理回發資料,原因就在這裡.

if (postData != null) {

foreach (string postKey in postData) {

if (postKey != null) {

// Ignore system post fields

if (IsSystemPostField(postKey))

continue;

Control ctrl = FindControl(postKey);

//根據回發物件標識獲取表單域控制元件

if (ctrl == null) {

if (fBeforeLoad) {

// It was not found, so keep track of it for the post load attempt

//為動態控制元件保留這些沒有被處理的回發鍵值,準備下一次呼叫會用到(Page.Load事件之後).

if (_leftoverPostData == null)

_leftoverPostData = new NameValueCollection();

_leftoverPostData.Add(postKey, null);

}

continue;

}

IPostBackDataHandler consumer = ctrl.PostBackDataHandler;

// Ignore controls that are not IPostBackDataHandler (see ASURT 13581)

if (consumer == null) {

// If it's a IPostBackEventHandler (which doesn't implement IPostBackDataHandler),

// register it (ASURT 39040)

//處理回發事件

//比如現在,回發的是Button控制元件,目前條件一定會滿足下面的.

//Button是表單元素,同時它實現了IPostBackEventHandler介面.

//所以它的事件會被執行.

if(ctrl.PostBackEventHandler != null)

RegisterRequiresRaiseEvent(ctrl.PostBackEventHandler);

continue;

}

bool changed;

if(consumer != null) {

changed = consumer.LoadPostData(postKey, _requestValueCollection);

//如果控制元件本身的資料變了,那麼後面將會執行資料變更事件.

if(changed)

_changedPostDataConsumers.Add(ctrl);

}

// ensure controls are only notified of postback once

//確保控制元件回發事件只被執行一次

//比如一個CheckBox控制元件在選中狀態後自動回發,那麼這裡將刪除它的註冊記錄.(CheckBox控制元件執行了Page.RegisterRequiresPostBack方法)

//否則它的回發事件可能會被執行兩次.

if (_controlsRequiringPostBack != null)

_controlsRequiringPostBack.Remove(postKey);

}

}

}



// Keep track of the leftover for the post-load attempt

ArrayList leftOverControlsRequiringPostBack = null;



// process controls that explicitly registered to be notified of postback

//處理註冊的要求回發的控制元件,比如自定義的Lable控制元件

if (_controlsRequiringPostBack != null) {

foreach (string controlID in _controlsRequiringPostBack) {

Control c = FindControl(controlID);



if (c != null) {

IPostBackDataHandler consumer = c._adapter as IPostBackDataHandler;

if(consumer == null) {

consumer = c as IPostBackDataHandler;

}



// Give a helpful error if the control is not a IPostBackDataHandler (ASURT 128532)

if (consumer == null) {

throw new HttpException(SR.GetString(SR.Postback_ctrl_not_found, controlID));

}



bool changed = consumer.LoadPostData(controlID, _requestValueCollection);

if (changed)

_changedPostDataConsumers.Add(c);

}

else

{

//首次載入,這裡如果沒有找到相應註冊過的id控制元件,那麼它可能就是動態控制元件,將這些id留給第二次呼叫該方法.

if (fBeforeLoad) {

if (leftOverControlsRequiringPostBack == null)

leftOverControlsRequiringPostBack = new ArrayList();

leftOverControlsRequiringPostBack.Add(controlID);

}

}

}



_controlsRequiringPostBack = leftOverControlsRequiringPostBack;

}



}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-620625/,如需轉載,請註明出處,否則將追究法律責任。

相關文章