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 ListItem
s were
not saved in ViewState. That means that these attribute values are not persisted across postbacks; they
must be reinjected into the ListControl
's ListItem
s 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
ListItem
class, configuring it to save itsAttributes
collection in ViewState - Extend the
ListControl
class's ViewState policy to include itsListItem
s'Attributes
collections
ListItem
s' 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 ListItem
s' 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
Items
property - theItems
property is a collection ofListItem
s; recall that theListItem
class only persists the values of itsText
andValue
properties. - The selected index(es)
ListItem
s' Attributes
collections.
Overriding SaveViewState()
and LoadViewState(object)
in skmCheckBoxList
To support persisting the
ListItem
s' 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 ListItem
s
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 ListItem
s 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