Web Control 开发系列(二) 深入解析Page的PostBack过程和IPostBackDataHandler)中已经介绍了IPostBackDataHandler的实现原理,本章主要介绍IPostBackEventHandler.最后进行总结,来看看Web Control整个事件机制的全貌。 要了解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()如下:
<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['form1']; ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
if (!theForm) theForm = document.form1;
} ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
function __doPostBack(eventTarget, eventArgument)
if (!theForm.onsubmit || (theForm.onsubmit() !=false)) theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
//]]>
</script>
其实一旦用户注册了__doPostBack函数,两个配套的Hidden字段也会同步注册
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value=""/>
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value=""/>
上面的代码就是先把Event target和Event Argument存入指定的Hidden Field中,然后调用<form>的submit方法来提交数据。这段脚本是Framework提供的最普通的引发PostBack的脚本,我们写Control的时候可以通过Page.ClientScript.GetPostBackEventReference来获得这个脚本(注意,这个方法有好几个重载的版本,其中当选用了一些参数的时候,也可能获得另一个脚本WebForm_DoPostBackWithOptions,下面将介绍)。
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分析 Button这是个特殊的Control,因为它们本身具备了自动调用<form>submit的能力,Button上面有一个属性叫UseSubmitBehavior,这个属性用来控制生成的<Input>的type是"submit"还是"button",如果为true,那么就是"submit". 1. 如果CauseValidation为false,UserSubmitBehavior为true,那么意味着仅仅进行提交,并不校验,所以这个时候,生成的代码是
<input type="submit" name="Button1" value="Button" id="Button1"/>
没有任何onclick的脚本调用。 2. 如果CauseValidation为true, UseSubmitBehavior为true,那么生成的代码是
<input type="submit" name="Button1" value="Button" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("Button1", "", true, "", "", false, false))" id="Button1"/>
因为要执行校验,所以必须调用WebForm_DoPostBackWithOptions方法。 3. 如果CauseValidation为false,UseSubmitBehavior为false,那么生成的代码是
<input type="button" name="Button1" value="Button" onclick="javascript:__doPostBack('Button1','')" id="Button1"/>
这个时候,因为type是“button”,它不具备submit数据的能力,所以只能用脚本帮助解决,同时不需要校验,所以就使用__doPostBack函数,如果CauseValidataion为true,onclick里面的函数就为WebForm_DoPostBackWithOptions,因为__doPostBack不具备客户端校验的能力。
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并不一致,根据我上一篇文章( HtmlInputImage.LoadPostData,
ImageButton.LoadPostData,
Page.ProcessPostData
这三个地方的调用都是在处理PostBackData阶段,因此我们可以认为这个注册机制最好在处理PostBackData阶段使用是比较符合规范的。 对于HtmlInputImage和ImageButton这两个Control,它们都有PostBackData,而且通过注册的方法实现了IPostBackDataHandler接口,所以在LoadPostData阶段调用Page.RegisterRequiresRaiseEvent,这样就显式的告诉Page在PostBackEvent处理阶段调用自己的IPostBackEventHandler接口,就实现了服务端Click事件的触发。
那么Page.ProcessPostData函数(在我的上一篇文章上面介绍的内容都是对Page.RaisePostBackEvent的分析:
privatevoid RaisePostBackEvent(NameValueCollection postData) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
// 1. 假如已经在Page上显式的注册了引起PostBackEvent的Control,就直接处理
if (this._registeredControlThatRequireRaiseEvent !=null) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
this.RaisePostBackEvent(this._registeredControlThatRequireRaiseEvent, null);
}
else ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
// 这部分代码,我自己按照Reflector反编译的结果重新组织了,但是逻辑
// 没有任何变化,只是方便阅读理解 ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制 [Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制](/default/index/img?u=L2RlZmF1bHQvaW5kZXgvaW1nP3U9YUhSMGNITTZMeTkzZDNjdVkyNWliRzluY3k1amIyMHZTVzFoWjJWekwwOTFkR3hwYm1sdVowbHVaR2xqWVhSdmNuTXZTVzVDYkc5amF5NW5hV1k9)
// 2. 假如没有注册,就查找__EVENTTARGET记录的Control来处理
string str = postData["__EVENTTARGET"];
bool flag =!string.IsNullOrEmpty(str);
Control control =null;
if (flag) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
control =this.FindControl(str);
if ((control !=null) && (control.PostBackEventHandler !=null)) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
string eventArgument = postData["__EVENTARGUMENT"];
this.RaisePostBackEvent(control.PostBackEventHandler, eventArgument);
}
}
elseif (this.AutoPostBackControl ==null) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
// 这个AutoPostBackControl的标记设置为了不重复做Validate,后面我在讲述
// Validation机制的时候会介绍
this.Validate();
}
}
}
四、Composite Control 的冒泡事件 在Control上面有一个方法RaiseBubbleEvent,这个方法就是沿着Control Tree向上一次调用OnBubbleEvent函数,知道返回true,就推出,是一个典型的冒泡事件。Control对于OnBubbleEvent的实现是简单的返回false,也就是说如果我们不做处理,那么事件会不停的向上冒泡知道最顶端的Page。
protectedvoid RaiseBubbleEvent(object source, EventArgs args) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
for (Control control =this.Parent; control !=null; control = control.Parent) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
if (control.OnBubbleEvent(source, args)) ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制]()
return;
}
}
} ![[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制 [Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制
[Joe 原创]Web Control 开发系列(三) 解析IPostBackEventHandler和WebForm的事件机制](/default/index/img?u=L2RlZmF1bHQvaW5kZXgvaW1nP3U9YUhSMGNITTZMeTkzZDNjdVkyNWliRzluY3k1amIyMHZTVzFoWjJWekwwOTFkR3hwYm1sdVowbHVaR2xqWVhSdmNuTXZUbTl1WlM1bmFXWT0=)
我们知道了这个冒泡的机制,那么冒泡的源头在哪里呢??这就是我们做Control的人要考虑的,如果我们希望我们的Control的Event支持冒泡,那么我们就应该在Control的Event发生的时候调用RaiseBubbleEvent这个函数,这样当别人在一个复合控件里面使用我们的Control的时候,它就可以在外面接收到我们Control发的冒泡事件,目前调用了这个冒泡函数的Control有
从上面,我们最值得注意的是有三个简单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
WebForm最大的魅力大概就是它自己的一套事件处理机制了,要做一个好的Control,必须深入理解这套机制,只有这样才可以让我们的Control有一整套Professional的Event,而IPostBackDataHandler和IPostBackEventHandler是实现事件机制的核心接口,在我的上一篇文章(Web Control 开发系列(二) 深入解析Page的PostBack过程和IPostBackDataHandler)中已经介绍了IPostBackDataHandler的实现原理,本章主要介绍IPostBackEventHandler.最后进行总结,来看看Web Control整个事件机制的全貌。 要了解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()如下:
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分析 Button这是个特殊的Control,因为它们本身具备了自动调用<form>submit的能力,Button上面有一个属性叫UseSubmitBehavior,这个属性用来控制生成的<Input>的type是"submit"还是"button",如果为true,那么就是"submit". 1. 如果CauseValidation为false,UserSubmitBehavior为true,那么意味着仅仅进行提交,并不校验,所以这个时候,生成的代码是
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并不一致,根据我上一篇文章(
那么Page.ProcessPostData函数(在我的上一篇文章上面介绍的内容都是对Page.RaisePostBackEvent的分析:
五、总结 所有WebForm事件的根源依赖于Form的submit()执行而引起PostBack(CallBack这里不考虑),而引起PostBack主要依赖于Html Input (type="image" or "submit")元素和脚本。 然后在PostBack阶段分析数据,如果数据变化可以Raise相关的Event,如果客户端记录了谁发了Event,也可以发Event。如果想让Event冒泡,就call RaiseBubbleEvent
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()如下:
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分析 Button这是个特殊的Control,因为它们本身具备了自动调用<form>submit的能力,Button上面有一个属性叫UseSubmitBehavior,这个属性用来控制生成的<Input>的type是"submit"还是"button",如果为true,那么就是"submit". 1. 如果CauseValidation为false,UserSubmitBehavior为true,那么意味着仅仅进行提交,并不校验,所以这个时候,生成的代码是
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并不一致,根据我上一篇文章(
那么Page.ProcessPostData函数(在我的上一篇文章上面介绍的内容都是对Page.RaisePostBackEvent的分析:
五、总结 所有WebForm事件的根源依赖于Form的submit()执行而引起PostBack(CallBack这里不考虑),而引起PostBack主要依赖于Html Input (type="image" or "submit")元素和脚本。 然后在PostBack阶段分析数据,如果数据变化可以Raise相关的Event,如果客户端记录了谁发了Event,也可以发Event。如果想让Event冒泡,就call RaiseBubbleEvent