[转]ASP.NET自定义控件复杂属性声明持久性浅析

在自定义控件的开发过程中,我们经常要给控件添加一些复杂类型的属性。利用声明持久性(Declarative Persistence)可使得页面开发人员能够让页面开发人员在ASP.NET页面中,声明性地设置这些复杂属性值,而无需编写任何C#或者VB.NET代码。

参见下面的例子:

  • GridView的DataKeyNames属性,其数据类型是string[]:

    <asp:GridView ID="GridView1" runat="server" DataKeyNames="ID, Title, Author">
    </asp:GridView>
  • GridView的RowStyle属性,其数据类型是System.Web.UI.WebControls.TableItemStyle:
    <asp:GridView ID="GridView1" runat="server">
        <RowStyle BackColor="Red" ForeColor="Black"/>
    </asp:GridView>
  • GridView的Columns属性,其数据类型是:System.Web.UI.WebControls.DataControlFieldCollection
    <asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1">
        <Columns>
             <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
             <asp:BoundField DataField="ID" HeaderText="ID" SortExpression="ID" />
        </Columns>
    </asp:GridView>
  • GridView的PagerTemplate属性, 其数据类型是System.Web.UI.ITemplate:
    <asp:GridView ID="GridView1" runat="server">
        <PagerTemplate>
             <div>
                 <span>Pager Template</span>
             </div>
        </PagerTemplate>
    </asp:GridView>

那如何才能实现在ASPX中声明性地设置这些复杂属性哪?

下面我将逐一讲述这些属性背后的故事,本文的重点不在于如何维护这些属性的状态,而是如何由ASPX Markup到复杂属性的构建。

一、由ASPX Markup 到C# 或者VB.NET class

1.ASP.NET管道


对于ASPX页面的请求,ASP.NET管道的目标是找到一个完全代表被请求页面的托管类,如果该类不存在,则即时创建并且编译。

ASP.NET页面由标记(Markup)和代码(Codebehind)文件组成,整个页面编译过程包含两个主要步骤:首先将ASPX Markup装换成一个适合ASP.NET类层次结构的C#或者VB.NET临时类,我们可以从ASP.NET的临时文件夹中找到包含该类的文件;其次将该临时类编译成一个程序集,最后将得到的程序集装入托管该应用程序的AppDomain中。

对于特定的请求,HttpApplication对象从config文件中获取处理对象以服务该请求,通过下面的代码片断我们可以看出.aspx资源与PageHandlerFactory相关联。 在ASP.NET管道中PageHandlerFactory对象将创建当前请求页面的实例,随后将请求交由该页面处理。

<httpHandlers>
     <add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True"/>
     <add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True"/>
</httpHandlers>

2.PageHandlerFactory

PageHandlerFactory负责找到包含请求页面类的程序集,如果该程序集还没有被创建,则即时动态创建。请求页面类是通过解析ASPX资源的Markup代码创建的,并且存放在ASP.NET的临时文件夹%AppData%"Local"Temp"Temporary ASP.NET Files中。

3.ControlBuilder

而ControlBuilder类就是负责将ASPX Markup声明解析成为ASP.NET Server控件,通常页面上的每个控件都有一个默认的 ControlBuilder类相关联。在ASPX页面解析过程中,ASP.NET 页框架首先会生成与页面控件树对应的 ControlBuilder 对象树,然后 ControlBuilder 树用于生成页代码并创建控件树。

ControlBuilder 定义了如何解析控件标记中的内容的,我们可以通过自定义ControlBuilder类来重写此默认行为。

在页面解析过程中,ControlBuilder将会检查ASP.NET Server控件是否标示了ParseChildren(true) Attribute。如果被标示则该控件内部嵌套的子节点将被解析为控件的子属性,否则该节点会被解析为ASP.NET Server控件,并添加到原控件的Controls集合,关于该Attribute见下一节。

二、相关的Attributes

1.ParseChildrenAttribute

ParseChildrenAttribute应用于自定义控件类上,该Attribute将会告诉ASPX页面解析器如何解析自定义控件内部的嵌套节点。

下表详细的描述了ParseChildrenAttribute的用法:


Attribute Usage


描述


ParseChildren(true)


