TAKempis Internet Programming Home
Home Uncle Chutney's Journal Information, Tutorials
Sponsored by my good friends of many years, at iLand Internet Solutions
Start Marquee
Stop Marquee

Anatomy of a Server Control
Part 2 - Maintaining State

by Uncle Chutney

Table of Contents:
Introduction: This is the second of a series, which begins with "Part 1 - A Simple Control3." This series builds on principles outlined in my "ASP.Net Fundamentals1" article and my "The Life and Times of a Server Control2" article. If you're unfamiliar with how ASP.Net works, you should begin by reading those.

Part 13 demonstrated how a Server Control renders an HTML interface, by using the Render() method of System.Web.UI.Control. Now we're going to expand on that, and talk about Maintaining State using ViewState. For our example Control, we will duplicate System.Web.UI.HtmlControls.HtmlInputText. This fairly simple Server Control renders an HTML text box (<input type="text"...>) in the page. The text box can retain its' value between PostBacks. Other than that, it can fire one event of its own: The ServerChange Event. This Event is fired when the value of the text box is changed programmatically on the server. Other than that, it's a pretty straightforward HTML text box. It has a few other properties which are specific to itself, such as MaxLength (maximum number of characters to enter), Size (Number of rows to display in the window), and Value (the text contained in the box). And that's about it!

Requirements: This demonstration was built with the Microsoft Visual Basic.Net language, using Microsoft's Visual Studio.Net. While these examples could fairly easily be written in Notepad, and converted in order to prevent the necessity of compilation, it is recommended that you use the same tools.

About HtmlInputText: The HtmlInputText class is derived (inherited) from System.Web.UI.HtmlControls.HtmlControl class, as are all other HtmlControls. Let's begin by looking at this class, which is derived from System.Web.UI.Control:

All classes which inherit System.Web.UI.HtmlControl are referred to as "HtmlControls." These are classes which do not much more than render HTML in a page. Certain HTML properties for HtmlControls can be accessed programmatically, and they do have the ability to maintain state across PostBacks. A plain HTML object in an ASP.Net Page Template can be converted to an Html Control simply by adding a "runat=server" attribute to the tag. Let's take a look at the Properties, Methods, and Events which all HtmlControls share in common first.

HtmlControl Properties: All HtmlControls share all of the Properties of System.Web.UI.Control4, plus the following:

  • Attributes - This is a System.Web.UI.AttributeCollection of all the HTML attributes of the Control. It can be used to programmatically add, remove, change, or examine the attributes of the Control.
  • Disabled - This Boolean value indicates whether or not the Control is Disabled on the Page. If the Control is Disabled, it is still visible, but inactive.
  • Style - This is a System.Web.UI.CssStyleCollection of all the CSS styles used for this Control. It can be used to programmatically add, remove, change, or examine the CSS styles used by this Control.
  • TagName - This String returns the HTML element name ("div" for example) of the HTML interface portion of this Control.

HtmlControl Methods: All HtmlControls share all the Methods of a System.Web.UI.Control5. No additional Methods exist.

HtmlControl Events: All HtmlControls share all the Events of a System.Web.UI.Control6. No additional Events exist.

HtmlInputText also derives from an abstract (MustInherit) Base class called System.Web.UI.HtmlControls.HtmlInputControl. HtmlInputControl is derived from HtmlControl, and is the common class for all HtmlControls that render "input" types of form fields, such as "input type=text", "input type=button", "input type=file" etc. The following are the Properties, Methods, and Events which all HtmlInputControls share in common:

HtmlInputControl Properties: All HtmlInputControls share all the Properties of HtmlControl, plus the following:

  • Name - This String returns the System.Web.UI.Control.UniqueID property for this instance.
  • Type - This String indicates the "type" attribute of the form element. As this Control represents all "input type=" HTML Form elements, this property represents the value of the "type" attribute.
  • Value - This String indicates the "value" attribute of the form element. As this Control represents all "input type=" HTML Form elements, this property represents the value of the "value" attribute.

