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.vbOption Strict OnImports 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>") _
    > _
    PublicClass Book
        Inherits WebControl
        Private authorValue As Author
        Private initialAuthorString AsString

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The name of the author."), _
        DesignerSerializationVisibility( _
            DesignerSerializationVisibility.Content), _
        PersistenceMode(PersistenceMode.InnerProperty) _
        > _
        PublicOverridableReadOnlyProperty Author() As Author
            GetIf (authorValue IsNothing) Then
                    authorValue = New Author()
                EndIfReturn authorValue
            EndGetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(BookType.NotDefined), _
        Description("Fiction or Not") _
        > _
        PublicOverridableProperty BookType() As BookType
            GetDim t AsObject = ViewState("BookType")
                If t IsNothingThen t = BookType.NotDefined
                ReturnCType(t, BookType)
            EndGetSet(ByVal value As BookType)
                ViewState("BookType") = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The symbol for the currency."), _
        Localizable(True) _
        > _
        PublicOverridableProperty CurrencySymbol() AsStringGetDim s AsString = CStr(ViewState("CurrencySymbol"))
                If s IsNothingThen s = String.Empty
                Return s
            EndGetSet(ByVal value AsString)
                ViewState("CurrencySymbol") = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue("0.00"), _
        Description("The price of the book."), _
        Localizable(True) _
        > _
        PublicOverridableProperty Price() AsDecimalGetDim p AsObject = ViewState("Price")
                If p IsNothingThen p = Decimal.Zero
                ReturnCType(p, Decimal)
            EndGetSet(ByVal value AsDecimal)
                ViewState("Price") = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The title of the book."), _
        Localizable(True) _
        > _
        PublicOverridableProperty Title() AsStringGetDim s AsString = CStr(ViewState("Title"))
                If s IsNothingThen s = String.Empty
                Return s
            EndGetSet(ByVal value AsString)
                ViewState("Title") = value
            EndSetEndPropertyProtectedOverridesSub 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()
        EndSubProtectedOverridesSub LoadViewState( _
            ByVal savedState AsObject)
            MyBase.LoadViewState(savedState)
            Dim auth As Author = CType(ViewState("Author"), Author)
            If auth IsNotNothingThen
                authorValue = auth
            EndIfEndSubProtectedOverridesFunction SaveViewState() AsObjectIf authorValue IsNotNothingThenDim currentAuthorString AsString = _
                    authorValue.ToString()
                IfNot _
                    (currentAuthorString.Equals(initialAuthorString)) Then
                    ViewState("Author") = authorValue
                EndIfEndIfReturnMyBase.SaveViewState()
        EndFunctionProtectedOverridesSub TrackViewState()
            If authorValue IsNotNothingThen
                initialAuthorString = authorValue.ToString()
            EndIfMyBase.TrackViewState()
        EndSubEndClassEndNamespace
// Book.csusing 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>")
    ]
    publicclass 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),
        ]
        publicvirtual Author Author
        {
            get
            {
                if (authorValue == null)
                {
                    authorValue = new Author();
                }
                return authorValue;
            }

        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(BookType.NotDefined),
        Description("Fiction or Not"),
        ]
        publicvirtual 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)
        ]
        publicvirtualstring 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)
        ]
        publicvirtual 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)
        ]
        publicvirtualstring Title
        {
            get
            {
                string s = (string)ViewState["Title"];
                return (s == null) ? String.Empty : s;
            }
            set
            {
                ViewState["Title"] = value;
            }
        }

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

        protectedoverridevoid LoadViewState(object savedState)
        {
            base.LoadViewState(savedState);

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

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

            returnbase.SaveViewState();
        }

        protectedoverridevoid 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.vbOption Strict OnImports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Globalization
Imports System.Web.UI

Namespace Samples.AspNet.VB.Controls
    < _
    TypeConverter(GetType(AuthorConverter)) _
    > _
    PublicClass Author
        Dim firstNameValue AsStringDim lastNameValue AsStringDim middleNameValue AsStringPublicSubNew()
            Me.New(String.Empty, String.Empty, String.Empty)
        EndSubPublicSubNew(ByVal firstname AsString, _
            ByVal lastname AsString)
            Me.New(firstname, String.Empty, lastname)
        EndSubPublicSubNew(ByVal firstname AsString, _
            ByVal middlename AsString, ByVal lastname AsString)
            firstNameValue = firstname
            middleNameValue = middlename
            lastNameValue = lastname
        EndSub

        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("First name of author."), _
        NotifyParentProperty(True) _
        > _
        PublicOverridableProperty FirstName() AsStringGetReturn firstNameValue
            EndGetSet(ByVal value AsString)
                firstNameValue = value
            EndSetEndProperty


        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Last name of author."), _
        NotifyParentProperty(True) _
        > _
        PublicOverridableProperty LastName() AsStringGetReturn lastNameValue
            EndGetSet(ByVal value AsString)
                lastNameValue = value
            EndSetEndProperty

        < _
        Category("Behavior"), _
        DefaultValue(""), _
        Description("Middle name of author."), _
            NotifyParentProperty(True) _
        > _
        PublicOverridableProperty MiddleName() AsStringGetReturn middleNameValue
            EndGetSet(ByVal value AsString)
                middleNameValue = value
            EndSetEndPropertyPublicOverridesFunction ToString() AsStringReturn ToString(CultureInfo.InvariantCulture)
        EndFunctionPublicOverloadsFunction ToString( _
            ByVal culture As CultureInfo) AsStringReturn TypeDescriptor.GetConverter( _
                Me.GetType()).ConvertToString(Nothing, culture, Me)
        EndFunctionEndClassEndNamespace
// Author.csusing System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Web.UI;

namespace Samples.AspNet.CS.Controls
{
    [
    TypeConverter(typeof(AuthorConverter))
    ]
    publicclass Author
    {
        privatestring firstnameValue;
        privatestring lastnameValue;
        privatestring 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),
        ]
        publicvirtual String FirstName
        {
            get
            {
                return firstnameValue;
            }
            set
            {
                firstnameValue = value;
            }
        }

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

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

        publicoverridestring ToString()
        {
            return ToString(CultureInfo.InvariantCulture);
        }

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

Code Listing for the BookType Enumeration

' BookType.vbOption Strict OnImports System

Namespace Samples.AspNet.VB.Controls
    PublicEnum BookType
        NotDefined = 0
        Fiction = 1
        NonFiction = 2
    EndEnumEndNamespace
// BookType.csusing System;

namespace Samples.AspNet.CS.Controls
{
    publicenum 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""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>

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

Concepts

Value Types in the Common Type System

Reference

IsPrimitive

Other Resources

Developing Custom ASP.NET Server Controls