理论文章(页生命周期与自定义控件的开发)_第1页
理论文章(页生命周期与自定义控件的开发)_第2页
理论文章(页生命周期与自定义控件的开发)_第3页
理论文章(页生命周期与自定义控件的开发)_第4页
理论文章(页生命周期与自定义控件的开发)_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Asp.Net 2.0 中的页生命周期与自定义用户控件开发2007/10/25摘要:Asp.Net2.0提供自定义用户开发的程序架构,基于该架构可以开发出满足用户特殊需求的各种可重用的控件。自定义控件的开发同样要遵循页生命周期的规律。在Asp.Net 2.0 中引入控件状态,以防止因关闭视图状态,而控件无法正常工作的现象。关键词:ASP.Net;页生命周期(Page Life Cycle);自定义控件;控件状态(View State)1.引述12.自定义控件的编程模型12.1.Net自定义控件开发静态模型12.2.简单呈现自定义控件的开发22.3.复合自定义控件的开发53.自定义控件的页生命周

2、期83.1.事件方法的调用顺序83.2.控件状态103.3.动态创建控件134.自定义控件的开发原则145.结束语15参考文献151. 引述在上一篇关于页生命周期的文章中重点介绍了页生命周期的基本概念,并给出了基本的页面编程规则。本篇文章则从自定义用户控件开发的角度进一步深入阐述页生命周期的问题,并且也给出开发自定义控件的一些基本编程规则以供参考。但是本篇文章并不阐述自定义控件的所有内容,有关这方面的完整帮助参见参考文献1。2. 自定义控件的编程模型2.1. .Net自定义控件开发静态模型自定义控件与Web用户控件类似,是页面组件重用的一种机制,但是它不同于Web用户控件,它提供了比Web用户

3、控件更强的设计时支持和实现与部署策略。但是自定义控件并不提供可视化的编程模型,.Net框架提供基于继承方式的编程模型。从.Net框架提供的自定义控件的静态模型来看,主要涉及到WebControl、CompositeControl两个重要的基类,而这两个基类都派生自Control基类。WebControl主要提供编写Web自定义控件需要公共属性、方法,CompisteControl是.Net2.0中为简化复合自定义控件编程工作而提供的基类,它同时又是从WebControl派生出来的子类,并且实现了INamingContainer,ICompositeControlDesignAccessor两个

4、接口,以方便子控件的自治地唯一命名(UniqueID),以及使复合控件设计器可以在设计时重新创建其关联控件的子控件。下图表示了主要的Web自定义控件的静态模型。System.Web.UI.ControlSystem.Web.UI.WebControls.WebControlSystem.Web.UI.WebControls.CompositeControlINamingContainer自定义控件开发无界面控件开发简单界面控件开发复合控件图 1 自定义控件开发的静态编程模型如果开发的是没有用户界面的自定义控件,自定义控件类直接从Control派生即可,如果开发的具有简单界面形式,一般这种控件最

5、后呈现为一个Html标记,则需要从WebControl派生,而如果开发的自定义控件是由已经存在的其他控件组合而成,则需要派生自CompositeControl。而在派生不同的类之后,还需要去重写一些基类方法和属性,以保证自定义控件的正常工作。下表给出了基本派生原则。表 1 自定义控件基本派生原则派生类原则需要重写的基类方法需要重写的基类属性Control无界面呈现。WebControl呈现为单一Html标记。RenderContents/RenderTagKey.getCompositeControl由其他控件组合而成,呈现为众多复杂的Hmtl标记。RecreateChildControlsC

6、reateChildControlsRender对于其他可以重写的方法和属性则根据实际需要确定是否重写。如,对于派生自WebControl的简单自定义控件,可以重写方法“AddAttributesToRender”以增加呈现为Html标记后的标记属性(Attribute)。2.2. 简单呈现自定义控件的开发下面定义一个类MailLink是一个呈现为电子邮件地址的简单控件的实现,它派生自WebControl基类。代码 1 MailLink的实现代码 / <summary> / 用于构造邮件地址的控件。 / </summary> DefaultProperty("

7、Text") ValidationProperty("EMail") ParseChildren(true, "Text") System.Drawing.ToolboxBitmap(typeof(MailLink), "MailLink.bmp") ToolboxData("<0:MailLink runat="server"></0:MailLink>") public class MailLink : WebControl private const st

8、ring _defaultText = "邮件地址" / <summary> / 邮件地址的显示文本。 / </summary> Bindable(true) Category("外观") DefaultValue(_defaultText) PersistenceMode(PersistenceMode.InnerDefaultProperty) Localizable(true) Description("显示为文本。") public virtual string Text get String s =

9、(String)ViewState"Text" return (s = null) ? _defaultText : s); set ViewState"Text" = value; / <summary> / 邮件地址,以“mailto:”开始。 / </summary> Bindable(true) Category("数据") DefaultValue("exapmle") Localizable(true) Description("E-Mail链接地址。") p

