扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
ASP.NET 2.0并没有抛弃1.1版本中的任何现有控件,而是增加了一组新的控件;同时还引入了若干新的控件开发技术。本系列文章将对这些内容展开全面探讨。
一、 引言
到目前为止,你可能已经了解了大量的ASP.NET 2.0新特征—母版页面,主题,提供者,等等……所有这样内容都相当精彩;但是,你是否了解到有关定制Web控件开发方面的重大变化?这正是我在本文中所想讨论的。如果你已经从事于控件开发,那么,我想本文所描述的ASP.NET 2.0中的新的改进特征会立即应用于你的控件开发中。
首先应该注意的是,你以前使用ASP.NET 1.1(或1.0)开发的所有Web控件在2.0版本下将继续良好运行—微软并没有破坏你的现有代码。在本文中,我将向你介绍的所有相关内容,包括许多新的令人激动的技术,所有这些你都可以添加到现有控件或在新的控件环境中使用。
作者注:本文假定你对定制Web控件开发已经有一个基本了解。在本文中,我以一个增强版本的EmailContact控件为例对ASP.NET 2.0中的Web控件改进技术作全面探讨。
二、 改进
表格1描述了ASP.NET 2.0在定制Web控件开发方面所作的大部分的重大改进。在本系列文章中,我将对这些特征展开逐一讨论。
表格1:ASP.NET 2.0 Web控件改进功能。
三、 增强EmailContact Web控件
本文中的定制EmailContact Web控件(参考图1)允许在你的站点中加入一个“contact us”表单,它具有完整的电子邮件功能。在本文中,我将使用该功能增强这一控件。
图1.缺省状态下的EmailContact控件
四、 一个新的基类
以前,开发者都是从WebControl类派生他们的可视化Web控件。我之所以在此使用了“可视化”一词是因为,典型情况上,没有在浏览器中生成任何内容的控件都是派生自Control类。这一点并没有改变—你应该继续使用该Control类来派生任何这样的非可视化控件—它们执行不可见功能或在浏览器中生成除可视化HTML内容之外的任何其它内容。而且,在开发可视化Web控件时,你还应该继续使用WebControl类。然而,我们所开发的大多数复合控件都是为了利用现有控件的功能。在这种情况下,你应该总是从WebControl类进行派生,但是你还要记住另外一些有关细节—否则的话,有可能导致许多问题。
复合控件必须实现INamingContainer接口,并且需要包括在你的控件类中。这个接口能够确保在你的控件及其可以生成的整个控件层次中的所有的HTML标签中都具有唯一的标签命名。当你在单个页面上存在多个相同类型的复合控件的情况下,这是相当关键的。在这样的情况下,你需要确保任何生成的子元素都具有唯一的名称。忘记实现该接口能够导致各种问题的出现。
在ASP.NET 2.0以前,复合控件开发者还需要记住在一个控件的Render方法中调用EnsureChildControls。在我以前的文章中曾经向你介绍如何重载该Render方法并且在调用基类的Render方法前调用这个方法。要使控件在Visual Studio设计时刻正确生成这一步是必要的;否则,有可能带来许多不便。
上面两个步骤在复合控件开发中如此普遍,以致于许多开发者往往都会构建一个包括这两个细节的基类,然后从该基类下派生他们所有的新的复合控件。作为代替,ASP.NET 2.0提供了(更准确地说是“名字为”)CompositeControl。借助于这个类来构建你的复合控件,你就不必再记住实现INamingContainer或从Render方法中执行一个EnsureChildControls调用了。
另外,还存在其它一些新的基类,例如用于数据绑定的控件等,在此不再赘述。 全面探讨ASP.NET 2.0中的Web控件改进技术之ControlState篇(二)
我的观点是:ViewState有可能成为你最好的朋友,也有可能成为你最坏的敌人—这要依赖于你使用它的方式来决定。如果你在以前曾经使用过ViewState,那么,你肯定会喜欢新的ControlState。
关于ViewState的最令人头痛的问题之一就是,它的“all-or-nothing”状态管理方法。页面开发者可以很容易地决定在任何控件级,页面级或在整个站点级(经由web.config)上关闭ViewState。事实上,如果你在整个站点级上通过web.config关闭ViewState的话,那么,你不妨猜测一下你还能够在其它什么地方关闭它?答案是:还可以在machine.config中实现—在此情况下,它能够影响到同一服务器上的所有站点。如果一个页面开发者决定关掉在ViewState中实现状态管理的能力,那么,你的控件生成有可能出现部分不可用,或更有甚者—完全不可用。
为此,在新版本中,微软创建了ControlState—旨在解决这一问题。页面开发者不能关掉ControlState,因此使用它进行属性选择更为安全。
使用ControlState与使用ViewState几乎完全一致。然而,ControlState并没有提供象ViewState这样的一个变量,而是提供了称为SaveControlState和LoadControlState的方法以便于你的控件能够进行重载。这些方法与SaveViewState和LoadViewState方法的工作原理完全一致。
因为ControlState在属性语句中没有提供一个相应的变量,所以,你必须借助于ASP.NET开发者以前在他们的对象中所使用的成员变量(属性语句)来实现相同的功能。
Protected _MailServer As String = "First name:" Public Property MailServer() As String Get Return _ MailServer End Get Set(ByVal value As String) _MailServer = value End Set End Property |
然而,因为我使用了一个标准的成员变量来保存值,所以我需要一种方法以便把数据存储在ControlState中—这正是前面提到的方法“登场”的原因。就象在它们相应的ViewState方法中那样,ASP.NET将在页面生命周期内调用这两个方法。其中,SaveViewState方法返回一个将被持久存储的对象类型。通过返回一个对象数组,这个方法可以存储多个值。并且,就象发生在SaveViewState方法中一样,也是使用数组的0下标元素来调用基类的SaveControlState方法。
Protected Overrides Function SaveControlState() As Object Dim state() As Object = New Object(2) {} state(0) = MyBase.SaveControlState() state(1) = _MailServer Return state End Function |
注意:LoadControlState方法以一个对象作为参数—这个对象是以前在SaveControlState中返回的一个对象。在这个方法中,我重新分配了成员变量—通过把该参数转换为一个对象数组,然后获得每个下标的值。与以前一样,我使用数组的0下标来调用基类的LoadControlState方法。
Protected Overrides Sub LoadControlState( _ ByVal savedState As Object) If savedState IsNot Nothing Then Dim state() As Object = CType(savedState, Object()) MyBase.LoadControlState(state(0)) _MailServer = CType(state(1), String) End If End Sub |
借助于这些方法来存储数据,在页面开发者关掉ViewState时,控件就不会出现前面那些麻烦。你可能对ControlState的存储位置感到惊讶;它对应于另一个生成到HTML页面中的隐藏的文本框。就象在ViewState情况下数据被存储在__ViewState隐藏文本框中类似,ASP.NET 2.0使用__ControlState隐藏文本框来存储ControlState数据。
遗憾的是,微软没有向开发者提供内在地使用ControlState的能力—就象在ViewState情况下那样。所以,为了ControlState使用,你需要注册你的控件。你可以重载控件的OnInit事件并且调用Page对象的RegisterRequiresControlState方法。
Protected Overrides Sub OnInit(ByVal e As System.EventArgs) MyBase.OnInit(e) If Page IsNot Nothing Then Page.RegisterRequiresControlState(Me) End If End Sub |
现在,你可以使用ControlState来存储你认为足够重要的数据—如果不把它存储起来,那么你的控件可能生成一些无用的内容。
记住,你在设计时刻对属性的修改将被硬编码到该控件的ASPX声明中,从而在相邻的再次回寄之间自动地存储。然而,如果表单上的一个行为改变了一个控件的属性,那么,这将会激活状态管理机制的使用。如果不把该属性存储在一个状态中,那么,在下一次回寄时它将恢复到“硬编码”状态。
现在,总的来看,我们应该把与外观相关的属性存储在ViewState中,而把与行为相关的属性存储在ControlState中。通过这种方式,如果一个页面开发者关掉ViewState,那么你的控件尽管可能看起来样子别扭,但是仍能正确工作。
全面探讨ASP.NET 2.0中的Web控件改进技术之灵敏标签篇(三)
当你最开始在Visual Studio 2005中使用Windows表单控件或是ASP.NET Web控件时,你首先会注意到,在许多控件右上角出现一个箭头形状的小玩意儿(见图2中的示例)。点击这个箭头会弹出一个小窗口,其中包含该控件的一些属性,还有一两个链接。微软设计这些灵敏标签是为了显示你需要操作的一些属性,其最终目的是为了使该控件在一个页面或表单上能够正确工作;并且你将注意到,它们比一个普通的快捷菜单更为精致。本节中我们讨论的内容既适用于Windows表单控件也适用于ASP.NET服务器控件。
图2.EmailContact控件的灵敏标签
为了构建你自己的灵敏标签,你需要使用一个控件设计器类。事实上,你在另外其它一些问题上也会使用这个类。但是,在我详细讨论设计器类前,我想先创建一个ActionList类—这个类将定义我的灵敏标签中包含的元素。
一个ActionList类继承自System.ComponentModel.Design命名空间中的DesignerActionList类。但是,在详细讨论这个类之前,让我先来解释一下存在于灵敏标签中的四种类型的元素:category header,property mapping,action link以及information item。图2展示了我构建这个灵敏标签的目标。你能够从中看出我所指的这四种类型的元素吗?我把这个灵敏标签根据标题分为三类:“Appearance & Behavior”,“Support”和“Information”。其中,“Appearance & Behavior”分类中包含了两个属性:Mail Server和Pre-defined Display。这些实际上都是EmailContact控件本身的属性。“Support”分类包含两个激活某些类型的一个行为的链接,而“Information”分类仅用于显示信息。现在,有了这四种类型的元素,我将着手创建我的ActionList类。
我将创建一个称为EmailContactActionList的类,并且从DesignerActionList中加以派生。(你可以在本文源码列表1中看到完整的类)。我将创建一个构造器—它接收一个EmailContact实例作参数并且把它的范围扩大到一个称为ctlEmailContact的类级变量。后面,当我把代码添加到设计器类时,你将看到这个构造器的使用情况。现在,我已经建立了一个类级的对象,它包含我正在设计的Web控件的实例。
接下来,我将创建灵敏标签将显示的属性的“property mapping”。在图2中,你看到我已经在该灵敏标签中标出了两个属性:MailServer和PreDefinedDisplay。这些将分别映射到EmailContact控件的称为MailServer和PreDefinedDisplay的属性上。ActionList类中的属性映射将在get存取器中返回控件的属性,而在set存取器中设置控件的属性。然而,由于微软设计ActionList基础结构的方式决定了,你不能直接设置该控件的属性。而是,你必须使用反射机制来存取该控件的属性,然后再设置它的值。为了方便这一实现,我编写了一个称为GetControlProperty的方法,它能够返回一个PropertyDescriptor对象。这样以来,开发者就不需要再重复每一种属性映射下的反射编码。下面是一个属性映射看起来的样子。
Public Property MailServer() As String Get Return ctlEmailContact.MailServer End Get Set(ByVal value As String) GetControlProperty("MailServer"). _ SetValue(ctlEmailContact, value) End Set End Property |
接下来,我需要建立的是你在图2中所看到的链接:“About EmailContact”和一个到我自己的网站的链接。这些链接将执行我将在这个类中创建的方法。我的第一个方法名为ShowAboutBox,它显示一个Windows表单以用作我的控件的一个“关于”信息提示窗口。第二个方法称为LaunchWebSite,它执行一个对System.Diagnostics.Process.Start的调用以便在一个浏览器实例中启动我的网站。这两个方法的唯一的要求是:每一个签名都必须是一个“Sub”(在C#语言中相应于一个void函数)并且不带参数。
注意,在这个灵敏标签示例中仅显示了两个属性和两个链接,但是借助于我刚才所展示的技术,你完全可以提供你所需要的尽可以多的这些对象。然而,我建议:不要使用太多的信息来重载一个灵敏标签。记住,你仅想把信息放于此以便页面开发者立即使用,从而使得Web控件开发更具直观性。
现在,既然我已经创建了我的属性映射和行为方法,那么接下来,我将创建灵敏标签的内容。其中,DesignerActionList类提供一个称为GetSortedActionItems的重载函数。以后,一个设计器类将重载这个函数,并且它会返回一个DesignerActionItemCollection(定义于System.ComponentModel.Design命名空间)类型的对象。
这个属性重载的实现部分将创建一个新的DesignerActionItemCollection对象并且使用四个不同的类(DesignerActionHeaderItem,DesignerActionPropertyItem,DesignerActionMethodItem和DesignerActionTextItem)的实例来填充它。注意,这四个类都派生自抽象DesignerActionItem类。下面,我将同你逐个展开讨论。
Dim o_Items As DesignerActionItemCollection = _ New DesignerActionItemCollection |
o_Items.Add(New DesignerActionHeaderItem( _ "Appearance & Behavior")) |
为每一种分类创建准确的标题是相当重要的,这不仅是因为它作为在灵敏标签中作为该分类的头部相应的显示文本这一用途。早些时候,我创建了两个分别称为MailServer和PreDefinedDisplay的属性映射;现在我想把它们添加到灵敏标签中。为此,我将创建DesignerActionPropertyItem类的实例并且把它们添加到集合中。
o_Items.Add(New DesignerActionPropertyItem( _ "MailServer", "Mail Server", _ "Appearance & Behavior")) |
注意,该构造器接收三个参数:属性名,将出现在灵敏标签上的文本信息,以及相应类型的准确标题(在DesignerActionHeaderItem的实例中定义)。
接下来,我想以相同的方式把行为链接添加到灵敏标签。注意,也仅仅是在此时,我们使用了DesignerActionMethodItem类的实例。
o_Items.Add(New DesignerActionMethodItem( _ Me, "ShowAboutBox", "About EmailContact2", _ "Support", "Displays the about box.", True)) |
在此,构造器接收方法名,链接说明,类型文本以及一个用作链接的提示信息的描述等共四个参数。其中,第四个参数决定这个链接是否还出现在属性浏览器的底部。
最后,我将把信息项添加到灵敏标签中—这是使用DesignerActionTextItem类来实现的。
o_Items.Add(New DesignerActionTextItem( _ "ID: " & ctlEmailContact.ID, "Information")) |
在此,构造器仅接收要显示的文本和该文本要放置的类型。
列表1(见源码文件)中的最终代码展示了我要添加到这个集合中的所有项。当该方法完成时,它简单地返回这个集合。在后面一篇中,我们将讨论控件设计器类的问题。
全面探讨ASP.NET 2.0中的Web控件改进技术之控件设计与模板设计篇(四)
一、 控件设计器
控件设计器类派生自System.Web.UI.Design.WebControls.CompositeControlDesigner。该类通过控件类中声明的一个属性绑定到控件上:
DesignerAttribute(GetType(EmailContactDesigner))
在Web表单设计器中,它能够把控件的所有外观和行为特征呈现在用户面前。当页面开发者把一个Web控件拖动到Web表单上时,页面开发者可以通过各种交互方式取得控件的各种属性。这些属性将影响到页面开发者所看到的内容—不只是影响到控件本身,还包括一些更微妙的幕后元素(例如灵敏标签)。
实际上,控件设计器能够在设计时刻生成一种与运行时刻不同的输出结果。在有些情况下,一个控件在运行时刻可能没有任何可视化生成,但是却要求在设计时刻实现一些显示,以便更容易地操作它。这种情况的一个示例就是,ASP.NET 2.0中所提供的声明性数据源。这些控件提供了数据存储和缓冲功能,但是却没有可视化生成。然而,在设计时刻,它们表现为一个带有一些描述性文本的灰色窗口—出现在web表单设计界面上。另一个关于不同生成的例子是,有些控件不生成任何内容—除非它们被绑定到数据源。例如,当GridView(在1.1版本中是DataGrid) 带有数据时,它看上去十分正常;但当不存在要显示的数据行时,看上去什么东西也没有(或只显示为空的头部)。当你把一个这样的控件拖动到Web表单上时,控件往往使用几个具有示例性质的空数据行进行显示—这是由设计器类所提供的。当Visual Studio需要决定在一个灵敏标签中显示的内容时,它存取设计器类的一个称为ActionLists的属性,并且使用它的结果来构建该灵敏标签。该ActionLists属性返回一个System.ComponentModel.Design命名空间中的DesignerActionListCollection类型的对象。在我的设计器类中,我将重载这个属性并且构建一个DesignerActionListCollection对象。我将返回的这个对象将在类范围上加以声明,并且检查ActionLists属性是否是一个“nothing”值。