嵌套的子节点必须对应着当前控件的属性,如果找不到对应属性将会产生一个解析错误。另外在当前控件的Tag内部也不允许任何文字节点。

例子:Repeater 以及其他数据绑定控件。


ParseChildrenAttribute(true, "PropertyName")


当前控件必须包含一个Public的属性,属性名等同于参数PropertyName。该属性应该是一个集合类的数据类型。

而嵌套的字节点必须对应着该属性的子Element.

例子:HtmlTable, HtmlTableRow控件。


ParseChildrenAttribute(false)

ParseChildrenAttribute(false, "PropertyName")

ParseChildrenAttribute is not applied to the control.


嵌套的子节点必须是ASP.NET 服务器控件。页面解析器会根据该节点创建一个子控件,然后在当前控件上调用IParserAccessor.AddParsedSubObject方法,该方法的默认实现是将解析到的子控件添加到当前控件的Controls集合。

任何Literal文字节点将被创建为LiteralControl的示例。

例子:Panel控件。


ParseChildrenAttribute (Type childControlType)


嵌套的子节点必须是指定的ASP.NET 服务器控件类型。

例子:MultiView控件。

WebControl类上已经被ParseChildrenAttribute(True)标示了,所以每个直接或者间接从WebControl派生的控件都会默认支持内部属性声明持久性。

在下面的例子中RowStyle节点将被解析为GridView控件的子属性:

<asp:GridView ID="GridView1" runat="server">
    <RowStyle BackColor="Red" ForeColor="Black"/>
</asp:GridView>

ASP.NET Server控件可以通过ControlBuilderAttribute来指定特定的ControlBuilder,来修改上述的解析逻辑。

2.  PersistChildrenAttribute

PersistChildrenAttribute应用于自定义控件类上,是一个DesignTime的Attribute。用于指定是否将自定义控件内部的嵌套节点解析为子控件,True将意味着解析该节点为控件。

WebControl类上已经被PersistChildrenAttribute (False)标示了,而Panel类则被PersistChildrenAttribute (True)标示。

示例:

[ParseChildren(false), PersistChildren(true)]
public class MyControl : WebControl
{ }

在设计时添加一个Button控件到MyControl中:

<cc1:MyControl2 ID="MyControl21" runat="server" BorderStyle="Dotted" Height="56px" Width="349px">
      <asp:Button ID="Button2" runat="server" Text="Button" />
</cc1:MyControl2> 

这个时候用户可以在VS IDE的Design View中选中子Button控件,而如果MyControl被PersistChildrenAttribute (False)标示的话,子控件Button不能被选中。

3.  PersistenceModeAttribute

PersistenceModeAttribute应用在ASP.NET 服务器控件属性上,是一个DesignTime的Attribute,用于指定用如何在设计时将ASP.NETServer控件属性(Property)保存到ASP.NET 页面

或者说在 ASPX Mrakup中以何种方式声明该属性。

PersistenceMode.InnerProperty则指定属性在 ASP.NET 服务器控件中保持为嵌套标记。

三、再看例子

1. GridView的DataKeyNames属性,其数据类型是string[]:

<asp:GridView ID="GridView1" runat="server" DataKeyNames="ID, Title, Author">
</asp:GridView> 

默认情况下ASP.NET页面解析引擎会将ASPX Markup中赋予DataKeyNames属性的值直接设置到该属性上,由于该属性的数据类型为string[],直接设置将会失败。

那GridView做了些什么哪,见下面的代码片断:

[TypeConverter(typeof(StringArrayConverter))]
public virtual string[] DataKeyNames
{get; set;} 

也就是说通过给属性添加特定的TypeConvertor来实现属性的设置,在上面的例子中StringArrayConvter将string装化为string[]后,设置到DataKeyNames属性上。

2. GridView的RowStyle属性,其数据类型是TableRowStyle:

<asp:GridView ID="GridView1" runat="server">
    <RowStyle BackColor="Red" ForeColor="Black"/>
</asp:GridView>

由于GridView控件已经标示了ParseChildrenAttribute(True),所以其内部嵌套的节点将被解析为自身的属性。

另外RowStyle属性也添加了PersistenceModeAttribute来指定如何生成ASPX Markup代码。