HtmlInputControl Methods: All HtmlInputControls share all the Methods of HtmlControl. No additional Methods exist.

HtmlInputControl Events: All HtmlInputControls share all the Events of HtmlControl. No additional Events exist.

Now, let's examine the Properties, Methods, and Events which the HtmlInputText class adds to these:

HtmlInputText Properties: The HtmlInputText Control shares all Properties of HtmlInputControl, plus the following:

  • MaxLength - This Integer indicates the maximum number of characters allowed to be typed into the text box.
  • Size - This Integer indicates the width of the text box, in characters.
  • Value (Overrides System.Web.UI.HtmlControls.HtmlInputControl.Value) - This String represents the text that is displayed in the text box.

HtmlInputText Methods:  The HtmlInputText Control shares all Properties of HtmlControl. No additional Methods exist.

HtmlInputText Events:  The HtmlInputText Control shares all Properties of HtmlControl, plus the following:

  • ServerChange - This Event is fired when the Value property is changed on the server. It can be used for any code which might need to be executed on the server side as a result of the Value property changing.

Maintaining State: Apart from rendering an Interface, maintaining State is the second most common type of thing that a Server Control can do. This is particularly important with ASP.Net because of the programming model of the WebForm. A WebForm posts back to itself. It can have form fields which must retain their value between PostBacks (otherwise, the user would have to keep filling them back in). Therefore, maintaining State is quite often necessary. In this example, the HtmlInputText Control renders a text box on the client-side HTML form. If the textbox resides in a page which posts back to itself, we want it to retain the same Value it had before it was posted back.

As you should recall from my first article1, HTTP is stateless, and State is emulated by passing data back and forth between the client and server. This is done using the hidden "__VIEWSTATE" field in the form. This demonstration will show how this is achieved using the ViewState class, and the IPostBackDataHandler interface.

Building DemoControls.HtmlInputText: I departed somewhat from the actual object model in this case, because I wanted to demonstrate how to maintain state in a Control. Since System.Web.UI.HtmlControls.HtmlInputText inherits System.Web.UI.HtmlControls.HtmlInputControl, and System.Web.UI.HtmlControls.HtmlInputControl already maintains the state of the Value property, it wouldn't make a very good example if I just let all that state maintenance remain encapsulated in the Base Class. So, instead, I inherited System.Web.UI.Control, and rebuilt all the underlying properties for myself.

In addition, for the sake of the clarity of this demonstration, I omitted several Base Class properties, methods, etc. From System.Web.UI.HtmlControl, I omitted the Attributes collection and the Style property.

I did leave in the one Event which the System.Web.UI.HtmlControls.HtmlInputText Control fires: The ServerChange Event7. While this demonstration is not about raising events, it is very simple to do so, and I left it in as an example.

Because this sample does somewhat more than the last, you will find quite a few inline comments in the code regarding the "how" and "why" of what is being done. The following is the complete class file for the DemoControls.HtmlInputText class:

Imports System.Web.UI
Imports System.Collections.Specialized

