Persisting State Information
Introduction
In the
previous lessons you developed HyperLinkGroup control. The control is
working as expected but it has one limitation. It can't retain its state
information across post backs. For example, if you set the SourceFile property
programmatically to some other XML file then across post backs it won't remember
the new file name. In this lesson you will learn two ways to overcome this
limitation.
Understanding the problem
Before we go further first of all let's make the problem clear with an
example. Open the same HyperLinkGroup control that you developed in
Lesson 1. Add a new XML file to the web site and name it as Links2.xml. Key
in the following markup in the Links2.xml file.
<?xml version="1.0" encoding="utf-8" ?>
<links>
<link>
<title>ASP.NET Official Web Site</title>
<url>http://www.asp.net</url>
</link>
<link>
<title>Windows Forms Official Web Site</title>
<url>http://www.windowsforms.net</url>
</link>
<link>
<title>MSDN Library</title>
<url>http://msdn.microsoft.com</url>
</link>
</links>
The structure and nesting of Links2.xml is same as Link.xml but the data is
different.
Now go to the Page_Load event handler of the default web form and add the
following code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
HyperLinkGroup1.SourceFile = "~/links2.xml";
}
}
The above code simply sets the SourceFile property of the HyperLinkGroup
control to the new XML file.
Drag and drop a Button control on the web form and set its Text property to
"Submit". You will use this button simply to cause post backs to the web form.
Run the default web form and try hitting the Submit button. What happens? When
the page loads for the first time it correctly renders the hyperlinks as per the
new XML file (see below).

However, when you hit the Submit button the HyperLinkGroup control renders
links from the older XML file i.e. Links.xml (see below).

