要了解IPostBackEventHandler接口,首先就必须了解.net Framework的脚本注册原理和一些重要的内置脚本。
一、脚本注册
.net Framework可以在我们毫不知情的情况下,根据我们在服务端对Control属性的设置,在ControlRender的时候,根据需要动态向客户端注册脚本和Hidden的<input>元素,Hidden的<input>用来在客户端保存一些重要的信息,而脚本是用来完成一些逻辑行为的控制。我们先看看.net Framwork是如何实现脚本和Hidden <input>的.
WebForm编程过程中,如果我们希望向客户端输出脚本或者一些Hidden的<input>元素,我们通常是通过 Page.ClientScript对象完成的,这个对象是一个ClientScriptManager类型的实例,我们一般(也有特殊情况)在Control.OnPreRender()方法里面调用Page.ClientScript.RegisterHiddenFiled或者Page.ClientScript.RegisterStartScript,还可以获得一些内置的脚本,比如 Page.ClientScript.GetPostBackEventReference,这些方法的调用都会记录一些标记数据,真正输出到客户端,是在Page的Render方法调用的时候,而完成输出的是下面两个方法:
Page.BeginFormRender 和Page.EndFormRender
这两个方法会在HtmlForm.RenderChildren里面调用,用来给<form>的开始和结束位置添加一些脚本和hidden field。具体完成的功能有:
* Render所有Register的Hidden Fields,同时也Render用来保存ViewState的Hidden Field
* Render用来保存当前 <form>的滚动位置的Hidden Field和Start Javascript
* Render控制当前焦点的Focus.js脚本引用语句
* Render用来执行回调的__doPostBack()函数,仅仅在相关标记打开的时候才会Render.
* Render用来执行PostBack的WebForms.js脚本引用语句。这个文件主要包含了WebForm常用的脚本,有PostBack的脚本和CallBack的脚本。
* Render已经注册的脚本块(Script Block)
* Renderl Client Startup Script(启动即执行的脚本)
__doPostBack()脚本
Render出来的__doPostBack()如下:
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
WebForm_DoPostBackWithOptions脚本
还有一个比较重要的脚本就是WebForm_DoPostBackWithOptions,它的作用比__doPostBack更强,比如对于支持CauseValidation属性的Control,如Checkbox,TextBox,这些Control当CauseValidation为true的时候,它就会在onclick属性里面Render出WebForm_DoPostBackWithOptions脚本。这样可以在调用theForm.submit()方法之前执行当前Form的所有Validator的客户端校验。
所以事实上,WebForm_DoPostBackWithOptions是包容__doPostBack函数的,凡是注册了WebForm_DoPostBackWithOptions的地方,必须注册__doPostBack,因为WebForm_DoPostBackWithOptions里面在执行完很多附加功能的代码(如对所有的Validator进行校验,控制Focus在没有通过校验的Control等)后,如果一切正常并且需要做ClientSubmit( Button当UseSumitBehavior为true和ImageButton不使用Client Script Submit,它们自己提交,因为它们自己输出的就是可以触发<form>提交的<input>元素),就调用__doPostBack。只有调用__doPostBack才会给__EVENTTARGET和__EVENTARGUMENT设置值,所以 Button(UseSumitBehavior为true)和ImageButton引起的回传的时候,我们观察__EVENTTARGET和__EVENTARGUMENT,会发现都为“”。
二、使用脚本进行PostBack的Control分析
注意首先要说明的一点是在HTML的表单域里面,具备自动让<form>想服务器端发送数据,引起回传的只有两个:
<input type="submit">
<input type="image"> Button
这是个特殊的Control,因为它们本身具备了自动调用<form>submit的能力,Button上面有一个属性叫UseSubmitBehavior,这个属性用来控制生成的<Input>的type是"submit"还是"button",如果为true,那么就是"submit".
1. 如果CauseValidation为false,UserSubmitBehavior为true,那么意味着仅仅进行提交,并不校验,所以这个时候,生成的代码是
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2. 如果CauseValidation为true, UseSubmitBehavior为true,那么生成的代码是
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
3. 如果CauseValidation为false,UseSubmitBehavior为false,那么生成的代码是
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
ImageButton
imageButton 比较特别的是同时实现了IPostBackDataHandler, IPostBackEventHandler, 在Asp.net2.0里面只有两个Control同时实现了这两个接口,一个是ImageButton,一个是HtmlInputImage。这两个Control最后生成的都是<input type="image">.
对于这个HTML元素,它在点击的时候,会引起<form>submit数据,同时会把当前点击的位置作为两个表单域被<form>收集。比如:<input type="image" name="myImageButton">,那么点击到(20,100)的时候,<form>表单域里面会多出两个:["myImageButton.x"] = "20",["myImageButton.y"] = "100".
通过上面的介绍,我们知道<input type="image">提交的数据和它的Name并不一致,根据我上一篇文章( Web Control 开发系列(二) 深入解析Page的PostBack过程和IPostBackDataHandler)的介绍,我们知道它必须使用注册的方法才能保证page在处理Request的时候调用当前Control的IPostBackDataHandler接口。具体做法就是在PreRender的时候调用Page.RegisterRequiresPostBack(this).
同上面介绍的Button一样,当CauseValidation为true的时候,会给输出加一个οnclick="WebForm_DoPostBackWithOptions"的属性设置,这样在提交前可以使用脚本进行校验。否则就不会生成脚本。
CheckBox,TextBox,RadioButton,ListControl及其派生类 等
这些Control输出的Html元素都没有自动submit的能力,所以这些Control普通情况下是不会引发回传的,但是为了方便用户,.net Framework在上面暴露了一个属性叫AutoPostBack,一旦这个属性为true,就表示这些Control具备了引发回传的能力,具体怎么实现回传呢,还是依赖于上面介绍的两段脚本。
当AutoPostBack为true,CauseValidation为true的时候就注册WebForm_DoPostBackWithOptions. 当AutoPostBack为true,CasuseValidataion为false的时候就注册 __doPostBack函数, 同时在AutoPostBack属性为true的时候, 为了防止性能问题,一般注册的脚本都用setTimeout(函数名,0)包起来,这样可以认为是一个模拟的异步调用(事实JavaScript是单线程的,这样的调用会自动进入调用队列,等待执行,不会阻塞现在的调用)。
三、服务器端处理
通过上面的分析知道,.net Framework一般是通过注册脚本,在脚本里面调用theForm.submit()来进行提交的,这样就形成了一次PostBack,而可以导致回传的两个重要的脚本也已经在上面介绍了。那么当脚本导致回传了,服务器端是如何处理请求并引发Control的事件的呢?
通过我第一篇文章( Web Control 开发系列(一) 页面的生命周期)的介绍,我们知道在页面Load阶段结束后,如果Page.IsPostBack,我们会先进入 IPostBackDataHandler的处理阶段,然后才进入IPostBackEventHandler处理阶段。我们下面分析的就是 IPostBackEventHandler处理阶段的逻辑。这个逻辑是通过 Page.RaisePostBackEvent(NameValueCollection postData)进入的。
在这个函数的处理里面有好几种情况:
1. 其中最普通的一种处理是通过 postData["__EVENTTARGET"]和 postData[“__EVENTARGUMENT”]拿到相应的 值,这些值都是在<form>提交前通过脚本设置上去的,然后通过Page.FindControl来找到合适的Control,这样就可 以取到Control.PostBackEventHandler,然后调用 IPostBackEventHandler.RaisePostBackEvent方法,就导致Control的服务端事件被触发。
2. 还有一种情况,就是服务器端的Control是Button或者ImageButton,它们的提交是通过Html元素自己的能力,所以提交发生的时候,没有任何脚本调用,自然postData["__EVENTTARGET"]和 postData[“__EVENTARGUMENT”]都为"",这个时候我们如何找到引发PostBack的Control并且调用它的 IPostBackEventHandler接口的方法呢?
这就要利用另一种发事件的机制——注册机制。这个机制主要通过 Page.RegisterRequiresRaiseEvent(IPostBackEventHandler control)函数实现的,这个函数在Asp.net2.0中有三个地方调用:
- HtmlInputImage.LoadPostData,
- ImageButton.LoadPostData,
- Page.ProcessPostData
对于HtmlInputImage和ImageButton这两个Control,它们都有PostBackData,而且通过注册的方法实现了IPostBackDataHandler接口,所以在LoadPostData阶段调用Page.RegisterRequiresRaiseEvent,这样就显式的告诉Page在PostBackEvent处理阶段调用自己的IPostBackEventHandler接口,就实现了服务端Click事件的触发。
那么Page.ProcessPostData函数(在我的上一篇文章 Web Control 开发系列(二) 深入解析Page的PostBack过程和IPostBackDataHandler有介绍),它会收集所有的表单提交数据,如果有和这个数据对应的Control(通过Page.FindControl查找),那么就设法调用其IPostDataHandler,如果IPostDataHandler为null,那么设法取其IPostEventHandler,如果不为null,那么就调用Page.RegisterRequiresRaiseEvent函数来注册它。Button只实现了IPostBackEventHandler接口,没有实现IPostBackDataHandler接口,所以就通过这种发式来触发事件的。
一旦在Page上进行了Page.RegisterRequiresRaiseEvent注册,系统就不会关心postData["__EVENTTARGET"]和postData["__EVENTARGUMENT"]了,直接就调用注册的IPostBackEventHandler.RaisePostBackEvent方法。
上面介绍的内容都是对Page.RaisePostBackEvent的分析:
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
四、Composite Control 的冒泡事件
在Control上面有一个方法RaiseBubbleEvent,这个方法就是沿着Control Tree向上一次调用OnBubbleEvent函数,知道返回true,就推出,是一个典型的冒泡事件。Control对于OnBubbleEvent的实现是简单的返回false,也就是说如果我们不做处理,那么事件会不停的向上冒泡知道最顶端的Page。
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/images/cnblogs_com/joeliu/classes.png)
从上面,我们最值得注意的是有三个简单Control实现了向上冒泡: Button, ImageButton, LinkButton,其它的都是一些复合Control在OnBubbleEvent里面进行二次冒泡。因此如果我们做一个复合的Control,我们可以在最外层的OnBubbleEvent函数里面监听这个Control内部的所有的Button,ImageButton,LinkButton的事件。
五、总结
所有WebForm事件的根源依赖于Form的submit()执行而引起PostBack(CallBack这里不考虑),而引起PostBack主要依赖于Html Input (type="image" or "submit")元素和脚本。
然后在PostBack阶段分析数据,如果数据变化可以Raise相关的Event,如果客户端记录了谁发了Event,也可以发Event。如果想让Event冒泡,就call RaiseBubbleEvent