服务器控件属性示例

更新:2007 年 11 月

此示例演示如何创建一个名为 Book 的控件,该控件保留简单属性和具有子属性的属性。

简单属性一个是类型为字符串或易于映射到字符串的类型的属性。简单属性 (Property) 在控件的开始标记上自行保留为属性 (Attribute)。String 类型的属性和 .NET Framework 类库中的基元值类型(如 BooleanInt16Int32Enum)均为简单属性。可以通过添加代码将简单属性存储在 ViewState 字典中,以在回发间进行状态管理。

如果一个属性的类型是本身具有属性(称为子属性)的类,则该属性就称为复杂属性。例如,WebControlFont 属性的类型是本身具有属性(如 BoldName)的 FontInfo 类。BoldNameWebControlFont 属性的子属性。ASP.NET 页框架可通过使用带有连字符的语法(例如 Font-Bold="true")在控件的开始标记上保存子属性,但如果在控件的标记(例如 <font Bold="true">)中保存子属性,则子属性在页中的可读性更强。

若要使可视化设计器将子属性保存为控件的子级,则必须将一些设计时属性 (Attribute) 应用于该属性 (Property) 及其类型;默认情况下在控件的标记上保存为带有连字符的属性 (Attribute)。此外,具有子属性的属性还需要自定义状态管理以使用视图状态,本主题后面的“代码讨论”部分将对此进行描述。

此示例中定义的 Book 控件可在网页中使用,以在目录中显示有关书的信息。Book 控件定义以下属性:

  • Author,一个类型为自定义类型 Author 且具有子属性的属性。Author 类型具有自己的属性,如 FirstName 和 LastName,这些属性是 Author 属性的子属性。

  • BookType,一个类型为自定义枚举 BookType 的简单属性。BookType 枚举包括诸如 Fiction 和 NonFiction 之类的值。

  • CurrencySymbol,一个类型为内置 String 类型的简单属性。

  • Price,一个类型为内置 Decimal 类型的简单属性。

  • Title,一个类型为内置 String 类型的简单属性。

BookType、CurrencySymbol、Price 和 Title 属性都是简单属性 (Property),因此在保持页时不需要任何特殊属性 (Attribute)。默认情况下,页框架将这些属性 (Property) 在控件的标记上保持为属性 (Attribute),如下面的示例所示:

<aspSample:Book Title="Wingtip Toys Stories" 
  CurrencySymbol="$" 
  Price="16" 
  BookType="Fiction">
</aspSample:Book>

Author 属性 (Property) 和 Author 类的属性 (Property) 需要设计时属性 (Attribute) 以在控件的标记中进行保持,如下面的示例所示:

<aspSample:Book >
  <Author FirstName="Judy" LastName="Lew" />
</aspSample:Book>

Book 控件将其简单属性存储在 ViewState 字典中。但是,Book 控件必须为 Author 属性实现自定义状态管理以管理回发间的属性状态。

实际应用中的 Book 控件实现可以定义其他与书相关的数据的属性,如出版商和出版日期。此外,还可用一个集合属性替换 Author 属性。有关实现集合属性的信息,请参见 Web 控件集合属性示例

4s70936s.alert_note(zh-cn,VS.90).gif说明:

网页开发人员可以禁用某页或某页中各个控件的视图状态。如果为了在内部正常使用,控件需要在回发间保持关键状态,则可以使用 ASP.NET 2.0 中定义的控件状态机制。控件状态在控件状态与视图状态示例中有介绍。

Book 控件的代码清单