Public Class HtmlInputText
     Inherits Control

     ' ** The IPostBackDataHandler interface provides the ability for Controls
     ' ** to handle data posted back from the WebForm. In order to update
     ' ** the value of a Property which maintains its' state, this must be implemented.
     ' ** Note that, when implementing this Interface, you must provide the 2 methods
     ' ** required by this Interface: LoadPostData, and RaisePostDataChangedEvent
     ' ** These methods are described more fully in their comments
     Implements IPostBackDataHandler

     ' ** This is the EventHandler that is fired when the Value property changes.
     Public Event ServerChange As EventHandler

     ' ** As this is a property which can be set in the Designer,
     ' ** the Private property (field) is initialized to prevent
     ' ** data type mismatch errors that would result if the developer did not assign
     ' ** a True or False value to it (Nothing is not a Boolean value)
     Private _Disabled As Boolean = False
     Public Property Disabled() As Boolean
          Get
               Return _Disabled
          End Get
          Set(ByVal Value As Boolean)
               _Disabled = Value
          End Set
     End Property

     Public ReadOnly Property TagName() As String
          Get
               Return "input"
          End Get
     End Property

     Public ReadOnly Property Name() As String
          Get
               Return UniqueID
          End Get
     End Property

     ' ** This property emulates the Type property shared by all
     ' ** System.Web.UI.HtmlInputControl Controls. All HTML "input type=" form fields
     ' ** have a "type" attribute. In this case, the Type property can have one
     ' ** of only 2 possible values: "text" or "password". While this property allows
     ' ** The programmer to set the value as any string, the Render method makes sure that
     ' ** the value rendered will always be either "text" or "password."
     Private _Type As String = "text"
     Public Property Type() As String
          Get
               Return _Type
          End Get
          Set(ByVal Value As String)
               _Type = Value
          End Set
     End Property

     ' ** Here is how the Value property maintains state:
     ' ** It is directly linked to the ViewState in its accessor methods
     ' ** However, it is still necessary to update the ViewState on PostBack -
     ' ** See the LoadPostData Sub for details
     Public Property Value() As String
          Get
               If Not IsNothing(ViewState("Value")) Then
                    Return ViewState("Value").ToString
               End If
               Return ""
          End Get
          Set(ByVal _Value As String)
               ViewState("Value") = _Value
          End Set
     End Property

     ' ** A textbox can have a MaxLength set or not. Because of this, we initialize
     ' ** the MaxLength property to a value less than 0 (the lowest possible MaxLength)
     ' ** We use logic to find out whether to include the MaxLength attribute or not,
     ' ** and what the value should be if it is included
     Private _MaxLength As Integer = -1
     Public Property MaxLength() As Integer
          Get
               Return _MaxLength
          End Get
          Set(ByVal Value As Integer)
               If Value > -2 Then _MaxLength = Value
          End Set
     End Property

     ' ** As this is a property which can be set in the Designer,
     ' ** the Private property (field) is initialized to prevent
     ' ** data type mismatch errors that would result if the developer did not assign
     ' ** a number to it (Nothing is not an Integer)
     Private _Size As Integer = 20
     Public Property Size() As Integer
          Get
               Return _Size
          End Get
          Set(ByVal Value As Integer)
               If Value > 0 Then _Size = Value
          End Set
     End Property

     ' ** As usual, the Render Method does the real work of rendering the HTML
     Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)

          ' ** Because the developer can assign any String value to the Type property,
          ' ** We need to make sure that if it is not "password" it is "text"
          Dim strType As String = "text"

          If _Type.ToLower = "password" Then strType = "password"
          writer.AddAttribute(HtmlTextWriterAttribute.Type, strType)

          ' ** Because the Disabled property is a Boolean value, we use Boolean logic
          ' ** To either add the "disabled" attribute or not.
          If _Disabled Then
               writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "true")
          End If
          If _MaxLength > 0 Then
               writer.AddAttribute(HtmlTextWriterAttribute.Maxlength, _MaxLength.ToString)
          End If
          writer.AddAttribute(HtmlTextWriterAttribute.Name, Name.ToString)
          writer.AddAttribute(HtmlTextWriterAttribute.Size, _Size.ToString)
          writer.AddAttribute(HtmlTextWriterAttribute.Value, Value)
          writer.RenderBeginTag(HtmlTextWriterTag.Input)
          writer.RenderEndTag()
     End Sub

     ' ** This is the first of the 2 IPostBackDataHandler methods required by the interface.
     ' ** When implemented, this method fires just after the LoadViewState method2.
     ' ** This method, as you can see, simply gets the value from the postcollection
     ' ** (the collection of all posted values from the WebForm), and assigns it
     ' ** To the Value property (thus updating the ViewState, as the Value property is
     ' ** linked directly to the ViewState)
     ' ** If this method returns True, the RaisePostDataChangedEvent method fires.
     ' ** Otherwise, it does not. We use this to raise the ServerChange Event,
     ' ** if the Value property changes.
     Public Function LoadPostData(ByVal postDataKey As String, _
          ByVal postCollection As System.Collections.Specialized.NameValueCollection) _
          As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData

          If Value <> postCollection(postDataKey) Then
               Value = postCollection(postDataKey)
               Return True
          End If
          Return False
     End Function

     ' ** If the LoadPostData Function returns True, this Sub is fired.
     Public Sub RaisePostDataChangedEvent() _
          Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent

          RaiseEvent ServerChange(Me, EventArgs.Empty)
     End Sub

     End Class