[PersistenceMode(PersistenceMode.InnerProperty)]
public TableItemStyle RowStyle
{get;} 

3. GridView的Columns属性,其数据类型是DataFieldCollection:

<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1">
    <Columns>
         <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
         <asp:BoundField DataField="ID" HeaderText="ID" SortExpression="ID" />
    </Columns>
</asp:GridView> 

ASP.NET页面是如何来解析上面的一段代码哪?首先Columns节点将被解析为GridView的属性,可是Columns内部的子节点是如何被解析并添加到Columns集合中去的哪?

另外在VS IDE Source View中准备添加Columns属性的子节点代码的时候,为什么VS IDE会帮我们列出所有可实例化的子类型?

让我们来看一下Columns属性的数据类型:DataControlFieldCollection.

ASP.NET页面解析器在解析控件属性的时候,如果发现该属性实现了IList接口,解析完成该属性的子节点后,会去调用该属性的Add方法来添加这些子Element。

另外在设计时也会通过该属性数据类型的Item子属性获取可以实例化的子Element类型,并且通过智能提示表现出来。

也就是说这些集合类属性必须实现IList接口才可以实现属性的声明持久化。

看一下上面示例的Markup代码对应的C#代码,注意代码行16,17。 

 1[System.Diagnostics.DebuggerNonUserCodeAttribute()]
 2private global::System.Web.UI.WebControls.BoundField @__BuildControl__control23()
 3{
 4      global::System.Web.UI.WebControls.BoundField @__ctrl;
 5      @__ctrl = new global::System.Web.UI.WebControls.BoundField();
 6      @__ctrl.DataField = "ID";
 7      @__ctrl.HeaderText = "ID";
 8      @__ctrl.SortExpression = "ID";
 9      return @__ctrl;
10}
11
12[System.Diagnostics.DebuggerNonUserCodeAttribute()]
13private void @__BuildControl__control22(System.Web.UI.WebControls.DataControlFieldCollection @__ctrl)
14{
15      global::System.Web.UI.WebControls.BoundField @__ctrl1;
16      @__ctrl1 = this[email protected]__BuildControl__control23();
17      @__ctrl.Add(@__ctrl1);
18}
19
20[System.Diagnostics.DebuggerNonUserCodeAttribute()]
21private global::System.Web.UI.WebControls.GridView @__BuildControlGridView1()
22{
23      global::System.Web.UI.WebControls.GridView @__ctrl;
24      @__ctrl = new global::System.Web.UI.WebControls.GridView();
25      this.GridView1 = @__ctrl;
26      @__ctrl.ApplyStyleSheetSkin(this);
27      @__ctrl.ID = "GridView1";
28
29      this[email protected]__BuildControl__control22(@__ctrl.Columns);
30
31      return @__ctrl;
32}

参照上面说的几点我们就可以写出自定义的集合类属性的声明持久化。

当然了这只是实现了ASPX到CS,我们还需要给集合类以及子Element添加关于状态管理的代码(实现IStateManager接口)才可以在实际项目中使用,这一点我就不再赘述了。

4. GridView的PagerTemplate属性,其数据类型是ITemplate:

其实ITemplate节点内部就是一个控件集合,TemplateBuilder负责将该Tag构建成为一个控件组。

在运行时我们通过ITemplate接口的InstantiateIn方法将一个实例化的Template放到指定的Container中去。

全文完。

转载地址:http://www.cnblogs.com/tedzhao/archive/2008/05/10/1190772.html

时间: 2024-08-28 16:29:17

[转]ASP.NET自定义控件复杂属性声明持久性浅析的相关文章

Asp.Net 自定义控件实现图片的上传,浏览,删除功能

4月的时候公司比较闲,就想着自己做点东西,其实主要是为了更加熟悉.Net,毕竟接触的时间不长,趁着有时间想提高提高.不过当我做到图片上传这个功能的时候,就有些停滞不前了,连续写了两天也达不到自己想要的标准.后来公司来活,然后就没有然后了,然而做事总不能半途而废吧~时隔一个多月,趁着这个周末,我再次拾起了这个项目,而首要工作就是攻破这个图片上传控件. 下面说说我的标准是什么子的吧~ 1.最多可以上传三张图片,超过三张有提示. 2.点击图片小图,有图片放大功能,再次点击,图片恢复原来尺寸. 3.在图