This proves that across post backs the HyperLinkGroup control is unable to
persist the property values or any state information in general.
Using ViewState to save state information
One solution to the above problem is to use ViewState object. You might be
aware that ASP.NET uses ViewState to save control values across post backs. The
same ViewState object is also available to your custom controls. Let's modify
the HyperLinkGroup control to use ViewState object.
The following code shows a class named HyperLinkGroupWithViewState that is
the modified version of the HyperLinkGroup class.
namespace BinaryIntellect.UI
{
public class HyperLinkGroupWithViewState : Control
{
public string SourceFile
{
get
{
return ViewState["sourcefile"].ToString();
}
set
{
ViewState["sourcefile"] = value;
}
}
private HyperLinkGroupDirection enumDir;
public HyperLinkGroupDirection Direction
{
get { return enumDir; }
set { enumDir = value; }
}
protected override void Render(HtmlTextWriter writer)
{
DataSet ds = new DataSet();
ds.ReadXml(HttpContext.Current.Server.MapPath(SourceFile));
if (enumDir == HyperLinkGroupDirection.Horizontal)
{
writer.WriteFullBeginTag("table");
writer.WriteFullBeginTag("tr");
foreach (DataRow row in ds.Tables[0].Rows)
{
writer.WriteFullBeginTag("td");
writer.WriteBeginTag("a");
writer.WriteAttribute("href", row["url"].ToString());
writer.Write(">");
writer.Write(row["title"].ToString());
writer.WriteEndTag("a");
writer.WriteEndTag("td");
}
writer.WriteEndTag("tr");
writer.WriteEndTag("table");
}
else
{
writer.WriteFullBeginTag("table");
foreach (DataRow row in ds.Tables[0].Rows)
{
writer.WriteFullBeginTag("tr");
writer.WriteFullBeginTag("td");
writer.WriteBeginTag("a");
writer.WriteAttribute("href", row["url"].ToString());
writer.Write(">");
writer.Write(row["title"].ToString());
writer.WriteEndTag("a");
writer.WriteEndTag("td");
writer.WriteEndTag("tr");
}
writer.WriteEndTag("table");
}
}
}
}
Most of the code of HyperLinkGroupWithViewState class is identical to
HyperLinkGroup class you created earlier. However, observe the SourceFile
property (marked in bold letters). Earlier it used to store the specified file
name in a local variable strSourceFile. Now it stores the file name in the
ViewState. The ViewState object is like dictionary or hashtable. While storing
values you specify some key and while retrieving them you use the same key.
Remember that ViewState saves all the information as object and hence you need
to type cast it as per your requirements.
Now use the HyperLinkGroupWithViewState control on the default page instead
of HyperLinkGroup control.
<cc2:HyperLinkGroupWithViewState
ID="HyperLinkGroup1"
runat="server"
SourceFile="~/links.xml"
Direction="Vertical" />
Run the web form and try clicking on the Submit button. This time the control
correctly renders links from Links2.xml file even after post backs.
Problem with ViewState
Though the ViewState approach that you used above worked well it has a
drawback. The ViewState can be disabled by the users of your control using
EnableViewState property. If EnableViewState property is set to false then your
control will not remember state information at all. Even if you store something
in the ViewState object it will be discarded before rendering the page. You can
try this out by setting the EnableViewState property of
HyperLinkGroupWithViewState control to false.
<cc2:HyperLinkGroupWithViewState
ID="HyperLinkGroup1"
runat="server"
SourceFile="~/links.xml"
EnableViewState="False"
Direction="Vertical" />
Using Control State to save state information
Fortunately, ASP.NET introduced an elegant way to save state information in
version 2.0. This mechanism is often called as Control State. The control state
is similar to ViewState but what makes it special is that you cannot disable it.
In order to use control state you need to follow three steps:
- Enable control state for your control
- Save control state
- Load control state
By default control state is not available for your control. This makes sense
because the control state is designed for critical data. The data without which
your control cannot live. One should use the control state carefully. Don't dump
each and every piece of information you wish to store in the control state.
To enable the control state for your control you need to register the custom
control with the page framework for control state. You do this by using
RegisterRequiresControlState() method of the Page class. You typically call this
method by overriding OnInit() method of the Control base class. The following
code shows how this is done.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
The RegisterRequiresControlState() method accepts the control for which the
control state is to be enabled.
Once enabled you can save state information in the control state. In order to
save state you must override SaveControlState() method of the Control base
class. The following code shows how this is done.
protected override object SaveControlState()
{
object state= base.SaveControlState();
Pair p = new Pair(state, strSourceFile);
return p;
}
The SaveControlState() method returns an object containing state information.
Inside the SaveControlState() method we first call SaveControlState() method on
the base class. Then we create an instance of Pair class. As the name suggests
the Pair class combines a piece of information to the existing state. In our
example we combine existing state (if any) and file name (strSourceFile). If you
wish to store multiple pieces of information then you need to create multiple
Pair instances. Finally, the newly created Paid instance is returned from the
SaveControlState() method.
The other part of the story is LoadControlState() method. This method loads
previously stored state information in the control. The LoadControlState()
method is shown below:
protected override void LoadControlState(object savedState)
{
Pair p = (Pair)savedState;
if (p != null)
{
base.LoadControlState(p.First);
strSourceFile = (string)p.Second;
}
}
The LoadControlState() method receives the saved state information as an
object. Inside we type cast this information to Pair class (recollect the we
returned a Pair instance in SaveControlState() method earlier). The Pair class
has two public members - First and Second. The First public member returns the
first object to which we combined some state information whereas the Second
public member returns the piece of information that we combined with the first
object. In our example the Second public member will return the file name. We
store this file name to the strSourceFile variable.
The following code shows the completed class (I have renamed it as
HyperLinkGroupWithControlState for the sake of clear understanding).
namespace BinaryIntellect.UI
{
public class HyperLinkGroupWithControlState : Control
{
private string strSourceFile;
public string SourceFile
{
get
{
return strSourceFile;
}
set
{
strSourceFile = value;
}
}
private HyperLinkGroupDirection enumDir;
public HyperLinkGroupDirection Direction
{
get { return enumDir; }
set { enumDir = value; }
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
protected override object SaveControlState()
{
object state= base.SaveControlState();
Pair p = new Pair(state, strSourceFile);
return p;
}
protected override void LoadControlState(object savedState)
{
Pair p = (Pair)savedState;
if (p != null)
{
base.LoadControlState(p.First);
strSourceFile = (string)p.Second;
}
}
protected override void Render(HtmlTextWriter writer)
{
DataSet ds = new DataSet();
ds.ReadXml(HttpContext.Current.Server.MapPath(SourceFile));
if (enumDir == HyperLinkGroupDirection.Horizontal)
{
writer.WriteFullBeginTag("table");
writer.WriteFullBeginTag("tr");
foreach (DataRow row in ds.Tables[0].Rows)
{
writer.WriteFullBeginTag("td");
writer.WriteBeginTag("a");
writer.WriteAttribute("href", row["url"].ToString());
writer.Write(">");
writer.Write(row["title"].ToString());
writer.WriteEndTag("a");
writer.WriteEndTag("td");
}
writer.WriteEndTag("tr");
writer.WriteEndTag("table");
}
else
{
writer.WriteFullBeginTag("table");
foreach (DataRow row in ds.Tables[0].Rows)
{
writer.WriteFullBeginTag("tr");
writer.WriteFullBeginTag("td");
writer.WriteBeginTag("a");
writer.WriteAttribute("href", row["url"].ToString());
writer.Write(">");
writer.Write(row["title"].ToString());
writer.WriteEndTag("a");
writer.WriteEndTag("td");
writer.WriteEndTag("tr");
}
writer.WriteEndTag("table");
}
}
}
}
In order to test the HyperLinkGroupWithControlState control create its
instance on the default web form. Set EnableViewState property to false and then
run the web form. You will find that even though the ViewState is disabled this
time control correctly remembers the new XML file.

The above figure shows the HyperLinkGroupWithControlState control in action.
Observe how HyperLinkGroupWithViewState shows the older links after disabling
the ViewState whereas HyperLinkGroupWithControlState still shows the new links.