' Book.vb
Option Strict On
Imports System
Imports System.ComponentModel
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace Samples.AspNet.VB.Controls
    < _
    AspNetHostingPermission(SecurityAction.Demand, _
        Level:=AspNetHostingPermissionLevel.Minimal), _
    AspNetHostingPermission(SecurityAction.InheritanceDemand, _
        Level:=AspNetHostingPermissionLevel.Minimal), _
    DefaultProperty("Title"), _
    ToolboxData("<{0}:Book runat=""server""> </{0}:Book>") _
    > _
    Public Class Book
        Inherits WebControl
        Private authorValue As Author
        Private initialAuthorString As String

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The name of the author."), _
        DesignerSerializationVisibility( _
            DesignerSerializationVisibility.Content), _
        PersistenceMode(PersistenceMode.InnerProperty) _
        > _
        Public Overridable ReadOnly Property Author() As Author
            Get
                If (authorValue Is Nothing) Then
                    authorValue = New Author()
                End If
                Return authorValue
            End Get
        End Property

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(BookType.NotDefined), _
        Description("Fiction or Not") _
        > _
        Public Overridable Property BookType() As BookType
            Get
                Dim t As Object = ViewState("BookType")
                If t Is Nothing Then t = BookType.NotDefined
                Return CType(t, BookType)
            End Get
            Set(ByVal value As BookType)
                ViewState("BookType") = value
            End Set
        End Property

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The symbol for the currency."), _
        Localizable(True) _
        > _
        Public Overridable Property CurrencySymbol() As String
            Get
                Dim s As String = CStr(ViewState("CurrencySymbol"))
                If s Is Nothing Then s = String.Empty
                Return s
            End Get
            Set(ByVal value As String)
                ViewState("CurrencySymbol") = value
            End Set
        End Property

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue("0.00"), _
        Description("The price of the book."), _
        Localizable(True) _
        > _
        Public Overridable Property Price() As Decimal
            Get
                Dim p As Object = ViewState("Price")
                If p Is Nothing Then p = Decimal.Zero
                Return CType(p, Decimal)
            End Get
            Set(ByVal value As Decimal)
                ViewState("Price") = value
            End Set
        End Property

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The title of the book."), _
        Localizable(True) _
        > _
        Public Overridable Property Title() As String
            Get
                Dim s As String = CStr(ViewState("Title"))
                If s Is Nothing Then s = String.Empty
                Return s
            End Get
            Set(ByVal value As String)
                ViewState("Title") = value
            End Set
        End Property

        Protected Overrides Sub RenderContents( _
            ByVal writer As HtmlTextWriter)
            writer.RenderBeginTag(HtmlTextWriterTag.Table)

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            writer.WriteEncodedText(Title)
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            writer.WriteEncodedText(Author.ToString())
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            writer.WriteEncodedText(BookType.ToString())
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            writer.Write(CurrencySymbol)
            writer.Write("&nbsp")
            writer.Write(String.Format("{0:F2}", Price))
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderEndTag()
        End Sub

        Protected Overrides Sub LoadViewState( _
            ByVal savedState As Object)
            MyBase.LoadViewState(savedState)
            Dim auth As Author = CType(ViewState("Author"), Author)
            If auth IsNot Nothing Then
                authorValue = auth
            End If
        End Sub

        Protected Overrides Function SaveViewState() As Object
            If authorValue IsNot Nothing Then
                Dim currentAuthorString As String = _
                    authorValue.ToString()
                If Not _
                    (currentAuthorString.Equals(initialAuthorString)) Then
                    ViewState("Author") = authorValue
                End If
            End If
            Return MyBase.SaveViewState()
        End Function

        Protected Overrides Sub TrackViewState()
            If authorValue IsNot Nothing Then
                initialAuthorString = authorValue.ToString()
            End If
            MyBase.TrackViewState()
        End Sub

    End Class
