Skip navigation

Inside ASP.NET Control Properties

Design and Implement Control Properties

CoreCoder

LANGUAGES: C# | VB.NET

ASP.NET VERSIONS: 1.x | 2.0

 

Inside ASP.NET Control Properties

Design and Implement Control Properties

 

By Dino Esposito

 

There s a tricky question that s been coming up regularly during conference talks and classes in the past few years. It refers to a feature of ASP.NET that has not been touched in version 2.0: the implementation of control properties. Sooner or later, all developers get to write custom ASP.NET controls, and, when it comes to this, properties are one of the first features they must come to grips with. Most of the sample code available out there through articles and books shows the same implementation of control properties based on viewstate. Many smart developers, new to control development, are therefore led to think that viewstate and control properties are an indissoluble and powerful duo.

 

As the developer makes progress on the subject, he or she soon realizes that there s more to learn and figure out. Dissecting the source code of more sophisticated examples, as well as using .NET Reflector to snoop into the decompiled code of ASP.NET controls, one can see that there are at least two ways of implementing the storage of control properties: viewstate and local variables.

 

You don t have to be an ASP.NET controls super-expert to note that these two solutions seem to be diametrically opposed. Nevertheless, being adopted in the same ASP.NET framework, both solutions should be valid and effective in their own scenarios.

 

In this article, I ll take the plunge into the internals of ASP.NET control properties and discuss how control properties manage their values in conjunction with the viewstate management API of ASP.NET pages.

 

The ABCs of Control Properties

A property is a public member of a class and basically represents a typed value associated with each instance of the class. The value of the property attribute can be read and written. The property sports special tools to read and write its own value. These tools are usually referred to as accessor (get) and mutator (set). Although the property is publicly exposed as an attribute, it is internally implemented through a pair of get/set methods. If either miss, the property is read-only or write-only. The property is simply a public name that top-level code uses to read and write a value stored elsewhere. A property is a public name used to abstract a value. Properties are not the only option you have to store information in a class. In addition to properties, you can make use of fields.

 

Fields and properties are almost indistinguishable from a client perspective, but are declared differently within a class. Whereas fields are simply public variables that a class makes visible to callers, properties use ad hoc blocks of code (mutator and accessor) to control how values are set or returned. Unlike properties, fields provide no abstraction. The field coincides with the value and exposes the value directly to the caller code.

 

In C# you declare properties and fields using nearly the same syntax:

 

// This is a field

public string Text;

// This is a property

public string Text

{

  get {

      // retrieve and return the value of the property

  }

  set {

      // set the new value for the property

  }

}

 

In Visual Basic .NET, properties require a special syntax that clearly distinguishes them from fields:

 

' This is a field

Public Text As String

' This is a property

Public Property Text As String

  Get

      ' retrieve and return the value of the property

  End Get

  Set (ByVal value As String)

      ' set the new value for the property

  End Set

End Property

 

The get accessor of the property is used whenever the code reads or assigns the value of the property, as in the code below:

 

Response.Write(ctl.Text);

 

The property mutator is used whenever the code assigns a new value to the property:

 

ctl.Text = "Hello, world";

 

In this case, the set mutator is used and the assigned value is accessed through the value parameter in Visual Basic .NET and the value keyword in C#.

 

As far as control properties are concerned, the storage of the internal value is a key point. When you implement a property, you must know already where the property is going to store any value it is expected to return or cache.

 

Choosing a Storage Medium

ASP.NET controls live in the context of ASP.NET pages. ASP.NET pages, in turn, are accessed through successive requests in a purely stateless fashion. Each request causes the ASP.NET runtime to create a new processing environment where any values related to any previous request, including control properties, are irreversibly lost.

 

So, should control properties be persistent? That s a good question. The role of a property is that of letting developers set attributes on the control so that the control can render out appropriate markup. For this to happen, properties don t have to be persistent. Period.

 

However, the ASP.NET platform makes a point of implementing automatic state maintenance through the viewstate. The viewstate is a container where all controls in the page can store their own key values to have them back when the same page posts back. At the end of each request, any values that a control has placed in the viewstate are persisted and restored when the same page instance posts back after a user action. The ASP.NET runtime serializes the contents of the viewstate to a hidden field when the request is going to end and deserializes it when the request comes back.

 

What s the overall advantage you get out of the viewstate? Simply put, a control property that uses the viewstate as its storage medium is automatically persisted and retrieves its value across successive requests. Put another way, using the viewstate for control properties makes the control work statefully over a stateless protocol like HTTP. Persistent properties are not a strict requirement to make ASP.NET controls work. However, having persistent properties greatly simplifies the development of Web applications using controls. Built-in ASP.NET controls make extensive use of the viewstate to persist the value of their own properties. Using public fields is not considered a good practice. The code in Figure 1 shows the typical viewstate-based implementation of a control property.

 

