从页面被编译成临时程序集开始,分析了页面架构是如何把客户端事件映射到服务端事件的。通过显示接口方法实现的,很巧妙的回调
两个基本概念:
1、什么是回传?
回传就是HttpPost请求,或者在Url中带有数据的HttpGet请求,通过网页源代码可以看到:
method="post" action="页面名",这说明了即使是Get也会被转到Post。
2、IPostBackEventHandle接口的作用是什么?
接口的实质就是把客户端事件映射到服务器端事件,有两类映射方式:
1、通过UniqueID。
2、通过脚本。
通过两个例子分析两种映射方式:
一、通过UniqueID
SimpleButton,模拟Button实现事件回传,以UniqueID方式回传到服务器端


[
// The DefaultEventAttribute allows a page
// developer to attach a handler to the default
// event by double-clicking on the control.
DefaultEvent("Click"),
DefaultProperty("Text")
]

public class SimpleButton: WebControl, IPostBackEventHandler
{
[
Bindable(true),
Category("Behavior"),
DefaultValue(""),
Description("The text to display on the button")
]

public virtual string Text
{

get
{
string s = (string)ViewState["Text"];
return((s == null) ? String.Empty : s);
}

set
{
ViewState["Text"] = value;
}
}
protected override void LoadViewState(object savedState)

{
base.LoadViewState(savedState);
}
protected override void TrackViewState()

{
base.TrackViewState();
}
protected override void LoadControlState(object savedState)

{
base.LoadControlState(savedState);
}
protected override void OnLoad(EventArgs e)

{
base.OnLoad(e);
}
protected override void OnInit(EventArgs e)

{
base.OnInit(e);
}
protected override object SaveControlState()

{
return base.SaveControlState();
}


protected override HtmlTextWriterTag TagKey
{

get
{
return HtmlTextWriterTag.Input;
}
}
[
Category("Action"),
Description("Raised when the button is clicked")
]
public event EventHandler Click;


protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Name,this.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Type,"Submit");
writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text);
}
// Method of IPostBackEventHandler that raises postback events.

void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{
OnClick(EventArgs.Empty);
}

// Invokes delegate registered with the Click event.

protected virtual void OnClick(EventArgs e)
{

if (Click != null)
{
Click(this, e);
}
}

protected override void Render(HtmlTextWriter writer)
{
// Ensures that this control is nested in a server form.

if (Page != null)
{
Page.VerifyRenderingInServerForm(this);
}
base.Render(writer);
}
}
让我们看看客户端单击事件是如何发送到服务器的:
首先,让我们从页面被编译成类似App_web_tingluzi.dll的临时文件开始说起,在此之前Application内部类CallHandlerExecutionStep的Excute()方法执行后,会转到Page的生命期。经过一系列的ProcessRequest、编译(JIT)App_web_tingluzi.dll临时文件,然后就会编译(JIT)我们写的控件的程序集了本例中是SimpleButton.dll,正式进入控件的生命期。(如果有Global,会在web页前被编译为Global.dll)
如果想了解详细的信息可以看dudu的:
解读System.Web.UI.Page中关键方法ProcessRequestMain()
注:基于1.1,不过基本的东西还是一样的
http://www.cnblogs.com/dudu/archive/2005/10/21/259328.htmlASP.NET 2.0运行时简要分析
http://www.cnblogs.com/dudu/archive/2006/01/14/317016.html客户端事件映射到服务器端事件的流程:
从Page的核心方法ProcessRequestMain()开始,它处理Page的整个生命期(详细的可看dudu的分析)。
现在只关注和回传有关的内容,下面是涉及到回发的页面生命期,从Init结束开始:
1、LoadAllState
(包含LoadPageStateFromPersistenceMedium,LoadViewStateRecursive)
2、ProcessPostData //通过判断控件是否实现PostBackEventHandler标记其是否引发Postback3、PreLoad(非PostBack)
4、Load(非PostBack)
5、ProcessPostData 6、RaiseChangedEvents
7、RaisePostBackEvent//通过显示接口调用客户端的RaisePostBackEvent,并且实现了两类绑定:UniqID和客户端脚步下面深入的了解一下Pages是如何实现自动映射的:
首先是ProcessPostData方法,他保证了回传能够正确的进行,是回传的基础:
下面是有关ProcessPostData的伪代码:
参数说明:postDate:Form回发给服务器的内容包含form中引起回传的控件、RequestQury的值、_VIEWSTATE、以及通过脚本回传给服务期的数据。通过Page.RequestValueCollection可以看到集合的值。
//postData指:Page.RequestValueCollection。bool标记是Load前调用的还是load后调用的
private IPostBackEventHandler _registeredControlThatRequireRaiseEvent;
private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad)
IPostBackDataHandler handler1 = control1.PostBackDataHandler;
if (handler1 == null)

