点击页面的控件会导致页面的postback,那么postback后页面是如何处理并且最终引发服务器端的事件的呢?下面我们就来简单的结合源代码叙述一下。
Code
/// <summary>
///
/// </summary>
/// <param name="postData">在load之前的那次调用是使用的request.Form里面的集合,
/// 在load之后的那次调用是使用的load之前控件还没有生成的键值对集合,而且在第一次已经被添加进入_leftoverPostData</param>
/// <param name="fBeforeLoad">true:load前的调用;false:load后的调用</param>
private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad)
{
//_changedPostDataConsumers 执行了控件的 LoadPostData 方法后并且返回 true 后被加入到这个集合中,从而在 RaiseChangedEvents 方法中调用
//IPostBackDataHandler.RaisePostDataChangedEvent(); 接口方法
//如:TextBox,CheckBox 或者 DropDownList 之类的 AutoPostBack 属性:
//如果 AutoPostBack 为 true,则在向客户端输出时,加入上面的 __doPostBack 式的回发;如为 false,则不加入这样的代码立即回发脚本,
//而是等待有其他可以引起回发的控件(比如 Button, LinkButton 等)回发后,
//在 this.RaiseChangedEvents(); 中触发 TextBox 的 TextChanged 事件, CheckBox 的 CheckedChanged 事件,DropDownList 的 SelectedIndexChanged 事件;在 RaisePostBackEvent 时继续分析其他引起回发的事件。
if (this._changedPostDataConsumers == null)
{
this._changedPostDataConsumers = new ArrayList();
}
if (postData != null)
{
//回传数据集合
foreach (string str in postData)
{
//没有值或者是系统字段(如: "__EVENTTARGET","__EVENTARGUMENT","__VIEWSTATEFIELDCOUNT","__VIEWSTATE","__VIEWSTATEENCRYPTED","__PREVIOUSPAGE","__CALLBACKID","__CALLBACKPARAM","__LASTFOCUS" 等等)
if ((str == null) || IsSystemPostField(str))
{
continue;
}
Control control = this.FindControl(str);
if (control == null)
{
//没有在页面上找到这个控件,并且在load前,说明是动态添加的控件,这个时候控件还没有生成,把key添加进去_leftoverPostData
//在load之后的调用再进行处理
if (fBeforeLoad)
{
if (this._leftoverPostData == null)
{
this._leftoverPostData = new NameValueCollection();
}
this._leftoverPostData.Add(str, null);
}
continue;
}
IPostBackDataHandler postBackDataHandler = control.PostBackDataHandler;
if (postBackDataHandler == null)
{
if (control.PostBackEventHandler != null)
{
//控件存在,没有实现IPostBackDataHandler,但是实现了IPostBackEventHandler,例如Button 我们为它注册事件。
//注意:这里只有被点击的 Button 会在 Request.Form 集合中生成一个 name-value 对,其他的不会
this.RegisterRequiresRaiseEvent(control.PostBackEventHandler);
}
}
else
{
if ((postBackDataHandler != null) && postBackDataHandler.LoadPostData(str, this._requestValueCollection))
{
//需要调用 IPostBackDataHandler.RaisePostDataChangedEvent()
this._changedPostDataConsumers.Add(control);
}
//从._controlsRequiringPostBack(LoadPageViewState时对它进行了赋值)中移除该控件.
//这样就从_controlsRequiringPostBack中移除了所有实现IPostBackDataHandler接口的控件.
//接着继续对 _controlsRequiringPostBack中余下的控件进行处理,
if (this._controlsRequiringPostBack != null)
{
this._controlsRequiringPostBack.Remove(str);
}
}
}
}
ArrayList list = null;
if (this._controlsRequiringPostBack != null)
{
foreach (string str2 in this._controlsRequiringPostBack)
{
Control control2 = this.FindControl(str2);
if (control2 != null)
{
IPostBackDataHandler handler2 = control2._adapter as IPostBackDataHandler;
if (handler2 == null)
{
handler2 = control2 as IPostBackDataHandler;
}
if (handler2 == null)
{
throw new HttpException(SR.GetString("Postback_ctrl_not_found", new object[] { str2 }));
}
if (handler2.LoadPostData(str2, this._requestValueCollection))
{
this._changedPostDataConsumers.Add(control2);
}
continue;
}
if (fBeforeLoad)
{
if (list == null)
{
list = new ArrayList();
}
list.Add(str2);
}
}
this._controlsRequiringPostBack = list;
}
}
/// <summary>
/// 这里只有被点击的 Button 会在 Request.Form 集合中生成一个 name-value 对,而且引发页面回传的 控件也只应该有一个,所以使用的“=”
/// </summary>
/// <param name="control"></param>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public virtual void RegisterRequiresRaiseEvent(IPostBackEventHandler control)
{
this._registeredControlThatRequireRaiseEvent = control;
}
/// <summary>
/// 如果上面的 _registeredControlThatRequireRaiseEvent 被赋值了,证明通过 RegisterRequiresRaiseEvent方法注册的事件
/// 因为 Button 会自动回发
/// </summary>
/// <param name="postData"></param>
private void RaisePostBackEvent(NameValueCollection postData)
{
if (this._registeredControlThatRequireRaiseEvent != null)
{
this.RaisePostBackEvent(this._registeredControlThatRequireRaiseEvent, null);
}
else
{
//如 LinkButton (对应于 HTML 的 A 元素),DropDownList (对应于 HTML 的 SELECT)等,则并不会自动引起回发。这种情况下,
//这时候 会自动生成一些脚本 如: href="javascript:__doPostBack('LinkButton1','')" 而__doPostBack('LinkButton1','')
//方法__EVENTTARGET,__EVENTARGUMENT分别赋值,并且值被传递回服务器端
/*
* <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
* <script type="text/javascript">
<!--
var theForm = document.forms['Form2'];
if (!theForm) {
theForm = document.Form2;
}
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
// -->
</script>
*/
//下面代码通过判断回传的值 __EVENTTARGET 证明是不是通过 __doPostBack 回传注册事件的。
string str = postData["__EVENTTARGET"];
bool flag = !string.IsNullOrEmpty(str);
if (flag || (this.AutoPostBackControl != null))
{
Control control = null;
if (flag)
{
control = this.FindControl(str);
}
if ((control != null) && (control.PostBackEventHandler != null))
{
string eventArgument = postData["__EVENTARGUMENT"];
//调用控件实现的 IPostBackEventHandler.RaisePostBackEvent 这个接口方法s
this.RaisePostBackEvent(control.PostBackEventHandler, eventArgument);
}
}
else
{
this.Validate();
}
}
}
/// <summary>
/// 调用控件实现的 IPostBackEventHandler.RaisePostBackEvent 这个接口方法
/// </summary>
/// <param name="sourceControl"></param>
/// <param name="eventArgument"></param>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
{
sourceControl.RaisePostBackEvent(eventArgument);
}
1实现的 IPostBackEventHandler 接口来注册回传事件,并且需要在控件里面传递参数的。
eg:
table1.Attributes.Add("OnClick", this.Page.GetPostBackClientEvent(this, pMenuItem.ID));
在生成html页面 的注册形式变为:
OnClick="__doPostBack('ToolBar1','tbSave')"
OnClick="__doPostBack('ToolBar1','tbCancel')"
//实现 IPostBackEventHandler 接口的方法
void IPostBackEventHandler.RaisePostBackEvent(string itemID)
{
this.OnMenuItemClick(new MenuItemClickEventArgs(this.SetMenuItemByitemID(itemID)));
}
其实这里也可以在OnPreRender中使用 this.Page.RegisterRequiresRaiseEvent(this);来强制调用 RaisePostBackEvent
2实现 IPostBackDataHandler 接口来注册回传事件,这里不需要传递参数。
因为通过调用方法 protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
postCollection[postDataKey]可以取到回传的数据进行处理,所以这里就需要控件存在一个 name = UniqueID 的input控件
private HtmlInputHidden SelectedTab;
this.SelectedTab = new HtmlInputHidden();
this.SelectedTab.ID = this.UniqueID;
如果没有这个 input控件,那么不会触发 LoadPostData方法了。
因为这个input控件 的 name和值会被加到 PostData键值对集合中去,而我们调用控件的 LoadPostData是根据PostData 里面的键数据找
到控件,然后调用的控件的LoadPostData 方法的。
其实这里也可以在OnPreRender中使用 this.Page.RegisterRequiresPostBack(this);来强制调用 LoadPostData
[EditorBrowsable(EditorBrowsableState.Advanced)]
public void RegisterRequiresPostBack(Control control)
{
if (!(control is IPostBackDataHandler) && !(control._adapter is IPostBackDataHandler))
{
throw new HttpException(SR.GetString("Ctrl_not_data_handler"));
}
if (this._registeredControlsThatRequirePostBack == null)
{
this._registeredControlsThatRequirePostBack = new ArrayList();
}
this._registeredControlsThatRequirePostBack.Add(control.UniqueID);
}
看上面,这里是 Add 说明可以调用多个控件的 LoadPostData 方法的。这里
this._registeredControlsThatRequirePostBack 和 _registeredControlThatRequireRaiseEvent
是有区别的
在生成html页面 的注册形式变为:
onClick="Tab_OnSelectServerClick(this,'TabControl1:tabPage1');__doPostBack('TabControl1','')"
//实现 IPostBackDataHandler 接口的LoadPostData方法
protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
base.ValidateEvent(postDataKey);
string text = this.Text;
string str2 = postCollection[postDataKey];
if (!this.ReadOnly && !text.Equals(str2, StringComparison.Ordinal))
{
this.Text = str2;
return true;
}
return false;
}
接着随着我们LoadPostData 的返回值来决定是否调用RaisePostDataChangedEvent 方法
protected virtual void RaisePostDataChangedEvent()
{
}
总结一下:
我们写控件的时候可以继承 IPostBackDataHandler IPostBackEventHandler
两个接口来来处理数据和回发页面
当我们的控件只继承IPostBackDataHandler 接口的话,我们在控件中必须满足条件:
控件里面存在一个 ID 为 UniqueID的input控件,或者在OnPreRender中使用 this.Page.RegisterRequiresPostBack(this);
只有这样 IPostBackDataHandler 中的 LoadPostData接口方法才会被调用。
而IPostBackDataHandler 接口中IPostBackDataHandler.RaisePostDataChangedEvent()方法是否被调用
是依靠LoadPostData 返回true 值来决定
如果LoadPostData 返回true
在这个控件里面触发事件的控件是HtmlInputButton,TextBox,CheckBox 或者 DropDownList 之类 那么它的 AutoPostBack 属性被设置成 true立刻回发,触发事件的控件是table,hr 等html元素,我们也可以依靠__dopostback()脚本 让页面立刻回发。
都不满足的话,而是等待有其他可以引起回发的控件(比如 Button, LinkButton 等)回发后,
只要一回发 方法RaisePostDataChangedEvent()必定执行
当我们控件只 继承 IPostBackEventHandler接口的时候 我们可以通过button,__dopostback()脚本(this.Page.GetPostBackClientEvent获得)回发页面并传递参数。