Custom Property State Management Example

This example shows how to define a server control property whose type implements its own state management. The BookNew control in the example defines an Author property whose type, StateManagedAuthor, performs its own state management by implementing the IStateManager interface. The StateManagedAuthor type is described in Custom Type State Management Example.

The BookNew control in this topic is similar to the Book control described in Server Control Properties Example, which shows how to define a property that has subproperties. The difference between the BookNew and Book controls is that the type of the Author property in BookNew delegates state management of the Author property to the state management methods of the property's StateManagedAuthor type. In contrast, the Book control explicitly manages the state of its Author property.

The methods used to implement state management (TrackViewState, SaveViewState, and LoadViewState) are described in the "Code Discussion" section later in this topic.

Code Listing for the BookNew Control

The following code listing of the BookNew control. State management is performed by the Author property of BookNew and the state management methods (TrackViewState, SaveViewState, and LoadViewState) of the StateManagedAuthor class.

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}:BookNew runat=""server""> </{0}:BookNew>") _
    > _
    Public Class BookNew
        Inherits WebControl
        Private authorValue As StateManagedAuthor

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The name of the author."), _
        DesignerSerializationVisibility( _
            DesignerSerializationVisibility.Content), _
        PersistenceMode(PersistenceMode.InnerProperty) _
        > _
        Public Overridable ReadOnly Property Author() _
            As StateManagedAuthor
            Get 
                If authorValue Is Nothing Then
                    authorValue = New StateManagedAuthor
                    If IsTrackingViewState Then 
                        CType(authorValue, IStateManager).TrackViewState()
                    End If 
                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 Render(ByVal writer As HtmlTextWriter)
            MyBase.AddAttributesToRender(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()
        End Sub

#Region "state management" 
        Protected Overrides Sub LoadViewState( _
            ByVal savedState As Object)
            Dim p As Pair = TryCast(savedState, Pair)
            If p IsNot Nothing Then 
                MyBase.LoadViewState(p.First)
                CType(Author, IStateManager).LoadViewState(p.Second)
                Return 
            End If 
            MyBase.LoadViewState(savedState)
        End Sub 

        Protected Overrides Function SaveViewState() As Object 
            Dim baseState As Object = MyBase.SaveViewState
            Dim thisState As Object = Nothing 

            If authorValue IsNot Nothing Then
                thisState = _
                    CType(authorValue, IStateManager).SaveViewState()
            End If 

            If thisState IsNot Nothing Then 
                Return New Pair(baseState, thisState)
            Else 
                Return baseState
            End If 
        End Function 

        Protected Overrides Sub TrackViewState()
            If authorValue IsNot Nothing Then 
                CType(Author, IStateManager).TrackViewState()
            End If 
            MyBase.TrackViewState()
        End Sub
#End Region

    End Class 
End Namespace
// BookNew.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}:BookNew runat=\"server\"> </{0}:BookNew>")
    ]
    public class BookNew : WebControl
    {
        private StateManagedAuthor authorValue;

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

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)authorValue).TrackViewState();
                    }
                }
                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 Render(HtmlTextWriter writer)
        {
            base.AddAttributesToRender(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();
        }

        #region state management
        protected override void LoadViewState(object savedState)
        {
            Pair p = savedState as Pair;
            if (p != null)
            {
                base.LoadViewState(p.First);
                ((IStateManager)Author).LoadViewState(p.Second);
                return;
            }
            base.LoadViewState(savedState);
        }

        protected override object SaveViewState()
        {
            object baseState = base.SaveViewState();
            object thisState = null;

            if (authorValue != null)
            {
                thisState = ((IStateManager)authorValue).SaveViewState();
            }

            if (thisState != null)
            {
                return new Pair(baseState, thisState);
            }
            else
            {
                return baseState;
            }

        }

        protected override void TrackViewState()
        {
            if (authorValue != null)
            {
                ((IStateManager)authorValue).TrackViewState();
            }
            base.TrackViewState();
        }
        #endregion

    }
}

Code Discussion

The BookNew control demonstrates the pattern for implementing properties whose types manage their own state by implementing the IStateManager interface. While examining the code for the BookNew control in this topic, you might find it useful to also examine the code for the StateManagedAuthor type, the type of the BookNew control's Author property. The code listing for StateManagedAuthor is in the Custom Type State Management Example topic.

BookNew defines the Author property as a read-only property, stored in a private field of type StateManagedAuthor. In the Author property accessor, if the private field corresponding to the property is null (Nothing in Visual Basic), BookNew assigns a new StateManagedAuthor object to the private field. If BookNew has started tracking state, BookNew initiates state tracking on the newly created StateManagedAuthor object by invoking the object's TrackViewState method. For more information about tracking, see Server Control Properties Example.

The Author property participates in state management by calling the IStateManager methods of the StateManagedAuthor object from its own state management methods: TrackViewState, SaveViewState, and LoadViewState.

In the overridden TrackViewState method, BookNew calls the TrackViewState method of the base class and the TrackViewState method of the StateManagedAuthor object that corresponds to the Author property.

In the overridden SaveViewState method, BookNew invokes the SaveViewState method of the base class and the SaveViewState method of the StateManagedAuthor object that corresponds to the Author property. If the Author property has state to save, the BookNew control's SaveViewState method returns a Pair object that contains the state of the base class and of the Author property. If the Author property does not have state to save, the method returns only the state returned by the SaveViewState call to the base class. Based on the number of custom properties that contribute state, you should return objects of type Pair, Triplet, or Array from SaveViewState. This enables you to retrieve the different parts of the saved state more easily in the LoadViewState method. In this case, the Pair class is used, because there are two items, the base class state and the Author state.

In the overridden LoadViewState method, BookNew implements the inverse of the operations that it implemented in the LoadViewState method. BookNew loads state into the base class and into the Author property, or, if the Author property did not have state to save, BookNew loads state into only the base class. You should always call the LoadViewState method of the base class, even if the saved state is null (Nothing in Visual Basic), because the base class might have implemented other logic in this method when it does not have state to restore.

Test Page for the BookNew Control

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

<%@ Page Language="C#"%>
<!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)
  {
    BookNew1.Author.FirstName = "Bob";
    BookNew1.Author.LastName = "Kelly";
    BookNew1.Title = "Contoso Stories";
    BookNew1.Price = 39.95M;
    Button1.Visible = false;
  }  
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>
      BookNew test page
    </title>
  </head>
  <body>
    <form id="Form1" runat="server">
      <aspSample:BookNew ID="BookNew1" Runat="server" 
        BorderStyle="Solid" BorderWidth="1px" Title="Tailspin Stories"
        CurrencySymbol="$" BackColor="#FFE0C0" Font-Names="Tahoma" 
        Price="16" BookType="Fiction">
        <Author FirstName="Judy" LastName="Lew" />
      </aspSample:BookNew>
      <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="BookNewTest.aspx" 
        Runat="server">
        Reload Page</asp:HyperLink>
    </form>
  </body>
</html>

Building and Using the Example

Compile the BookNew control with the StateManagedAuthor class and the StateManagedAuthorConverter class described in Custom Type State Management Example.

For more information about building the control and using it in a page, see Building the Custom Server Control Examples.

See Also

Concepts

Custom Type State Management Example

Server Control Properties Example

Other Resources

Developing Custom ASP.NET Server Controls