10、ublic virtual string EMail get String s = (String)ViewState"EMail" return (s = null) ? "exapmle" : s); set ViewState"EMail" = value; / <summary> / 重写属性。 / </summary> protected override HtmlTextWriterTag TagKey get return HtmlTextWriterTag.A; / <summary>

11、; / 重写方法。 / </summary> / <param name="writer"></param> protected override void AddAttributesToRender(HtmlTextWriter writer) base.AddAttributesToRender(writer); writer.AddAttribute(HtmlTextWriterAttribute.Href, "mailto:" + EMail); / <summary> / 重写方法。 / <

12、/summary> / <param name="output"></param> protected override void RenderContents(HtmlTextWriter output) if (String.IsNullOrEmpty(Text) Text = EMail; output.Write(Text); 上述代码中,定义了两个属性“Text”和“EMail”分别表示电子邮件地址的现实文本和地址本身的链接,这两个属性都被保存在视图状态中。重写的属性为“TagKey”,将该自定义控件呈现为“HtmlTextWrite

13、rTag.A”枚举定义的Html的标记“<a>”。重写的方法有两个,其中“RenderContents”用于将属性“Text”呈现为Html标记内包含的文字,而“AddAttributesToRender”用于为呈现的Html标记加上属性(Attribute)。该控件最终呈现的Html为:代码 2 MailLink 自定义控件最终呈现的Html<a href=”mailto:example”>邮件地址</a>虽然对于简单控件也可以直接重写“Render”方法完成所有的呈现工作,但是这种做法并不推荐。具体的代码如下:代码 3直接重写Render方法完成所有呈现

14、工作 protected override void Render(HtmlTextWriter output) output.Write(“<a href=mailto:” + EMail + “>” + Text + “</a>”; 2.3. 复合自定义控件的开发而对于复合自定义控件则主要通过重写“Render”方法,对复合控件进行呈现。具体的代码可以参见“eSchool.Web.UI.WebControls”命名控件下的一些派生自“CompositeControl”的自定义复合控件的源码。复合控件是由众多的子控件组合而成的,“Render”方法只能用来呈现所有子控

15、件,但是创建子控件的任务则是由“CreateChildControls”和“RecreateChildControls”方法来实现的,因此需要重写这两个重要的方法。如果将上述的MailLink自定义控件改造为一个符合控件,则具体的代码如下:代码 4改造为复合自定义控件的MailLink实现 / <summary> / 用于构造邮件地址的控件。 / </summary> DefaultProperty("Text") ValidationProperty("EMail") ParseChildren(true, "Text

16、") System.Drawing.ToolboxBitmap(typeof(MailLink), "MailLink.bmp") ToolboxData("<0:MailLink runat="server"></0:MailLink>") public class MailLink : CompositeControl private const string _defaultText = "邮件地址" / <summary> / 邮件地址的显示文本。 / <

17、/summary> Bindable(true) Category("外观") DefaultValue(_defaultText) PersistenceMode(PersistenceMode.InnerDefaultProperty) Localizable(true) Description("显示为文本。") public virtual string Text get String s = (String)ViewState"Text" return (s = null) ? _defaultText : s); s

18、et ViewState"Text" = value; / <summary> / 邮件地址,以“mailto:”开始。 / </summary> Bindable(true) Category("数据") DefaultValue("exapmle") Localizable(true) Description("E-Mail链接地址。") public virtual string EMail get String s = (String)ViewState"EMail&quo

19、t; return (s = null) ? "exapmle" : s); set ViewState"EMail" = value; / <summary> / 定义子控件。 / </summary> HyperLink _hyperLnkEmail; / <summary> / 重写方法属性。 / </summary> protected override void RecreateChildControls() EnsureChildControls(); / <summary> / 重

20、写方法。 / </summary> protected override void CreateChildControls() this.Controls.Clear(); _hyperLnkEmail = new HyperLink(); _hyperLnkEmail.ID = "hyperLnkEmail" _hyperLnkEmail.Text = this.Text; _lnkBtnFirst.NavigateUrl = "mailto:" + this.EMail; this.Controls.Add(_hyperLnkEmail)

21、; / <summary> / 重写方法。 / </summary> / <param name="writer"></param> protected override void Render (HtmlTextWriter writer) this.AddAttributesToRender(writer); writer.RenderBeginTag(HtmlTextWriterTag.Table); writer.RenderBeginTag(HtmlTextWriterTag.Tr); writer.RenderBe

22、ginTag(HtmlTextWriterTag.Td); _lnkBtnFirst.RenderControl(writer); writer.RenderEndTag(); writer.RenderEndTag(); writer.RenderEndTag(); 从上述代码可以看出,变化主要在以下几点:(1) 需要派生自基类“CompositeControl”。(2) 需要将自控件定义为自定义控件的成员变量,如:“HyperLink _hyperLnkEmail;”。(3) 需要重写方法“RecreateChildControls”,在该方法中调用“EnsureChildControls

23、”方法,确保自定义控件是否包含子控件,如果不包含,则创建子控件。(4) 需要重写方法“CreateChildControls”,实例化子控件,并设置子控件的属性。(5) 需要重写方法“Render”,以呈现自定义控件,包括对子控件的呈现。在该过程中请注意以下几点:(1) 在重写“CreateChildControls”方法的时候,首先需要将当前自定义控件的子控件集合清空,即调用语句“this.Controls.Clear()”,在将子控件实例化之后,需要将子控件添加到自定义控件的子控件集合中,即调用语句“this.Controls.Add(_hyperLnkEmail)”。如果不清空子控件集合

24、,则在自定义控件重新创建的时候会因为视图状态而保留上次的结果,并重复添加自控件。如果不将子控件添加到自定义控件子控件集合中,虽然能正常呈现子控件,但无法响应子控件的服务器事件。(2) 在重写“Render”方法的时候,需要首先应用Html标记属性(Attribute)呈现,即调用语句“this.AddAttibutesToRender(writer)”,以便能够对自定义控件应用样式和皮肤。(3) 对自定义控件的呈现的时候,推荐使用“堆栈方式”呈现。上述代码中的“writer.RenderBeginTag(HtmlTextWriterTag.Table)”即向呈现堆栈中送入了Html标记“<

25、;Table>”,接着使用“writer.RenderBeginTag(HtmlTextWriterTag.Tr)”进一步送入“<Tr>”标记,然后在呈现完对应的控件之后,调用若干出栈呈现语句“writer.RenderEndTag()”,以呈现Html的结束标记“</Table>”等。由于采用栈方式,因此在出栈呈现的时候,无须指明呈现的Html标记。这样处理的好处在于代码结构较好,并且Html标记可以被Asp.Net框架识别在不同浏览器中按照相关特性进行呈现。(4) 子控件的呈现很简单,只需要调用子控件的方法“RenderControl”即可完成。3. 自定义控

26、件的页生命周期自定义控件同样遵循Asp.Net框架对页生命周期的处理,与Web用户控件相比较,除了关心主要事件方法在一次页生命周期中的调用顺序之外,还需要了解一些被重写的方法在一次页生命周期中被调用的时机。只有了解它们被调用的顺序之后,才能正确地处理自定义控件的编程问题。3.1. 事件方法的调用顺序图2给出了以复合自定义控件为例的一次页生命周期的方法调用顺序。从图中可以看出,自定义控件的事件方法与Web用户控件一致,但是由于自定义控件的编程环境不是在Asp.Net的页面设计环境中,因此编写自定义控件的事件方法时,只需要重写相关事件方法即可,如:“OnInit”、“OnLoad”、“OnPreR

27、ender”、“OnUnload”。在自定义控件的页生命周期中,有以下几点需要关注:(1) “CreateChildControls”方法的调用相对比较特殊,虽然在图 中出现了两次该方法的调用,但是在运行时,是在不同时间内被调用的独立两次,在一次页生命周期中,仍然是只被调用一次。区别就在于当页请求为初次页请求的时候,该方法将被Asp.Net框架在页的“Page_PreRender”事件方法被执行之后才被调用。但是当回发页请求的时候,由于页生命周期需要在“Page_PreLoad”之前恢复所有控件的动态页视图状态,因此需要在该事件方法被调用之前创建复合控件的子控件,以便恢复其动态视图状态。(2)

28、 “Render”方法则是在“Page_SaveStateComplete”页事件方法之后被调用的。(3) 如果自定义控件的“Visible”属性被设置为“false”,则自定义控件的“OnPreRender”、“CreateChildControls”和“Render”方法将不会被调用。Page_PreInitPage_InitPage_InitCompletePage_PreLoadPage_LoadControl PostBack EventPage_LoadCompletePage_PreRenderData Bound EventPage_PreRenderCompletePage_

29、SaveStateCompletePage_UnloadOnInitOnLoadControl PostBack EventOnPreRenderOnUnload页事件复合自定义控件事件/方法Page ValidControl ValidCreateChildControlsCreateChildControlsRender图 2 页面与复合自定义控件事件及调用顺序3.2. 控件状态.Net2.0中增加了控件状态的概念,它不同于视图状态,控件状态不会随页面或控件的视图状态的关闭而丢失,以控件状态保存的属性值将会强制保存在视图状态承载的隐藏域中,在每次回发请求的过程中都会被恢复。这样可以保证自定

30、义控件的关键属性值不会因视图状态被关闭而丢失,从而影响到控件的基本正常运行。但是控件状态不能大量存在,因为它在页各请求期间总是存在,并且作为回发数据处理,会影响系统的系统,应该将关键性的控件属性保存为控件状态。在“eSchool.Web.UI.WebControls”命名空间中提供的很多复合控件是对外部控件进行控制的自定义控件,如:“Paging”控件就是用来为“GridView”这样支持分页功能的控件提供更加丰富的分页功能,它有一个属性“ControlID”就是用来记录需要控制分页的外部控件的ID,如:某个页面指定的“GridView”控件的ID。这样的属性如果在页面回发请求过程中丢失,将会

31、造成“Paging”控件无法查找到需要控制分页的外部控件,从而无法正常工作。代码 5记录为控件状态的属性 private string _controlId = string.Empty; / <summary> / 控制分页的控件ID。 / </summary> Browsable(true) IDReferenceProperty TypeConverter(typeof(ControlIDConverter) DefaultValue("") Category("数据") Description("控制分页的控件I

32、D。") public string ControlID get return _controlId; set _controlId = value; 上述代码中,记录为控件状态的属性,按照普通的属性定义方法去封装一个在自定义控件类中定义的成员变量。为了将该属性保存为控件状态还需要在自定义控件的“OnInit”事件方法中用页面对象的“RegisterRequiresControlState”方法将该自定义控件注册为需要保存控件状态的页面控件,以通知页面在保存状态的时候,将需要保存为控件状态的属性保存到控件状态中。代码 6用“RegisterRequiresControlState”页

33、面方法将当前控件注册为需要保存控件状态的页面控件 / <summary> / 重写OnInit方法,以响应控件的Init事件,并将当前控件注册为需要页面保存其控件状态的页面控件。 / </summary> / <param name="e"></param> protected override void OnInit(EventArgs e) base.OnInit(e); Page.RegisterRequiresControlState(this); InitControl(); 但是有上述代码,还无法将“Control

34、ID”属性保存在控件状态中,还需要去重写两个重要的基类方法“SaveControlState”和“LoadControlState”。代码 7保存和装载控件状态的代码 / <summary> / 保存控件状态。 / </summary> / <returns>需要保存的控件状态对象。</returns> protected override object SaveControlState() System.Collections.ArrayList list = new System.Collections.ArrayList(); list.A

35、dd(base.SaveControlState();/保存基类控件状态 list.Add(_controlId); return list; / <summary> / 在回发请求的时候去重新装载控件状态。 / </summary> / <param name="savedState">需要状态的控件状态。</param> protected override void LoadControlState(object savedState) if (savedState is System.Collections.Array

36、List) ArrayList list = savedState as ArrayList; if (list.Count >= 2) base.LoadControlState(list0);/恢复基类控件状态 ControlID = (string)list1; else base.LoadControlState(savedState); “SaveControlState”重写方法是用来提供需要保存到控件状态中的对象,“LoadControlState”重写方法则是从该方法的参数中恢复保存在控件状态中的属性值。由于控件状态需要作为隐藏域来处理,因此保存在控件状态中的属性类型需要

37、是可序列化的类型。其中“SaveControlState”方法将在每次页请求的“Page_SaveStateComplete”页面事件之前被调用,而“LoadControlState”方法只在页面回发请求中的“Page_PreLoad”页面事件之前被调用。具体调用过程参见图 。Page_PreInitPage_InitPage_InitCompletePage_PreLoadPage_LoadControl PostBack EventPage_LoadCompletePage_PreRenderData Bound EventPage_PreRenderCompletePage_SaveSt

38、ateCompletePage_UnloadOnInitOnLoadControl PostBack EventOnPreRenderOnUnload页事件复合自定义控件事件/方法Page ValidControl ValidCreateChildControlsCreateChildControlsRenderSaveControlStateLoadControlState图 3“SaveControlState”和“LoadControlState”方法在页生命周期中的调用时机3.3. 动态创建控件动态创建控件是指在自定义控件中,或者是在页面中,控件的个数或者类型是随着运行时的要求而发生变

39、化的一种控件创建方式。在“eSchool.Web.UI.WebControls”命名空间中,用于执行筛选的控件“Filtering”,其动态增加和删除条件构造的过程即属于动态创建控件的过程。使用“Filtering”控件的执行效果如图。一组动态创建的条件表达式构造控件图 4“Filtering”筛选控件动态创建条件表达式构造控件的运行效果在“”控件中,用于构造筛选条件的子控件是按照组的方式,被用户在运行的时候动态构造的。对于Asp.Net框架来说,如何保持上一次页面请求过程中的这些动态构造的控件属性是很关键的。这主要通过为每次动态构造的控件保持一个唯一不重复的“ID”属性。Asp.Net框架按

40、照页面每个控件的“ID”属性在视图状态中寻找这些控件的上次页请求保留下来的属性值,并在当前请求过程中恢复过来。因此,在为了做到这一点,在自定义控件的“CreateChildControls”重写方法中要能为动态构造的控件恢复性的创建它们的“ID”属性。具体代码请参见“eSchool.Web.UI.WebControls.Filtering”控件的源代码。下面的代码是动态创建条件表达式构造控件的核心代码。代码 8动态创建条件表达式构造控件的核心代码 private const string m_CriteriaNoListViewStateName = "CriteriaNoList&

41、quot; / <summary> / 为每一个筛选条件设置一个唯一的编号,以便在回发请求的时候重构筛选条件。 / </summary> Browsable(false) Description("记录条件编号。") protected int CriteriaNoList get return Utility.GetViewStatePropertyValue<int>(this.ViewState, m_CriteriaNoListViewStateName, new int 0 ); set Utility.SetViewState

42、PropertyValue<int>(this.ViewState, m_CriteriaNoListViewStateName, value); / <summary> / 记录在当前页请求周期中从条件编号列表中恢复过来的编号对应的列表下标。 / </summary> private int iCriteriaNo = 0; / <summary> / 构造空白的筛选条件行。 / </summary> / <returns></returns> private TableRow ConstructBlankC

43、riteriaTableRow() int iCount = (iCriteriaNo > this.CriteriaNoList.Length - 1) ? Utility.GetMaxValue<int>(this.CriteriaNoList, 0) + 1 : this.CriteriaNoListiCriteriaNo; TableRow tblRow = new TableRow(); TableCell tblCell = new TableCell(); CheckBox chkBoxChooseOne = new CheckBox(); / 在此处需要位当前

44、动态创建的控件安置唯一的ID。 chkBoxChooseOne.ID = "chkBoxChooseOne" + iCount.ToString(); tblCell.Controls.Add(chkBoxChooseOne); tblRow.Cells.Add(tblCell); /其它动态创建控件的代码。 iCriteriaNo+; return tblRow; 上述代码中使用了一个“CriteriaNoList”的属性保存每次动态创建的条件表达式的控件ID属性的一个顺序编码,并且将其保存在视图状态中,以便下次页请求的时候恢复过来。在“ConstructBlankCriteriaTableRow”方法中,为每个动态创建的控件依照恢复过来的“CriteriaNoList”中的记录恢复ID。由于动态创建控件的问题,关键在于控件的ID需要是唯一的。4. 自定义控件的开发原则由于自定义控件对代码编写能力和页生命周期的理解要求较高,如果没有处理好页生命周期的问题,很容易引发设计时和运行的错误,因此,需要有一个开发指导性原则会帮助写出正确的控件。以下是在开发“”命名空间中的自定义控件时总结出来的原则。(1) 首先要了解页事件方法和自定义控件方法之间的调用顺序,具体参见以上陈述。(2) 对于复合自定义控件中的子控件,以

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论