// Text string property

public string Text

{

  get {

      object o = ViewState["Text"];

      if (o == null)

         return String.Empty;

      return (string) o;

  }

  set {

      ViewState["Text"] = value;

  }

}

' Text string property

Public Property Text As String

  Get

      Dim o As Object = ViewState("Text")

      If o Is Nothing Then

          Return String.Empty;

      End If

      Return DirectCast(o, String);

  End Get

  Set (ByVal value As String)

      ViewState("Text") = value

  End Set

End Property

Figure 1: Typical viewstate-based implementation of a control property.

 

By convention, each property takes a viewstate slot named after the property name. The get accessor first attempts to get a value out of the given viewstate entry. If this value is null that is, the property has not been initialized the default value of the property is returned. If the property holds a value, that value is cast to the right property type and returned. Note that giving properties their default value in the control class constructor is not considered a good practice. If you do so, in fact, you end up having code that relates to a property in two distinct places: the property definition and the class constructor.

 

If Visual Basic .NET is your favorite programming language, make sure you use DirectCast instead of CType to convert the viewstate object to the property type. CType succeeds as long as there is a valid conversion defined between the expression and the type. DirectCast, instead, requires that the run-time type of the object be the same as the specified type. If this is the case, the performance of DirectCast is better. Put another way, either DirectCast fails and throws an exception or it runs faster. In this particular scenario, you are the only one who can put a value of the wrong type in the viewstate entry. It is safe to assume that the types match. In addition, the use of DirectCast ensures that developers working with your control get an exception if they store values of incompatible types in the property (assuming that the compiler doesn t catch this at compile time).

 

Using Other Storage Media

In case of (real) need, you can replace the viewstate with other ASP.NET intrinsic objects, such as Session or Cache. Note that using Session or Cache is not a way to optimize the behavior of the application (quite the reverse, I d say), but should be done in case you want to expose as classic control properties information that is already stored in Session or Cache. Consider also that the viewstate object is scoped to the current page. This means that you can handle multiple instances of the same page and multiple instances of the same control in the same page and each page and control will have its own copy of the viewstate. No conflicts are possible between properties as long as they belong to distinct controls. The same is not guaranteed if a property is persisted to Session or Cache. If Session is used, the scope is the session and you should guarantee that a property slot has a name that is unique to the page and the control. It s even worse if Cache is used; in this case, the scope is the entire application.

 

Properties of a Complex Type

A property persisted to the viewstate undergoes a serialization step. The system component responsible for viewstate serialization has been changed in ASP.NET 2.0. The LosFormatter class used by ASP.NET 1.x has been replaced by the ObjectStateFormatter class in ASP.NET 2.0. The key factor differentiating these classes is the serialization algorithm employed and ultimately the final size of the generated text. The ObjectStateFormatter class uses an improved algorithm and saves about 50 percent of the viewstate size compared to ASP.NET 1.x.

 

Both classes operate a clear distinction between simple and complex types. Simple types are strings, numbers, enumerations, dates, colors, booleans, bytes, Unit, arrays, and pairs or triplets of the simple types. Any classes are considered complex types.

 

For simple types, the viewstate formatter employs a specific, optimized algorithm that minimizes the amount of information stored. For complex types, two options are considered. If a type converter exists for the class, it is used to convert the contents of the class to a string. Otherwise, the BinaryFormatter is invoked to serialize the class instance to an array of bytes. Using the BinaryFormatter class is a last resort and should be avoided whenever possible. The BinaryFormatter class can serialize any .NET class that is flagged with the [Serializable] attribute, but is not the fastest option and it generates a relatively large output. The point is that BinaryFormatter is designed to pursue framework run-time object serialization and to persist and restore the whole state of an object, including assembly information. To persist a control property, you don t need more than a bunch of values. For this reason, a type converter is preferable.

 

What is a type converter? A type converter is simply a class that takes care of converting an object of a type to an object of another type, including a string type. It is nothing really different from a sort of simplified and made-to-measure text serializer. You create a type converter by deriving a class from TypeConverter and overriding a few methods: CanConvertFrom, CanConvertTo, ConvertFrom, and ConvertTo.

 

Imagine now that your control needs a property that is a collection of custom types. For example, list controls such as DropDownList feature the Items property whose type is ListItemCollection. The signature of the collection class is shown here:

 

public sealed class ListItemCollection :

 IList, IStateManager

{

  :

}

Public NotInheritable Class ListItemCollection :

 Implements IList, IStateManager

  :

End Class

 

