服务器控件属性示例
更新:2007 年 11 月
此示例演示如何创建一个名为 Book 的控件,该控件保留简单属性和具有子属性的属性。
简单属性一个是类型为字符串或易于映射到字符串的类型的属性。简单属性 (Property) 在控件的开始标记上自行保留为属性 (Attribute)。String 类型的属性和 .NET Framework 类库中的基元值类型(如 Boolean、Int16、Int32 和 Enum)均为简单属性。可以通过添加代码将简单属性存储在 ViewState 字典中,以在回发间进行状态管理。
如果一个属性的类型是本身具有属性(称为子属性)的类,则该属性就称为复杂属性。例如,WebControl 的 Font 属性的类型是本身具有属性(如 Bold 和 Name)的 FontInfo 类。Bold 和 Name 是 WebControl 的 Font 属性的子属性。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 控件集合属性示例。
说明: |
---|
网页开发人员可以禁用某页或某页中各个控件的视图状态。如果为了在内部正常使用,控件需要在回发间保持关键状态,则可以使用 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(" ")
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(" ");
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 属性的 DesignerSerializationVisibilityAttribute 和 PersistenceModeAttribute 在 Web 控件集合属性示例中有介绍。需要使用这些属性 (Attribute) 对 Author 类的属性 (Property) 进行序列化和保持。
视图状态机制是指 ASP.NET 用于在回发间保持状态的技术。该机制在页处理结束时将页及其控件树的状态序列化为字符串表示形式,并在回发时对该字符串进行反序列化。默认情况下,页将这些字符串作为隐藏字段发送到浏览器。有关更多信息,请参见 ASP.NET 状态管理概述。
若要管理简单属性的状态,请将其定义为一个存储在控件的 ViewState 属性中的读/写属性。Book 控件通过此方法来定义其简单属性(BookType、CurrencySymbol、Price 和 Title)。这样就可以自行对存储在 ViewState 属性中的属性的状态进行管理。
若要管理具有子属性的属性,可以将该属性定义为只读属性,然后编写代码以管理对象的状态。为此,需要重写以下方法:
TrackViewState,发送信号使控件在初始化之后开始跟踪属性更改。
SaveViewState,在页请求结束时保存属性(如果已更改)。
LoadViewState,在回发时将保存的状态加载到属性中。
说明: 除了这里讨论的方法之外,还有其他方法可对复杂属性的状态进行管理。可以扩展演练:开发和使用自定义服务器控件中使用的方法,在每次属性更改时修改视图状态。还可以管理属性类中的状态,如自定义属性状态管理示例中所示。
ViewState 属性的类型 StateBag 是一个具有内置状态管理功能的字典。StateBag 类实现 IStateManager 接口,而该接口定义 TrackViewState、SaveViewState 和 LoadViewState 方法。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 类库中的基元值类型(如 Boolean、Int16、Int32、Enum、Pair、Triplet、Array、ArrayList 和 Hashtable)以及包含任何这些基元类型的所有类型。此外,还可以在视图状态中存储为其定义了类型转换器的自定义类型,如 Pair、Triplet、Array、ArrayList 和 Hashtable。为控件定义视图状态序列化时,必须将控件数据转换为这些类型之一。如果在视图状态中存储与视图状态序列化机制不兼容的类型,虽然可以编译该控件,但在运行时将生成一个错误。最后,可以在视图状态中存储可序列化的类型(即,实现 ISerializable 接口的类型或具有 SerializableAttribute 标记的类型),但对这些类型进行序列化要比对基元类型进行序列化速度慢很多。
一个控件为序列化提供的状态对象就是该控件的视图状态。一个控件的 ViewState 属性只是控件视图状态的一部分,即自行参与视图状态机制的那一部分。Control 类在其 SaveViewState 和 LoadViewState 方法中实现保存和加载 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 类一起编译。
有关编译和使用自定义控件示例的信息,请参见生成自定义服务器控件示例。