Notes: As you can see, to maintain state for this control, I had to do 2 things. First, I created the Value property of the Control as a Public property with accessor methods which Get and Set the ViewState for this property. Secondly, I implemented IPostBackDataHandler, and wrote code in the LoadPostData function that updates the ViewState for this property on PostBack.

I also demonstrated raising an event from a Control, by adding the ServerChange Event for this one. To do this, I added a Public Property ServerChange, which is of the type "EventHandler" and raised the Event when the LoadPostData Sub found that the Value of the Control had changed.

There were a few other properties which I added. We can see that if a Property's state should be maintained, we need to create accessor methods to tie it to the ViewState. Also, if a Property's state is maintained via hard-coded Template values, we create a Private field, and tie the Public Property's accessor methods to that field. When adding Properties to a Control, make sure that they are initialized in order to prevent data type mismatch errors.

Once I had compiled this Control, I added it to the Toolbox in Visual Studio.Net, and dragged it onto an ASPX page (Default.aspx):

HtmlInputText in Visual Studio.Net's IDE

I set the Value property to "Test" and was now ready to add my CodeBehind:

Public Class _Default
     Inherits System.Web.UI.Page

     Protected WithEvents Text1 As DemoControls.HtmlInputText

     #Region " Web Form Designer Generated Code "

          'This call is required by the Web Form Designer.
          <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

          End Sub

          Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) _
               Handles MyBase.Init
               'CODEGEN: This method call is required by the Web Form Designer
               'Do not modify it using the code editor.
               InitializeComponent()
          End Sub

     #End Region

     Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
          Handles MyBase.Load
          'Put user code to initialize the page here
     End Sub

     Private Sub Text1_ServerChange(ByVal sender As Object, ByVal e As System.EventArgs) _
          Handles Text1.ServerChange
          FindControl("Form1").Controls.Add(New LiteralControl("<p>Text has changed</p>"))
     End Sub
End Class

Note that I added no additional code to the page, other than the Event Handler for the ServerChange Event. Now I was ready to test it in a browser:

HtmlInputtext In Action (Browser Views)

The top part represents the way the page looked initially. I typed in the "ing" and submitted the form, which resulted in the portion seen in the bottom, displaying the Event Handler's message.

Conclusions: By building an HtmlInputText Control, we have demonstrated how a Server Control maintains state, by mapping properties to ViewState, and implementing IPostBackDataHandler to update the state of the Control during PostBack.

In addition, we have provided a simple demonstration of a Server Control which raises an Event.

 
Footnotes:
1. See "ASP.Net Fundamentals"
2. See "The Life and Times of a Server Control"
3. See "Anatomy of a Server Control Part 1: A Simple Control"
4. See "The Life and Times of a Server Control" 'Control Properties' section
5. See "The Life and Times of a Server Control" 'Control Methods' section
6. See "The Life and Times of a Server Control" 'Control Events' section
7. The ServerChange Event is fired whenever the Value property of the text box changes
Home Uncle Chutney's Journal Information, Tutorials
TAKempis Internet Programming Home
© 1997-2007 by TAKempis - All Rights Reserved