When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, November 2, 2005

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
One nuisance shared among all controls is the fact that their items don't render attributes. For example, imagine that you wanted to display a CheckBoxList with particular CheckBoxes in the list displayed using a certain CSS class; or maybe when a particular RadioButton in a RadioButtonList control is selected, you want to run some client-side JavaScript. These are features that would be typically set using the CheckBox or RadioButton Web control's 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...

- continued -

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:

  1. Extend the ListItem class, configuring it to save its Attributes collection in ViewState
  2. Extend the ListControl class's ViewState policy to include its ListItems' Attributes collections
Option #1 may seem to be the most straightforward - if we want to have save the 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:

  1. Its internal ViewState - this includes things like its BackColor, ForeColor, and other simple properties' values.
  2. The view state of its Items property - the Items property is a collection of ListItems; recall that the ListItem class only persists the values of its Text and Value properties.
  3. The selected index(es)
We need to augment this policy to include saving the 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.

public class skmCheckBoxList : CheckBoxList, IRepeatInfoUser
{
   ...
   
   protected override object SaveViewState()
   {
      // Create an object array with one element for the CheckBoxList's
      // ViewState contents, and one element for each ListItem in skmCheckBoxList
      object [] state = new object[this.Items.Count + 1];

      object baseState = base.SaveViewState();
      state[0] = baseState;

      // Now, see if we even need to save the view state
      bool itemHasAttributes = false;
      for (int i = 0; i < this.Items.Count; i++)
      {
         if (this.Items[i].Attributes.Count > 0)
         {
            itemHasAttributes = true;
               
            // Create an array of the item's Attribute's keys and values
            object [] attribKV = new object[this.Items[i].Attributes.Count * 2];
            int k = 0;
            foreach(string key in this.Items[i].Attributes.Keys)
            {
               attribKV[k++] = key;
               attribKV[k++] = this.Items[i].Attributes[key];
            }

            state[i+1] = attribKV;
         }
      }

      // return either baseState or state, depending on whether or not
      // any ListItems had attributes
      if (itemHasAttributes)
         return state;
      else
         return baseState;
   }

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 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);
   }
}

Here we start by determining if we were passed an 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.

With the overridden 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.

Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the complete code (ZIP format)



  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article