End Namespace
// Book.cs
using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Samples.AspNet.CS.Controls
{
    [
    AspNetHostingPermission(SecurityAction.Demand,
        Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, 
        Level=AspNetHostingPermissionLevel.Minimal),
    DefaultProperty("Title"),
    ToolboxData("<{0}:Book runat=\"server\"> </{0}:Book>")
    ]
    public class Book : WebControl
    {
        private Author authorValue;
        private String initialAuthorString;

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description("The name of the author."),
        DesignerSerializationVisibility(
            DesignerSerializationVisibility.Content),
        PersistenceMode(PersistenceMode.InnerProperty),
        ]
        public virtual Author Author
        {
            get
            {
                if (authorValue == null)
                {
                    authorValue = new Author();
                }
                return authorValue;
            }

        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(BookType.NotDefined),
        Description("Fiction or Not"),
        ]
        public virtual BookType BookType
        {
            get
            {
                object t = ViewState["BookType"];
                return (t == null) ? BookType.NotDefined : (BookType)t;
            }
            set
            {
                ViewState["BookType"] = value;
            }
        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description("The symbol for the currency."),
        Localizable(true)
        ]
        public virtual string CurrencySymbol
        {
            get
            {
                string s = (string)ViewState["CurrencySymbol"];
                return (s == null) ? String.Empty : s;
            }
            set
            {
                ViewState["CurrencySymbol"] = value;
            }
        }


        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue("0.00"),
        Description("The price of the book."),
        Localizable(true)
        ]
        public virtual Decimal Price
        {
            get
            {
                object price = ViewState["Price"];
                return (price  == null) ? Decimal.Zero : (Decimal)price;
            }
            set
            {
                ViewState["Price"] = value;
            }
        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description("The title of the book."),
        Localizable(true)
        ]
        public virtual string Title
        {
            get
            {
                string s = (string)ViewState["Title"];
                return (s == null) ? String.Empty : s;
            }
            set
            {
                ViewState["Title"] = value;
            }
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            writer.RenderBeginTag(HtmlTextWriterTag.Table);

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.WriteEncodedText(Title);
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.WriteEncodedText(Author.ToString());
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.WriteEncodedText(BookType.ToString());
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.Write(CurrencySymbol);
            writer.Write("&nbsp;");
            writer.Write(String.Format("{0:F2}", Price));
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderEndTag();
        }

        protected override void LoadViewState(object savedState)
        {
            base.LoadViewState(savedState);

            Author auth = (Author)ViewState["Author"];
            if (auth != null)
            {
                authorValue = auth;
            }
        }

        protected override object SaveViewState()
        {
            if (authorValue != null)
            {
                String currentAuthorString = authorValue.ToString();
                if (!(currentAuthorString.Equals(initialAuthorString)))
                {
                    ViewState["Author"] = authorValue;
                }
            }

            return base.SaveViewState();
        }

        protected override void TrackViewState()
        {
            if (authorValue != null)
            {
                initialAuthorString = authorValue.ToString();
            }
            base.TrackViewState();
        }

    }
}

代码讨论

应用于 Book 控件的 Author 属性的 DesignerSerializationVisibilityAttributePersistenceModeAttributeWeb 控件集合属性示例中有介绍。需要使用这些属性 (Attribute) 对 Author 类的属性 (Property) 进行序列化和保持。

视图状态机制是指 ASP.NET 用于在回发间保持状态的技术。该机制在页处理结束时将页及其控件树的状态序列化为字符串表示形式,并在回发时对该字符串进行反序列化。默认情况下,页将这些字符串作为隐藏字段发送到浏览器。有关更多信息,请参见 ASP.NET 状态管理概述

若要管理简单属性的状态,请将其定义为一个存储在控件的 ViewState 属性中的读/写属性。Book 控件通过此方法来定义其简单属性(BookType、CurrencySymbol、Price 和 Title)。这样就可以自行对存储在 ViewState 属性中的属性的状态进行管理。

若要管理具有子属性的属性,可以将该属性定义为只读属性,然后编写代码以管理对象的状态。为此,需要重写以下方法:

ViewState 属性的类型 StateBag 是一个具有内置状态管理功能的字典。StateBag 类实现 IStateManager 接口,而该接口定义 TrackViewStateSaveViewStateLoadViewState 方法。StateBag 类可实现这些方法,以在初始化之后开始跟踪对控件属性所做的更改,在页请求结束时保存已修改的项,以及在回发时将保存的状态加载到相应的项中。如果在对页请求执行了 OnInit 方法之后设置了某个项,则 StateBag 将通过将该项标记为已修改来进行跟踪。例如,如果在页初始化之后运行的页中的任何代码设置了 Book 控件的 Title 属性,则 ViewState["Title"] 将被更改。因此,ViewState 中“Title”键下存储的值将被标记为已修改。有关更多信息,请参见 ASP.NET 页生命周期概述

