Kriya and Meditation for Software / IT Professionals. Conducted by Bipin Joshi in Thane. Read more...
Learn ASP.NET MVC, ASP.NET Core and ASP.NET Design Patterns from the comfort of your home. Online courses conducted by Bipin Joshi on weekends. Click here for more details.

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.

 


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 02 December 2007


Tags : ASP.NET Custom Controls