{
if (control1.PostBackEventHandler != null)

{
this.RegisterRequiresRaiseEvent(control1.PostBackEventHandler);
}
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public virtual void RegisterRequiresRaiseEvent(IPostBackEventHandler control)

{
this._registeredControlThatRequireRaiseEvent = control;
}
Page会在postDate中寻找实现PostBackEventHandler接口的控件,如果找到就把他赋值给_registeredControlThatRequireRaiseEvent。这样在 RaisePostBackEvent(NameValueCollection postData)中就实现了通过UniqID把客户端事件映射到服务端。
下面看RaisePostBackEvent方法,下面是伪代码:
RaisPostBackEvent()是Page的方法和PostbackEventHandler接口的方法没有关系。
private void RaisePostBackEvent(NameValueCollection postData)

{
//ProcessPostData已经把实现PostBackEventHandler接口的控件标记为可PostBack了
if (this._registeredControlThatRequireRaiseEvent != null)

{
//通过UniqID映射
this.RaisePostBackEvent(this._registeredControlThatRequireRaiseEvent, null);
}
else

{
//通过脚步回传映射
string text1 = postData["__EVENTTARGET"];
bool flag1 = !string.IsNullOrEmpty(text1);
if (flag1 || (this.AutoPostBackControl != null))

{
Control control1 = null;
if (flag1)

{
control1 = this.FindControl(text1);
}
if ((control1 != null) && (control1.PostBackEventHandler != null))

{
string text2 = postData["__EVENTARGUMENT"];
this.RaisePostBackEvent(control1.PostBackEventHandler, text2);
}
}
else

{
this.Validate();
}
}
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)

{
//注意下面是显式接口调用,sourceControl被转换成了IPostBackEventHandler
sourceControl.RaisePostBackEvent(eventArgument);
}
在这个方法内部实现了两种映射,在有两个参数的重载中通过显式接口方法调用了子控件的RaisePostBackEvent,引发了子控件定义的服务器事件。
二、通过脚本
NavButtons,提供了向前和向后两个事件的导航按钮,因为有两个事件,事件通过委托链实现。


// NavButtons.cs
// Developing Microsoft ASP.NET Server Controls and Components
// Copyright ?2002, Nikhil Kothari and Vandana Datye
//

using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;



namespace MSPress.ServerControls
{
// The NavButtons control renders two HTML buttons and generates
// JavaScript to cause postback. Upon postback,
// the control uses the value of the argument passed into
// the RaisePostBackEvent method to determine which
// HTML element caused postback.
//
[
DefaultEvent("ClickNext"),
DefaultProperty("NextText")
]

public class NavButtons: WebControl, IPostBackEventHandler
{
private static readonly object EventClickNext = new object();
private static readonly object EventClickPrevious = new object();
[
Bindable(true),
Category("Behavior"),
DefaultValue(""),
Description("The text to display on the Next button")
]

public virtual string NextText
{

get
{
string s = (string)ViewState["NextText"];
return((s == null) ? String.Empty : s);
}

set
{
ViewState["NextText"] = value;
}
}
[
Bindable(true),
Category("Behavior"),
DefaultValue(""),
Description("The text to display on the Previous button")
]

public virtual string PreviousText
{

get
{
string s = (string)ViewState["PreviousText"];
return((s == null) ? String.Empty : s);
}

set
{
ViewState["PreviousText"] = value;
}
}
[
Category("Action"),
Description("Raised when the Next button is clicked")
]

public event EventHandler ClickNext
{

add
{
Events.AddHandler(EventClickNext, value);
}

remove
{
Events.RemoveHandler(EventClickNext, value);
}
}

[
Category("Action"),
Description("Raised when the Previous button is clicked")
]

public event EventHandler ClickPrevious
{

add
{
Events.AddHandler(EventClickPrevious, value);
}

remove
{
Events.RemoveHandler(EventClickPrevious, value);
}
}

// Invokes delegates registered with the ClickNext event.

protected virtual void OnClickNext (EventArgs e)
{
EventHandler clickNextHandler = (EventHandler)Events[EventClickNext];

if (clickNextHandler != null)
{
clickNextHandler(this, e);
}
}
// Invokes delegates registered with the ClickPrevious event.

protected virtual void OnClickPrevious (EventArgs e)
{
EventHandler clickPreviousHandler = (EventHandler)Events[EventClickPrevious];

if (clickPreviousHandler != null)
{
clickPreviousHandler(this, e);
}
}

// Method of IPostBackEventHandler that raises postback events.

void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{
if (eventArgument == "Previous")
OnClickPrevious(EventArgs.Empty);
else if (eventArgument == "Next")
OnClickNext(EventArgs.Empty);
}

protected override void Render(HtmlTextWriter writer)
{
// Ensures that this control is nested in a server form.

if (Page != null)
{
Page.VerifyRenderingInServerForm(this);
}
base.Render(writer);
}


protected override void RenderContents(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.GetPostBackEventReference(this, "Previous"));
writer.AddAttribute("language", "javascript");
writer.RenderBeginTag(HtmlTextWriterTag.Button);
writer.Write(this.PreviousText);
writer.RenderEndTag();

writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.GetPostBackEventReference(this, "Next"));
writer.AddAttribute("language", "javascript");
writer.RenderBeginTag(HtmlTextWriterTag.Button);
writer.Write(this.NextText);
writer.RenderEndTag();
}
}
}


这种绑定的实现和UniqueID类似,主要区别是,Page类的RaisPostBackEvent方法通过检测PostDate是否含有"__EVENTTARGET","__EVENTARGUMENT"来实现客户端事件到服务器的映射,见else部分的代码。
ClientScriptManager.GetPostBackClientHyperlink的作用:
1、生成回传的Javascrip和两个隐藏域
2、生成调用Javascrip的脚本,可以把它和Html特性绑定
注:编译指运行时编译,编译(JIT)指jit编译