ListControl Items, Attributes, and ViewState
By Scott Mitchell
Introduction
ASP.NET 1.x provides four Web controls that serve as list controls:
- The DropDownList,
- The CheckBoxList,
- The RadioButtonList, and
- The ListBox
Attributes collection.
Unfortunately, when a list control is rendered it does not render the attributes of the items.
In a previous article of mine, ListControl Items and
Attributes, I discussed one technique for adding attribute support to the ASP.NET 1.x list controls. Specifically, I looked
at extending the CheckBoxList class and overriding the Render() method to squirt out the
ListItem's attributes. This culminated in a
live demo that showed how to create a CheckBoxList that included a 'None' option that, when checked, would use
client-side JavaScript to automatically unselect all other checkboxes in the CheckBoxList.
In the article, however, I pointed out one drawback of my code: the attributes specified for the ListItems were
not saved in ViewState. That means that these attribute values are not persisted across postbacks; they
must be reinjected into the ListControl's ListItems on each postback, as was done in the
live demo from the previous article. This article examines how to augment the code examined from the previous article to
persist the ListItem attributes in ViewState. Read on to learn more!
I strongly encourage you to first read ListControl Items and Attributes before tackling this article...
The Impetus for this Article
In ListControl Items and Attributes I identified this ViewState problem and mentioned how a discussion on it might warrant a "Part 2":
"... the ListItem class does not save its values in ViewState (hence they are lost across postback). In this article I am [not going to talk about fixing this ViewState bug]; perhaps a future article will detail cracking [this bug]!Since then a couple of readers have emailed me asking about how to fix this ViewState problem. In particular, reader Gavin L. wrote in to share a solution to the ViewState problem, as discussed by Microsoft employee Lewis Wang in this newsgroup post. Lewis's approach looks at adding attribute support for a DropDownList control, including ViewState support. Lewis's post just gives a quick code dump. This article intends to delve into the code more deeply than Lewis did, as well as provide a slight modification to reduce the size of the rendered ViewState when the
ListControl does not contain
any items with attributes.
A Brief ViewState Primer
ViewState is a topic that is a little befuddling for many ASP.NET developers. In a nutshell, ViewState is the mechanism by which programmatic changes to the state of a Web control are persisted across postbacks. For example, imagine that you have an ASP.NET page with a Label Web control and two Button Web controls. One Button Web control, call it X, includes code in its server-side
Click event handler that sets the Label's BackColor property to Red.
(The other Button does not have any event handler associated with its Click event.)
When the user first visits the page, the Label has no background color. When they click Button X, a postback ensues
and the Click event handler code is executed, setting the Label's background color to Red. This causes the
Label Web control to render a style="background-color:red;" attribute to the browser, which displays the
Label with a Red background. But how does the Label control for this page remember that its background color was set to Red?
That is, if the user clicks the other Button (the one without a Click event handler) a postback ensues and
the page proceeds to be re-rendered. But how does the Label Web control remember that last time the page was visited by
this user its BackColor property was set to Red?
This seemingly magical memory of the Label Web control is possible thanks to ViewState. What happens is during the page's
lifecycle each control in the control hierarchy gets a chance to say, "Hey, remember this bit of information for me." When the
Label's BackColor property is set to Red, the Label essentially says, "Hey, remember that my BackColor
was set to Red." This information is "remembered" by being encoded into a base-64 string that is injected into the HTML
sent to the browser as the hidden __VIEWSTATE form field. On postback, this information is sent back and the
page passes back the "remembered" information to the controls in the hierarchy. (Specifically, controls can provide
SaveViewState() and LoadViewState(object) methods; SaveViewState() returns
an object that is to be remembered across postback; LoadViewState(object) is called on postback,
passing back to the control the object that was asked to be remembered.)
ViewState rememberance is a feature that is added at the Web control level. Since virtually all built-in Web controls provide ViewState support, as page developers we rarely have to worry about the intricacies of ViewState. However, when we find ourselves mucking around at the code level of Web controls - as we did in ListControl Items and Attributes, when creating a custom CheckBoxList control - that responsibility falls squarely on our shoulders. Therefore, if you ever find yourself creating or extending Web controls it's important that you have a good understanding of ViewState. A thorough discussion of ViewState is beyond the scope of this article, but for the more interested check out my MSDN Online article, Understanding ASP.NET ViewState; there's also a ViewState category on my blog, ScottOnWriting.NET.
Examining the ListControl and ListItem Classes' ViewState Policies
Using a tool like Reflector you can peer into the source code of the .NET Framework. By focusing in on the
ListControl and ListItem classes, we can see how they persist
their state across postbacks. The ListItem class saves just its Text and Value property
values in ViewState - it simply neglects its Attributes collection. This is why when extending, say, the
CheckBoxList (as we did in ListControl Items and Attributes) to support item-level attributes the extended control
does not remember the attribute values - the ListItem isn't saving it!
When extending a ListControl to support attributes, we have two ways that we can have the attribute values
remembered:
- Extend the
ListItemclass, configuring it to save itsAttributescollection in ViewState - Extend the
ListControlclass's ViewState policy to include itsListItems'Attributescollections
ListItems' Attributes collections,
put that responsibility in the hands of the ListItem class itself. Problem is, if we create a new, extended
ListItem class, we have to peck through the extended ListControl and in every method and property
where the default ListItem class is used, we need to replace it with our extended ListItem class.
This can be a pain, since it will likely mean retouching each and every method (and many properties) in the ListControl
class. The simpler approach is to just augment the ListControl's ViewState-related methods to include saving/loading
its ListItems' Attributes collections.
The ListControl class's ViewState policy saves three bits of information across postbacks:
- Its internal ViewState - this includes things like its
BackColor,ForeColor, and other simple properties' values. - The view state of its
Itemsproperty - theItemsproperty is a collection ofListItems; recall that theListItemclass only persists the values of itsTextandValueproperties. - The selected index(es)
ListItems' Attributes collections.
Overriding SaveViewState() and LoadViewState(object) in skmCheckBoxList
To support persisting the
ListItems' Attributes collections to ViewState, we need to override the
SaveViewState() and LoadViewState(object) methods in the extended ListControl
class (skmCheckBoxList, in ListControl Items and Attributes). The following code snippets show these new overridden
methods; you can download the complete, working code at the end of this article.
|
The SaveViewState() method is called before the page is rendered, but after the Load event and
any postback-related event handlers (i.e., Click, TextChanged, etc.) have run. We start by
getting the ViewState of the base control (CheckBoxList) using base.SaveViewState(). We also create an object
array with enough elements for this base state and an element for each ListItem.
Next, the Items collection is enumerated. For each ListItem that has Attributes defined,
an object array is created and populated with the Key and Value for each attribute.
My code is similar to that posted
by Lewis Wang. However, the astute observer will note that I added an itemHasAttributes flag that is 'turned on'
only if there exists at least one ListItem with an attribute. If the ListControl's ListItems
lack any attributes, then there's no need to persist a larger, empty array; instead, just the baseState is persisted.
However, if any of the ListControl's ListItems have an attribute defined, then the entire
object array is persisted.
Persisting the data to the ViewState is only half the story; the other half is, on postback, reloading the persisted state
back into the control. This is done through the control's
Here we start by determining if we were passed an
With the overridden
Happy Programming!
LoadViewState(object) method, which is shown below:
protected override void LoadViewState(object savedState)
{
if (savedState == null) return;
// see if savedState is an object or object array
if (savedState is object[])
{
// we have an array of items with attributes
object [] state = (object[]) savedState;
base.LoadViewState(state[0]); // load the base state
for (int i = 1; i < state.Length; i++)
{
if (state[i] != null)
{
// Load back in the attributes
object [] attribKV = (object[]) state[i];
for (int k = 0; k < attribKV.Length; k += 2)
this.Items[i-1].Attributes.Add(attribKV[k].ToString(),
attribKV[k+1].ToString());
}
}
}
else
// we have just the base state
base.LoadViewState(savedState);
}
}
object array or just an object. If we are passed
just an object, then this method boils down to one simple line of code, base.LoadViewState(savedState);.
However, if we were passed an object array then that means that there is at least one attribute set in at least
one ListItem. Therefore, we need to enumerate the array and set the appropriate ListItem's attribute
value using the Attributes collection's Add(key, value) method.
SaveViewState() and LoadViewState(object) methods, skmCheckBoxList
now persists its values in the ViewState, meaning that these values can survive across postbacks.
Conclusion
When adding attribute support to a ListControl server control, we need to provide our own ViewState
support since the ListItem class does not persist its Attributes collection.
This support is achieved by overriding the SaveViewState() and LoadViewState(object) methods,
which is what we examined in this article. The complete, working code is available for download at the end of this article.
Attachments