Asp.Net 自定义控件实现图片的上传,浏览,Delete功能

4月的时候公司比较闲,就想着自己做点东西,其实主要是为了更加熟悉.Net,毕竟接触的时间不长,趁着有时间想提高提高.不过当我做到图片上传这个功能的时候,就有些停滞不前了,连续写了两天也达不到自己想要的标准.后来公司来活,然后就没有然后了,然而做事总不能半途而废吧~时隔一个多月,趁着这个周末,我再次拾起了这个项目,而首要工作就是攻破这个图片上传控件. 下面说说我的标准是什么子的吧~ 1.最多可以上传三张图片,超过三张有提示. 2.点击图片小图,有图片放大功能,再次点击,图片恢复原来尺寸. 3.在图

Asp.net 自定义控件开发相关的几种嵌入资源解决方案

前提: 如下将要介绍的几种类型资源都要在其属性页窗口, 将 <生成操作> 属性, 设置为[嵌入的资源], 如图: ? 给自定义控件添加自定义图标的几种方案 方法一: 直接在自定义控件项目中添加一个 *.bmp格式的图标文件, 并将其命名 与主控件文件相同, 扩展名为 .bmp, 比如主控件文件名为: CustomButton.cs, 则图标文件命名为: CustomButton.bmp . 编译项目. 然后在工具箱中添加此控件就可以看到刚刚设置的图标效果. 方法二: 图标文件名称与主控件名称不

attrs.xml中declare-styleable 详解(用于自定义控件的属性)

1. 框架定义: <declare-styleable name = "名称"> <attr name = "……" format = "……" /> </declare-styleable> 2. color:颜色值,指定这个属性必须输入的是颜色值 <attr name = "textColor" format = "color" /> 3. boolean

asp.net 自定义控件

在Visual Studio中,所有的ASP.NET 2.0控件都是自定义控件,创建自己的自定义控件一般需要完成以下三步.(1)在站点APP_Code下创建一个新类:(2)修改这个类,让它成为WebControl类(包含在System.Web.UI.WebControls命名空间)的派生类:(3)重写基类(即WebControl类)的RenderContents()方法. 下面是一个最简单的ASP.NET控件,它的功能只有一个,显示"Hellow World". using Syste

[ASP.NET]UserControl透过属性,动态加入RequiredFieldValidator

前言 原理同之前设计在Custom Control里面一样,请参考:TextBox动态加入RequiredFieldValidator与CustomValidator 只是这边是运用在User Control里面,因为User Control组合的弹性更大, 顺手写个范例给开发的team member作个参考,也希望各位前辈不吝赐教. 介绍 这边的User Control仍为之前范例上,有两个TextBox,一个为ID,一个为Name. 需求: 要有property可以存取CodeID与Code

iOS:不同属性声明方式的解析

代码: /* 属性声明方式说明: ----------------------- 1 @interface ... { id name } @end 这样声明的属性其实可以认为是private属性,因为它只能在方法里通过name引用,外部无法通过“object.name”的方式进行引用 (内部也不能通过self引用) ---------------------- 2 @interface ... @property id name @end 这样声明的属性可以认为是public属性,内部通过“s

黑马程序员——oc语言学习心得—— 属性声明和赋值

黑马程序员——oc语言学习心得—— 属性声明和赋值 -------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 1,在oc中所有类继承与终极父类Object2,声明字符变量采用N是string  *_xxx 实例变量一般以下划线开头3,在oc中方法以+ -号区分 -号开头是实例方法或对象方法  +号开头是类方法  前置用对象调用 后者用类名调用4,在xcode4以后声明@property 不用在写@snysize  自动生成get.set方法5,属性

【翻译】ASP.NET MVC 5属性路由

原文链接:http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx#why-attribute-routing 最近在学习MVC相关的东西,今天刚好到msdn上看到了这样的一片文章,感觉不错,于是决定将它翻译出来和博友们一起分享下.我第一次发表文章,有不对的地方非常欢迎指出. —— 写在前面 废话不多说了,咱们开始吧 路由是ASP.NET MVC 怎样去用一个URI去匹配一个