Book 控件将 Author 属性定义为只读属性,并实现自定义状态管理,如下所示:

  • 在 TrackViewState 方法中,Book 控件先将初始 Author 属性保存到字符串,然后通过调用基类的 TrackViewState 方法开始跟踪状态。

  • 在 SaveViewState 方法中,Book 控件确定 Author 属性是否已从初始值发生更改。如果该属性已更改,则 Book 使用“Author”键将 Author 属性保存到 ViewState 字典中。然后 Book 控件调用基类的 SaveViewState 方法。由于启动了状态跟踪,因此保存在 ViewState 中的 Author 对象将自动标记为已修改,并被保存为基类视图状态的一部分。

  • 在 LoadViewState 中,Book 控件首先调用基类的 LoadViewState 方法。此调用自动还原 ViewState 字典。然后 Book 控件确定 ViewState 字典的“Author”下是否存储了某个项。如果有,该控件就会将相应的视图状态值加载到 Author 属性中。

Author 类型(在下面的代码清单中定义)有一个自定义类型转换器,因此可在视图状态中存储一个 Author 实例。该类型转换器既可以将 Author 实例转换为字符串,也可以进行反向转换。通过定义类型转换器,可以在可视化设计器中设置 Author 的子属性。自定义类型转换器在类型转换器示例中有介绍。可在视图状态中存储的类型由 LosFormatter 类限定,该类由 ASP.NET 用于进行视图状态序列化。序列化效率最高的类型是 String、.NET Framework 类库中的基元值类型(如 BooleanInt16Int32EnumPairTripletArrayArrayListHashtable)以及包含任何这些基元类型的所有类型。此外,还可以在视图状态中存储为其定义了类型转换器的自定义类型,如 PairTripletArrayArrayListHashtable。为控件定义视图状态序列化时,必须将控件数据转换为这些类型之一。如果在视图状态中存储与视图状态序列化机制不兼容的类型,虽然可以编译该控件,但在运行时将生成一个错误。最后,可以在视图状态中存储可序列化的类型(即,实现 ISerializable 接口的类型或具有 SerializableAttribute 标记的类型),但对这些类型进行序列化要比对基元类型进行序列化速度慢很多。

一个控件为序列化提供的状态对象就是该控件的视图状态。一个控件的 ViewState 属性只是控件视图状态的一部分,即自行参与视图状态机制的那一部分。Control 类在其 SaveViewStateLoadViewState 方法中实现保存和加载 ViewState 字典中的已修改项的逻辑。视图状态的其他部分是您(以及您的控件的基类)通过重写 SaveViewState 方法在视图状态中保存的附加对象。在重写 SaveViewState 和 LoadViewState 方法时,必须调用基类的相应方法。

Author 类的代码清单

NotifyParentPropertyAttribute 应用于 FirstName、LastName 和 MiddleName 属性 (Property) 并将属性 (Attribute) 的构造函数参数设置为 true,会使可视化设计器将这些属性 (Property) 的更改传播和序列化到其父属性(一个 Author 实例)。

' Author.vb
Option Strict On
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Globalization
Imports System.Web.UI

Namespace Samples.AspNet.VB.Controls
    < _
    TypeConverter(GetType(AuthorConverter)) _
    > _
    Public Class Author
        Dim firstNameValue As String
        Dim lastNameValue As String
        Dim middleNameValue As String

        Public Sub New()
            Me.New(String.Empty, String.Empty, String.Empty)
        End Sub

        Public Sub New(ByVal firstname As String, _
            ByVal lastname As String)
            Me.New(firstname, String.Empty, lastname)
        End Sub

        Public Sub New(ByVal firstname As String, _
            ByVal middlename As String, ByVal lastname As String)
            firstNameValue = firstname
            middleNameValue = middlename
            lastNameValue = lastname
        End Sub

        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("First name of author."), _
        NotifyParentProperty(True) _
        > _
        Public Overridable Property FirstName() As String
            Get
                Return firstNameValue
            End Get
            Set(ByVal value As String)
                firstNameValue = value
            End Set
        End Property


        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Last name of author."), _
        NotifyParentProperty(True) _
        > _
        Public Overridable Property LastName() As String
            Get
                Return lastNameValue
            End Get
            Set(ByVal value As String)
                lastNameValue = value
            End Set
        End Property

        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Middle name of author."), _
            NotifyParentProperty(True) _
        > _
        Public Overridable Property MiddleName() As String
            Get
                Return middleNameValue
            End Get
            Set(ByVal value As String)
                middleNameValue = value
            End Set
        End Property

        Public Overrides Function ToString() As String
            Return ToString(CultureInfo.InvariantCulture)
        End Function

        Public Overloads Function ToString( _
            ByVal culture As CultureInfo) As String
            Return TypeDescriptor.GetConverter( _
                Me.GetType()).ConvertToString(Nothing, culture, Me)
        End Function
    End Class