As you can see, the class is a collection and implements the IStateManager interface. Let s take a look at the implementation of the Items property in a typical list control such as the DropDownList (see Figure 2).

 

private ListItemCollection items;

public virtual ListItemCollection Items

{

     get

     {

           if (items == null)

           {

                 items = new ListItemCollection();

                 if (base.IsTrackingViewState)

                       items.TrackViewState();

           }

           return items;

     }

}

Private items As ListItemCollection

Public Overridable ReadOnly Property Items As ListItemCollection

     Get

            If (Me.items Is Nothing) Then

                 Me.items = New ListItemCollection

                 If MyBase.IsTrackingViewState Then

                       Me.items.TrackViewState

                 End If

           End If

           Return Me.items

     End Get

End Property

Figure 2: Implementing the Items property in a typical list control.

 

The property is read-only and is implemented through a local variable named items. At first glance you may form two erroneous ideas that the property is not persisted to the viewstate and that the property value doesn t survive a page postback because the property is saved to a local variable.

 

In spite of appearances, the Items property is persisted to the viewstate. However, it is serialized through a custom mechanism and doesn t pass through the ViewState container. Would writing the code shown in Figure 3 be a mistake?

 

public ListItemCollection Items

{

  get {

      object o = ViewState["Items"];

      if (o == null)

         return new ListItemCollection;

      return (ListItemCollection) o;

  }

}

Public Property Items As ListItemCollection

  Get

      Dim o As Object = ViewState("Items")

      If o Is Nothing Then

         Return New ListItemCollection;

      End If

      Return DirectCast(o, ListItemCollection);

  End Get

End Property

Figure 3: Right or wrong?

 

The code in Figure 3 is correct and works just fine as long as the ListItemCollection class is serializable. The point is, how efficient is this code? Not very, I d say, because the ListItemCollection class (and any other custom class you use in similar situations) will be serialized through the binary formatter. Should you use a type converter instead? It would be much better, but requires you to write, document, and maintain yet another class. The best practice consists of embedding the viewstate serialization code in the control itself.

 

Serializing Custom Types

The methods of the IStateManager interface represent the contract by means of which a class can be serialized to, and deserialized from, the viewstate. Figure 4 details the methods of the interface. By implementing IStateManager, a class determines how its contents should be saved and restored to and from the viewstate. A class that implements IStateManager doesn t have to be serializable and is not even processed by ObjectStateFormatter.

 

Member

Description

IsTrackingViewState

Indicates whether the class is currently tracking the viewstate for changes. This property is typically implemented returning an internal boolean variable.

LoadViewState

Populates a new instance of the class with the data stored in the object it receives.

SaveViewState

Saves the current state of the class to an object, usually a pair, a triplet, or an array. This object will be then passed to LoadViewState on the next postback.

TrackViewState

Begins tracking the viewstate for changes. This method is typically implemented by setting the boolean set behind the IsTrackingViewState property.

Figure 4: Members of the IStateManager interface.

 

However, IStateManager simply states that the class can save to the viewstate. What takes care of writing bytes to the viewstate? It is the control itself through a couple of overridden methods, SaveViewState and LoadViewState.

 

Any custom control that has properties of complex types must override SaveViewState and LoadViewState to add the bytes of the property to its own copy of the viewstate. Figure 5 shows the typical code of SaveViewState and LoadViewState for a custom control.

 

protected override object SaveViewState()

{

     object state = base.SaveViewState();

     object extra = Items.SaveViewState();

     return new Pair(state, extra);

}

protected override void LoadViewState(object savedState)

{

     if (savedState != null)

     {

           Pair p = (Pair) savedState;

           base.LoadViewState(p.First);

           Items.LoadViewState(p.Second);

     }

}

Protected Overrides Function SaveViewState() As Object

     Dim state As Object = MyBase.SaveViewState

     Dim extra As Object = Me.Items.SaveViewState

     Return New Pair(state, extra)

End Function

Protected Overrides Sub LoadViewState(ByVal savedState As Object)

     If Not savedState Is Nothing Then

           Dim p As Pair = DirectCast(savedState, Pair)

           MyBase.LoadViewState(p.First)

           Me.Items.LoadViewState(p.Second)

     End If

End Sub

Figure 5: ViewState management on a custom control.

 

Conclusion

Control properties need a storage medium and typically use the ViewState container for this purpose. Using the ViewState base property is efficient only if the type of the control is one that the ASP.NET infrastructure knows how to handle. If your properties are of a complex type such as a collection of custom classes the best that you can do is persisting the property through a local variable and instructing the class to serialize to the viewstate. In addition, you can override a couple of methods on the control to add the bytes of the class to the control viewstate.

 

Dino Esposito is a Solid Quality Learning mentor and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish