Server Control Properties Example

This example shows how to create a control named Book that persists simple properties and properties that have subproperties.

A simple property is a property whose type is a string or a type that maps easily to a string. A simple property is persisted as an attribute on the control's opening tag without any work on your part. Properties of type String and primitive value types in the .NET Framework class library such as Boolean, Int16, Int32, and Enum are simple properties. You can add code to store a simple property in the ViewState dictionary for state management across postbacks.

A property is referred to as complex when the property type is a class that itself has properties, which are referred to as subproperties. For example, the type of the Font property of WebControl is the FontInfo class that itself has properties such as Bold and Name. Bold and Name are subproperties of the Font property of WebControl. The ASP.NET page framework can persist subproperties on a control's opening tag using hyphenated syntax (for example, Font-Bold="true"), but subproperties are more readable in the page when persisted within the control's tags (for example, <font Bold="true">).

To enable a visual designer to persist subproperties as children of the control, you must apply several design-time attributes to the property and its type; the default persistence is hyphenated attributes on the control's tag. In addition, a property that has subproperties also needs custom state management to use view state, as described in the "Code Discussion" section later in this topic.

The Book control defined in the example is a control that could be used in a Web page to display information about a book in a catalog. The Book control defines the following properties:

  • Author, a property with subproperties whose type is the custom type Author. The Author type has its own properties such as FirstName and LastName, which are subproperties of the Author property.

  • BookType, a simple property whose type is the custom enumeration BookType. The BookType enumeration has values such as Fiction and NonFiction.

  • CurrencySymbol, a simple property whose type is the built-in String type.

  • Price, a simple property whose type is the built-in Decimal type.

  • Title, a simple property whose type is the built-in String type.

The BookType, CurrencySymbol, Price, and Title properties are all simple properties, and therefore do not need any special attributes for page persistence. The page framework persists these properties by default as attributes on the control's tag, as in the following example:

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

The Author property and the properties of the Author class need design-time attributes to enable persistence within the control's tags, as shown in the following example:

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

The Book control stores its simple properties in the ViewState dictionary. However, the Book control has to implement custom state management for the Author property to manage the state of the property across postbacks.

A production-quality implementation of the Book control could define properties for other book-related data such as the publisher and publication date. In addition, the Author property could be replaced by a collection property. For information about implementing a collection property, see Web Control Collection Property Example.

Note

A page developer can disable view state for a page or for individual controls in the page. If your control needs to maintain critical state across postbacks for its internal functioning, you can use the control state mechanism defined in ASP.NET 2.0. Control state is described in Control State vs. View State Example.

Code Listing for the Book Control

' 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();
        }

    }
}

Code Discussion

The DesignerSerializationVisibilityAttribute and PersistenceModeAttribute applied to the Author property of the Book control are described in Web Control Collection Property Example. These attributes are needed to serialize and persist the properties of the Author class.

The view state mechanism refers to the technique that ASP.NET uses for maintaining state across postbacks. The mechanism serializes the state of a page and its control tree into string representations at the end of page processing and deserializes the string on postback. By default, the page sends the strings to the browser as hidden fields. For more information, see ASP.NET State Management Overview.

To manage the state of a simple property, you define it as a read/write property that is stored in the control's ViewState property. The Book control defines its simple properties (BookType, CurrencySymbol, Price, and Title) this way. The state of properties that you store in the ViewState property is managed for you without any work on your part.

To manage a property that has subproperties, you can define the property as read-only and write code to manage the state of the object. To do so, you override the following methods:

The ViewState property's type, StateBag, is a dictionary with built-in state management. The StateBag class implements the IStateManager interface, which defines the TrackViewState, SaveViewState and LoadViewState methods. The StateBag class implements these methods to start tracking changes to control properties after initialization, save the items that have been modified at the end of the page request, and load saved state into items on postback. StateBag tracks items by marking an item as modified if the item is set after the OnInit method is executed for a page request. For example, if any code in the page that runs after page initialization sets the Title property of the Book control, a change is made to ViewState["Title"]. As a consequence, the value stored under the "Title"key in ViewState is marked as modified. For more information, see ASP.NET Page Life Cycle Overview.

The Book control defines the Author property as a read-only property and implements custom state management as follows:

  • In the TrackViewState method, the Book control first saves the initial Author property to a string and then starts state tracking by invoking the TrackViewState method of the base class.

  • In the SaveViewState method, the Book control determines whether the Author property has changed from its initial value. If the property has changed, Book saves the Author property in the ViewState dictionary using the key "Author". The Book control then invokes the SaveViewState method of the base class. Because state tracking is on, the Author object saved in ViewState is automatically marked as modified and saved as part of the view state of the base class.

  • In LoadViewState, the Book control first invokes the base class's LoadViewState method. This call automatically restores the ViewState dictionary. The Book control then determines whether the ViewState dictionary has an item stored under "Author". If so, the control loads the view state value into the Author property.

The Author type (defined in the code listing that follows) has a custom type converter so that an Author instance can be stored in view state. The type converter converts an Author instance to a string and vice versa. Defining a type converter allows the subproperties of Author to be set in a visual designer. The custom type converter is described in Type Converter Example. The types that you can store in view state are limited by the LosFormatter class that ASP.NET uses for view state serialization. The types that are most efficiently serialized are String; primitive value types in the .NET Framework class library such as Boolean, Int16, Int32, Enum, Pair, Triplet, Array, ArrayList, and Hashtable; and any types that contain any of these primitive types. In addition, you can also store in view state custom types that have type converters defined for them, such as Pair, Triplet, Array, ArrayList, and Hashtable. When defining view state serialization for your control, you must convert your control data to one of these types. If you store types in view state that are not compatible with the view state serialization mechanism, your control might compile but will generate an error at run time. Finally, serializable types (that is, types that implement the ISerializable interface or are marked with SerializableAttribute) can be stored in view state but serialization for these types is significantly slower than for primitive types.

The state object contributed by a control for serialization is the control's view state. The ViewState property of a control is only one piece of a control's view state—it is the piece that automatically participates in the view state mechanism without any work on your part. The Control class implements the logic for saving and loading the modified items in the ViewState dictionary in its SaveViewState and LoadViewState methods. The other part or parts of view state are additional objects that you (and your control's base class) save in view state by overriding the SaveViewState method. When you override the SaveViewState and LoadViewState methods, you must call the corresponding methods of the base class.

Code Listing for the Author Class

Applying the NotifyParentPropertyAttribute to the FirstName, LastName, and MiddleName properties and setting the attribute's constructor argument to true causes a visual designer to propagate and serialize changes for these properties into their parent property (an Author instance).

' 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);
        }
    }
}

Code Listing for the BookType Enumeration

' 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
    }
}

Test Page for the Book Control

The following example shows an .aspx page that uses the Book control.

<%@ Page Language="C#" Debug="true" Trace="true"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "https://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="https://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" NavigateUrl="BookTest.aspx" 
        Runat="server">
        Reload Page</asp:HyperLink>
    </form>
  </body>
</html>

Building and Using the Example

Compile the classes in this example with the AuthorConverter class listed in Type Converter Example.

For information about compiling and using the custom control examples, see Building the Custom Server Control Examples.

See Also

Reference

IsPrimitive

Other Resources

Developing Custom ASP.NET Server Controls