End Namespace
// Author.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Web.UI;

namespace Samples.AspNet.CS.Controls
{
    [
    TypeConverter(typeof(AuthorConverter))
    ]
    public class Author
    {
        private string firstnameValue;
        private string lastnameValue;
        private string middlenameValue;

        public Author()
            :
            this(String.Empty, String.Empty, String.Empty)
        {
        }

        public Author(string firstname, string lastname)
            :
            this(firstname, String.Empty, lastname)
        {
        }

        public Author(string firstname, 
                    string middlename, string lastname)
        {
            firstnameValue = firstname;
            middlenameValue = middlename;
            lastnameValue = lastname;
        }

        [
        Category("Behavior"),
        DefaultValue(""),
        Description("First name of author."),
        NotifyParentProperty(true),
        ]
        public virtual String FirstName
        {
            get
            {
                return firstnameValue;
            }
            set
            {
                firstnameValue = value;
            }
        }

        [
        Category("Behavior"),
        DefaultValue(""),
        Description("Last name of author."),
        NotifyParentProperty(true)
        ]
        public virtual String LastName
        {
            get
            {
                return lastnameValue;
            }
            set
            {
                lastnameValue = value;
            }
        }

        [
        Category("Behavior"),
        DefaultValue(""),
        Description("Middle name of author."),
        NotifyParentProperty(true)
        ]
        public virtual String MiddleName
        {
            get
            {
                return middlenameValue;
            }
            set
            {
                middlenameValue = value;
            }
        }

        public override string ToString()
        {
            return ToString(CultureInfo.InvariantCulture);
        }

        public string ToString(CultureInfo culture)
        {
            return TypeDescriptor.GetConverter(
                GetType()).ConvertToString(null, culture, this);
        }
    }
}

BookType 枚举的代码清单

' BookType.vb
Option Strict On
Imports System

Namespace Samples.AspNet.VB.Controls
    Public Enum BookType
        NotDefined = 0
        Fiction = 1
        NonFiction = 2
    End Enum
End Namespace
// BookType.cs
using System;

namespace Samples.AspNet.CS.Controls
{
    public enum BookType
    {
        NotDefined = 0,
        Fiction = 1,
        NonFiction = 2
    }
}

Book 控件的测试页

下面的示例演示一个使用 Book 控件的 .aspx 页。

<%@ Page Language="C#" Debug="true" Trace="true"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
  void Button_Click(object sender, EventArgs e)
  {
    Book1.Author.FirstName = "Bob";
    Book1.Author.LastName = "Kelly";
    Book1.Title = "Contoso Stories";
    Book1.Price = 39.95M;
    Button1.Visible = false;
  }  
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>
      Book test page
    </title>
  </head>
  <body>
    <form id="Form1" runat="server">
      <aspSample:Book ID="Book1" Runat="server"  
        Title="Tailspin Toys Stories" CurrencySymbol="$" 
        BackColor="#FFE0C0" Font-Names="Tahoma" 
        Price="16" BookType="Fiction">
        <Author FirstName="Judy" LastName="Lew" />
      </aspSample:Book>
      <br />
      <asp:Button ID="Button1" OnClick="Button_Click" 
        Runat="server" Text="Change" />
      <asp:Button ID="Button2" Runat="server" Text="Refresh" />
      <br />
      <br />
      <asp:HyperLink ID="Hyperlink1" href="BookTest.aspx" 
        Runat="server">
        Reload Page</asp:HyperLink>
    </form>
  </body>
</html>

生成和使用示例

将此示例中的类与类型转换器示例中列出的 AuthorConverter 类一起编译。

有关编译和使用自定义控件示例的信息,请参见生成自定义服务器控件示例

请参见

概念

通用类型系统中的值类型

参考

IsPrimitive

其他资源

开发自定义 ASP.NET 服务器控件