ASP.NET 2.0 and Data-Bound Controls: A New Perspective and Some New Practices

 

Dino Esposito
Wintellect

January 2005

Applies to:
   Microsoft ASP.NET 1.x
   Microsoft ASP.NET 2.0

Summary: Learn how the tools for building custom data-bound controls evolve in ASP.NET 2.0. (19 printed pages)

Contents

Why We Need a New Data Source Model Data-bound Controls in ASP.NET 2.0
Points of Analysis
The Data-Binding Mechanism
List Controls
A HeadlineList Sample Control
Managing Custom Collections
A Word on Composite Controls
In Conclusion

Why We Need a New Data Source Model

Data binding was one of the most pleasant surprises that developers found out of the ASP.NET 1.x box. Compared to Active Server Pages support for data access, data binding was an extraordinary mix of simplicity and effectiveness. Measured against the needs of real developers, though, it proved a bit unperfected. Limitations are not in the overall functionality, but rather lay in the fact that developers have to write a lot of code to handle even simple and common operations such as paging, sorting, or deleting. To make up for this, ASP.NET 2.0 adds a new data source model (see my article More Load, Less Code with the Data Enhancements of ASP.NET 2.0). It consists of many new UI-less controls that bridge the gap between visual parts of data-bound controls and data containers. Basically, the vast majority of code that developers were required to write in ASP.NET 1.x, properly factored and authored, is now embedded in a new family of controls: data source components.

There are many benefits to using data source components—first and foremost, the possibility of a fully declarative data binding model. The new model reduces the loose code inserted inline in ASPX resources or scattered through code-behind classes. The new data-binding architecture forces developers to play by strict rules. Furthermore, it inherently changes the quality of the code. Long blocks of code attached to events tend to disappear, replaced by components that just plug into the existing framework. The data source components derive from abstract classes, implement well known interfaces, and overall signify a higher level of reusability.

Nikhil Kothari's excellent book about control development, Developing Microsoft ASP.NET Server Controls and Components, helped thousands of developers to build custom controls and illustrated best practices for design and implementation. However, a book—no matter how great it is—can never replace a better system framework. With ASP.NET 2.0, you also get a completely redesigned graph of classes that add more specific data-binding capabilities as you scroll the tree from base to leaf classes. The new hierarchy of data-bound controls makes it easier for all developers to pick up the right class to inherit from for building their own custom data-bound control.

In this article, you'll get an early look at the changes in the ASP.NET 2.0 data-binding model that affect custom controls. Along the way, you'll learn about the new base classes available and the new requirements for high-quality new custom controls.

Data-Bound Controls in ASP.NET 2.0

The ASP.NET 2.0 data source model doesn't necessarily require a new breed of controls (for example GridView and FormView); it still works with old-style controls such as DataGrid and CheckBoxList. What does this mean to control developers? There are two distinct types of data source to deal with—classic IEnumerable-based data containers such as DataView and collections, and data source controls such as SqlDataSource and ObjectDataSource. In the end, ASP.NET 2.0 data-bound controls must be able to normalize any incoming data to an enumerable collection regardless of the source—be it an ADO.NET object, a custom collection, or a data source component.

In ASP.NET 1.x, the documentation is somehow ahead of the framework. The documentation correctly identifies and discusses three types of data-bound controls—standard controls, list controls, and composite controls. To the first category belongs any control that simply provides a non-empty implementation of the DataBind method and DataSource property. List controls are an interesting mix of advanced layout properties (for example, RepeatColumns and RepeatLayout) and fixed and embedded item templates to repeat for each bound data element. Finally, composite controls are controls that take care of designing the final user interface by combining one or more existing controls. The documentation accurately addresses any issue related to the creation of these types of control; the ASP.NET framework, though, doesn't supply many base classes to simplify the developer's task. Figure 1 shows the new hierarchy of data-bound controls in ASP.NET 2.0. Note in yellow the base classes and their distribution in the overall tree.

Aa479321.databound01(en-us,MSDN.10).gif

Figure 1. The hierarchy of data-bound controls in ASP.NET 2.0

It is interesting to take a look at the base classes rendered in Figure 1. They are listed and detailed in Table 1.

Class Description
BaseDataBoundControl Root class for data-bound controls. Performs the data binding and validates any bound data.
DataBoundControl Contains the logic to communicate with data source controls and data containers. You inherit from this class to build a standard data-bound control.
ListControl Base class for list controls, provides an Items collection and advanced layout rendering capabilities.
CompositeDataBoundControl Implements the typical code required by composite controls, including the code that restores the control's tree from viewstate after a postback is made.
HierarchicalDataBoundControl Root class for hierarchical, tree-based controls.

Table 1. Base data-bound classes in ASP.NET 2.0

To anyone who has endured the pain of creating a feature-rich data-bound control that manages its own collection of data and restores correctly from viewstate, these classes are extraordinarily welcome. Want an illuminating example? Read on.

Points of Analysis

The ASP.NET Developer Center featured in the past few months a couple of articles about ASP.NET 1.1 data-bound controls: the RssFeed and DetailsView controls (Building DataBound Templated Custom ASP.NET Server Controls and A DetailsView Control for ASP.NET 1.x, respectively). If you delve deep into the code of both controls you'll see that they employ special techniques at various points in the source. For example, they rebuild the control's tree from viewstate after a postback is made to the page (that is, a postback not under the control's jurisdiction); they expose a collection of items through a custom collection class; they allow to style items; they support quite a few types of input sources. For each of these features, in ASP.NET 1.1 you have to write code and, more importantly, you have to write it in a particular sequence, overriding particular base methods, and carefully following instructions and suggestions from the documentation and the aforementioned excellent Developing Microsoft ASP.NET Server Controls and Components.

In ASP.NET 2.0, most of the plumbing code used in the two sample controls is hardcoded in the base classes listed in Table 1. To compare and contrast data-bound controls in ASP.NET 1.1 and 2.0, I'll focus on the following main points:

  • The overall data-binding mechanism and the different data source types
  • Collections and viewstate management

The list probably isn't exhaustive but it is certainly enough to give you the big picture of control development. You'll be pleased and surprised to see how little code you need to develop rich custom controls.

The Data-Binding Mechanism

To build a new data-bound control in ASP.NET 2.0, you start by deciding which class better suits you. The choice, though, is not limited to relatively empty classes like Control and WebControl or, perhaps, ListControl. Let's explore classes under the hood. The BaseDataBoundControl is the root of all data-bound control classes. It defines the DataSource and DataSourceID properties and validates their assigned content. DataSource accepts enumerable objects obtained and assigned the ASP.NET 1.x way.

Mycontrol1.DataSource = dataSet;
Mycontrol1.DataBind();

DataSourceID is a string and refers to the ID of a bound data source component. Once a control is bound to a data source, any further interaction between the two (in both reading and writing) is handled out of your control and hidden from view. This is both good and bad news at the same time. It is good (rather, great) news because you can eliminate a large quantity of code. The ASP.NET framework guarantees that correct code executes and is written according to recognized best practices. You're more productive because you author pages faster with the inherent certainty of having no subtle bugs in the middle. If you don't like this situation—look, the same situation that many ASP.NET 1.x developers complained about—you can stick to the old-style programming that passes through the DataSource property and DataBind method. Also in this case, the base class saves you from common practices even though the saving on the code is less remarkable.

DataBoundControl is the class to use for standard custom data-bound controls that don't have much to share with existing controls. If you have to handle your own collection of data items, manage viewstate and styles, create a simple but made-to-measure user interface, this class provides a good starting point. Most interestingly, the DataBoundControl class connects the control to data source components and hides any difference between enumerable data sources and ad hoc components at the API level. In short, when you inherit from this class you only need to override a method that receives a collection of data regardless of the origin—be it a DataSet object or a newer data source component.

Let's expand on this point, which represents a key change in the architecture.

BaseDataBoundControl overrides the DataBind method (originally defined on Control) and makes it call the PerformSelect method, marked as protected and abstract. As the name suggests, PerformSelect is expected to retrieve a working collection of data for the binding to take place. The method is protected because it contains implementation details; it is abstract (MustInherit in Visual Basic jargon) because its behavior can only be precised by derived classes like DataBoundControl.

So what does DataBoundControl do to override PerformSelect?

It connects to the data source object and obtains the default view. A data source object (for example, a control like SqlDataSource or ObjectDataSource) executes its select command and returns the resulting collection. The protected method that operates the data retrieval, named GetData, is smart enough to check the DataSource property too. If the DataSource is non-empty, the bound object is wrapped in a dynamically created data source view object and returned.

The next step begins to involve you as a control developer. So far, in a totally automated way, the base classes have retrieved data either from ADO.NET objects or data source components. The next step depends on what the control is expected to do. Here's where the overridable PerformDataBinding method fits in. The following code snippet shows the method as implemented in DataBoundControl. Note that the IEnumerable parameter the framework passes to the method simply contains the data to bind, regardless of their origin.

protected virtual void PerformDataBinding(IEnumerable data)
{
}

In a custom data-bound control, you only need to override this method and populate any control-specific collection like the Items collection of many list-controls (CheckBoxList, for example). The rendering of the control's user interface occurs in the Render method or in CreateChildControls, depending on the nature of the control. Render is okay for list controls; CreateChildControls is the perfect fit for composite controls.

One thing remains to explain—who starts the data binding process? In ASP.NET 1.x, data binding requires an explicit call to the DataBind method to begin working. In ASP.NET 2.0, this is still required if you bind data to controls using the DataSource property. If, instead, you use data source components through the DataSourceID property, you should avoid it. The data binding process is automatically triggered by the internal OnLoad event handler defined in DataBoundControl, as the following pseudocode demonstrates.

protected override void OnLoad(EventArgs e)
{
   this.ConnectToDataSourceView();
   if (!Page.IsPostBack)
       base.RequiresDataBinding = true;
   base.OnLoad(e);
}

Whenever the control is loaded into the page (postback or first time), the data is retrieved and bound. It is up to the data source to decide whether to go again with a query or use some cached data.

If the page is displayed for the first time, the RequiresDataBinding property is also turned on to require binding of data. When the assigned value is true, the setter of the property calls DataBind internally. The pseudo code below shows the internal implementation of the RequiresDataBinding setter.

protected void set_RequiresDataBinding(bool value)
{
   if (value && (DataSourceID.Length > 0))
      DataBind();
   else
      _requiresDataBinding = value;
}

As you can see, for backward compatibility the automatic call to DataBind takes place only if DataSourceID is not null, that is, if you're bound to an ASP.NET 2.0 data source control. In light of this, if you also call DataBind explicitly it results in a double binding of data.

Note that you can't have both DataSource and DataSourceID set at the same time. When this happens, an invalid operation exception is thrown.

Finally, a quick mention is due to the EnsureDataBound protected method. Defined on the BaseDataBoundControl class, the method ensures the control has been correctly bound to the required data. If RequiresDataBinding is true, the method invokes DataBind, as in the code snippet below.

protected void EnsureDataBound()
{
  if (RequiresDataBinding && (DataSourceID.Length > 0))
      DataBind();
}

If you have written complex and sophisticated data-bound controls, you probably know already what I mean. In ASP.NET 1.x, a data-bound control is normally architected to build its own user interface in either of the following two scenarios: with full access to the data source or based on the viewstate. When the control needs to manage its own postback events (for example, imagine a DataGrid with paging enabled), the two aforementioned options appear to be two mere distant extremes. In ASP.NET 1.x, these controls (again, think of the DataGrid) had only one way out: firing events to the host page to be refreshed. This approach leads to the well-known surplus of code in ASP.NET 1.x pages—just the problem that data source components are called to fix.

In ASP.NET 2.0, you set RequiresDataBinding to true whenever something happens in the life of a control to require binding of data. Setting the property triggers the data binding mechanism that recreates an updated version of the control's internal infrastructure. The built-in OnLoad event handler also connects the control to the data source. To be really effective, this technique must rely on smart data source controls with the capability of caching their data somewhere. The SqlDataSource control, for instance, supports many properties to store any bound result set to the ASP.NET cache for a given duration.

List Controls

Data-bound controls are often list controls. A list control builds its own user interface by repeating a fixed template for each bound data item within the boundaries of the control's mainframe. For example, a CheckBoxList control just repeats a CheckBox control for each bound data item. Likewise, a DropDownList control iterates through its data source and creates a new <option> element within a parent <select> tag. In addition to list controls, ASP.NET also features iterative controls. How do they differ?

List and iterative controls differ with regards to the level of customization allowed on the repeatable template applied to each data item. Like a CheckBoxList control, a Repeater control walks its way through the bound data items and applies the user-defined template. The Repeater (and the more sophisticated DataList control) is extremely flexible, but doesn't help much to keep code modular and layered. To use a Repeater, you need to define templates in the page (or in an external user control) and consume data-bound properties in the ASPX source. It is quick, effective, sometimes necessary, but definitely not neat and elegant.

In ASP.NET 1.x, all list-controls inherit from ListControl—the only class in Table 1 to be defined already in 1.x. Let's enter in code-monkey mode and start practicing with data-bound controls in ASP.NET 2.0. I'll start by building a HeadlineList control that renders out two lines of data-bound text for each data item. In addition, the control will also feature some layout capabilities such as vertical or horizontal rendering.

A HeadlineList Sample Control

As mentioned, ListControl is the base class for all list controls in both ASP.NET 1.x and 2.0. Nicely enough, the HeadlineList control, written here for ASP.NET 2.0, can be ported back to ASP.NET 1.x in a very seamless way. For some reason, when it comes to building a list of headlines, the first idea that springs to mind is to use a Repeater. A Repeater, indeed, would make it really simple.

<asp:Repeater runat="server">
   <HeaderTemplate>
      <table>
   </HeaderTemplate>
   <ItemTemplate>
      <tr><td>
      <%# DataBinder.Eval(Container.DataItem, "Title") %>
      <hr>
      <%# DataBinder.Eval(Container.DataItem, "Abstract") %>
      </td></tr>
   </ItemTemplate>
   <FooterTemplate>
      </table>
   </FooterTemplate>
</asp:Repeater>

What's wrong with this code? Or, more precisely, what can be improved in this code?

NOTE: In ASP.NET 2.0, you can replace DataBinder.Eval(Container.DataItem, field) with a shorter expression that benefits from a new public method—Eval—on the Page class. The new expression looks like Eval(field). Internally, Eval calls the static Eval method on the DataBinder class and determines the correct binding context to use.

The names of the fields are hardcoded in the ASPX page. Reusability is possible, but only through cut-and-paste. The more code you add to enrich the Repeater's behavior, the more you jeopardize the solution and its reusability across pages and projects. If a headline list control is just what you want, try the following approach instead.

public class HeadlineList : ListControl, IRepeatInfoUser
{
  :
}

ListControl is the base class for list controls (in the same family as CheckBoxList, DropDownList, and the like); IRepeatInfoUser is the little known interface that most of these controls implement to render in columns and rows, horizontally or vertically. Note that ListControl and IRepeatInfoUser exist also in ASP.NET 1.x and work in nearly the same way as in 2.0.

A list control is built around a control to repeat; this control (or graph of controls) is a class property and is instantiated upon loading to save some CPU. Here's the implementation of the private ControlToRepeat property.

private Label _controlToRepeat;

private Label ControlToRepeat

{
   get
   {
      if (_controlToRepeat == null)
      {
         _controlToRepeat = new Label();
         _controlToRepeat.EnableViewState = false;
         Controls.Add(_controlToRepeat);
      }
      return _controlToRepeat;
   }
} 

In this case, the control to repeat—the headline—is a Label that is instantiated on the first reading. The HeadlineList control should also give users a way to influence the appearance through a variety of properties like RepeatLayout, RepeatColumns, and RepeatDirection. These properties are defined on many standard list controls and as such should be nothing new to developers. Their implementation is similar and looks like the code below.

public virtual RepeatDirection RepeatDirection
{
   get
   {
      object o = ViewState["RepeatDirection"];
      if (o != null)
         return (RepeatDirection) o;
      return RepeatDirection.Vertical;
   }
   set
   {
      ViewState["RepeatDirection"] = value;
   }
}

The other piece of code to write to complete the HeadlineList control revolves around rendering. The IRepeatInfoUser interface counts various properties through which you can control the rendering process. Examples are the HasHeader, HasFooter, and HasSeparator Boolean properties. You implement these properties as you would any other ordinary property and use them if needed in the RenderItem interface method.

public void RenderItem(ListItemType itemType, int repeatIndex, 
RepeatInfo repeatInfo, HtmlTextWriter writer)
{
   string format = "<b>{0}</b><hr style='solid 1px black'>{1}";
   Label lbl = ControlToRepeat;
   int i = repeatIndex;
   lbl.ID = i.ToString();
   string text = String.Format(format, Items[i].Text, Items[i].Value);
   lbl.Text = text;
   lbl.RenderControl(writer);
}

RenderItem is ultimately responsible for the output served to the page. It takes the control to repeat and renders it out to markup. RenderItem is called from Render.

protected override void Render(HtmlTextWriter writer)
{
   if (Items.Count >0)
   {
      RepeatInfo ri = new RepeatInfo();
      Style controlStyle = (base.ControlStyleCreated 
                                  ? base.ControlStyle : null);
      ri.RepeatColumns = RepeatColumns;
      ri.RepeatDirection = RepeatDirection;
      ri.RepeatLayout = RepeatLayout;
      ri.RenderRepeater(writer, this, controlStyle, this);
   }
}

RepeatInfo is a helper object specifically designed to build new controls by repeating existing graphs of controls. That's all that's needed. Let's arrange a sample page and test the control.

<expo:headlinelist id="HeadlineList1" runat="server" 
       repeatlayout="Table" repeatdirection="Vertical" repeatcolumns="2" 
       datatextfield="LastName" datavaluefield="Notes" /> 

Figure 2 shows the control in action.

Aa479321.databound02(en-us,MSDN.10).gif

Figure 2. The HeadlineList data-bound control

The control behaves well at design time without any further injection of code. The nicest side effect of this code is not free design-time support, though. To me, it is simply fantastic that it works with ADO.NET data source objects (for example, DataTable or DataSet) and data source components like SqlDataSource. You take this code, compile it into an ASP.NET 1.x project, and it works with IEnumerable-based data sources. Bring it into an ASP.NET 2.0 project and it'll work—unchanged—with data source objects as well.

What's the moral of the story?

In ASP.NET 1.x, the ListControl class is a pleasant exception—yet an exception. In ASP.NET 2.0, you can build any data-bound control using a similar simple-but-effective approach. In doing so, you take advantage of new base classes that incorporate most of the complexity and hard-code most of the known best practices.

Managing Custom Collections

ListControl is a much too specialized class that performs data binding in a fixed way that is out of your control unless you override methods like PerformSelect, OnDataBinding, and PerformDataBinding. It also provides a predefined Items collection property. Let's tackle data-binding in ASP.NET 2.0 at a lower level and design a ButtonList control that:

  • Uses a custom collection class to hold constituent items
  • Manages viewstate in a custom way

The ButtonList control is another list control that outputs a pushbutton for each bound data item. You can make it inherit from ListControl; more, you could take the source code of HeadlineList, replace Label with Button and it should work as well. I'm taking a different approach this time to illustrate the behavior of DataBoundControl. For simplicity, I'll also skip the IRepeatInfoUser interface.

public class ButtonList : System.Web.UI.WebControls.DataBoundControl
{
   :
}

A caption and command name characterize each button. This information is taken from the bound data source through a couple of custom properties, named DataTextField and DataCommandField. You can easily add similar properties to provide for data-bound tool tips, or perhaps URLs.

public virtual string DataCommandField
{
   get
   {
      object o = ViewState["DataCommandField"];
      if (o == null)
         return "";
      return (string)o;
   }
   set { ViewState["DataCommandField"] = value; }
}

All the information found about each bound button is stuffed into a collection of custom objects exposed through the Items property. (Note that Items is only a standard, conventional, yet arbitrary, name for this property.)

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual ButtonItemCollection Items
{
   get
   {
      if (_items == null)
      {
         _items = new ButtonItemCollection();
         if (base.IsTrackingViewState)
            _items.TrackViewState();
      }
      return _items;
   }
}

The Items collection is an instance of the custom ButtonItemCollection class—a collection of ButtonItem objects. The ButtonItem class just stores the key information about a bound button—Text and CommandName properties, plus a couple of constructors and the ToString method. The ButtonItem class is the counterpart to the ListItem class as a generic list control. Here's an example.

public class ButtonItem
{
   private string _text;
   private string _command;

   public ButtonItem(string text, string command) {
      _text = text;
      _command = command;
   }
   public string Text {
      get {return _text;}
      set {_text = value;}
   }
   public string CommandName {
      get { return _command; }
      set { _command = value; }
   }
   public override string ToString() {
      return "Button [" + Text + "]";
   }
}

Now, how would you create a collection of ButtonItem objects? In ASP.NET 1.x, you have to build a custom collection class inheriting from CollectionBase and overriding a couple of methods at the very minimum. A custom collection, though, is merely a wrapper around an ArrayList object with no real advantage in terms of access speed. Casting, in fact, is still required. Generics in .NET 2.0 provide a true turning point. To build a collection of ButtonItem objects you need the following code:

public class ButtonItemCollection : Collection<ButtonItem>
{
}

And it also performs better because of the work the compiler does under the hood. The ButtonList control requires only two overridden methods: Render and PerformDataBinding. Render assumes that the Items collection is filled; so it simply iterates and outputs markup code.

protected override void Render(HtmlTextWriter writer)
{
   for(int i=0; i<Items.Count; i++)
   {
      ButtonItem item = Items[i];
      Button btn = new Button();
      btn.Text = item.Text;
      btn.CommandName = item.CommandName;
      btn.RenderControl(writer);
   }
}

Why is the Items collection important? It helps you to achieve two results. First, you can populate the list control with manually added items. Second, once the collection is persisted in the viewstate, you can rebuild the user interface of the control on postback without binding to data. Where, and by what, is the Items collection populated on data binding? This is what PerformDataBinding is for. The method takes an enumerable list of data (regardless of the original source) and uses that to fill the Items collection.

protected override void PerformDataBinding(IEnumerable dataSource)
{
   base.PerformDataBinding(dataSource);
   string textField = DataTextField;
   string commandField = DataCommandField;

   if (dataSource != null) {
   foreach (object o in dataSource)
   {
      ButtonItem item = new ButtonItem();
      item.Text = DataBinder.GetPropertyValue(o, textField, null);
      item.CommandName = DataBinder.GetPropertyValue(o, 
                                             DataCommandField, null);
      Items.Add(item);
   } 
   }
}

Whenever data binding is required, this method ensures that the Items collection is populated. What happens on postbacks? In this case, the Items collection must be reconstructed from the viewstate. You give the custom collection class this ability through the methods on the IStateManager interface. Here are the key methods of the interface:

public void LoadViewState(object state)
{
   if (state != null) {
      Pair p = (Pair) state;
      Clear();
      string[] rgText = (string[])p.First;
      string[] rgCommand = (string[])p.Second;

      for (int i = 0; i < rgText.Length; i++)
         Add(new ButtonItem(rgText[i], rgCommand[i]));
   }
}

public object SaveViewState()
{
   int numOfItems = Count;
   object[] rgText = new string[numOfItems];
   object[] rgCommand = new string[numOfItems];

   for (int i = 0; i < numOfItems; i++) {
      rgText[i] = this[i].Text;
      rgCommand[i] = this[i].CommandName;
   }

   return new Pair(rgText, rgCommand);
}

The class serializes itself into the viewstate using a Pair object—a sort of optimized 2-position array. You create two object arrays to hold text and command names for each button. The two arrays are then packed in a pair and inserted in the viewstate. When the viewstate is restored, the pair is unpacked and the Items collection refilled using the previously stored information. Using this approach is preferable over making the ButtonItem class serializable because of worse performance (in both space and time) of the classic binary formatter.

Adding viewstate support to the collection is not enough, though. The ButtonList control must also be enhanced to take advantage of the serialization capabilities of the collection. You override LoadViewState and SaveViewState on the control class.

protected override void LoadViewState(object savedState)
{
   if (savedState != null) {
      Pair p = (Pair) savedState;
      base.LoadViewState(p.First);
      Items.LoadViewState(p.Second);
   }
   else
      base.LoadViewState(null);
}

protected override object SaveViewState()
{
   object baseState = base.SaveViewState();
   object itemState = Items.SaveViewState();
   if ((baseState == null) && (itemState == null))
      return null;
   return new Pair(baseState, itemState);
}

The viewstate of the control is made of two elements—the default control's viewstate plus the Items collection. The two objects are packed into a Pair object. In addition to Pair objects, you can use Triplet objects—arrays of three objects—or compose any number of objects using pairs of Pair or Triplet.

Custom collections designed this way give satisfaction also at design time. The default collection editor embedded in Visual Studio 2005 recognizes the collection and pops up a dialog box like that in Figure 3.

Aa479321.databound03(en-us,MSDN.10).gif

Figure 3. The ButtonList Items collection at design time

It is worth noticing that in ASP.NET 2.0 some data-bound controls let you keep data-bound items separated from items programmatically added through the Items collection. The Boolean AppendDataBoundItems property controls this aspect of the control's programming interface. The property is defined on ListControl (not on DataBoundControl) and defaults to false.

A Word on Composite Controls

The CompositeDataBoundControl class is the starting point for building composite controls that I believe is just what you refer to when thinking about data-bound controls. A composite control must:

  • Act as a naming container.
  • Create its own user interface through the CreateChildControls method.
  • Implement a particular logic to restore its hierarchy of child elements after postback.

The last point is well illustrated in Nikhil Kothari's book and implemented in all built-in controls of ASP.NET 1.x. If you haven't yet fully understood that concept so far, the good news is that you can now forget about it entirely. Everything is now hardcoded in the CompositeDataBoundControl class. The main aspect you need to care about is designing the children of your control. You do this by overriding a new method defined as follows:

protected abstract int CreateChildControls(
      IEnumerable dataSource, bool dataBinding);

CompositeDataBoundControl inherits from DataBoundControl, so most of the things stated in this article about collections, binding, and viewstate apply to composite controls too.

In Conclusion

Data binding and data-bound controls represented a quantum leap forward in ASP.NET 1.x but left a number of points unexplained and several questions unanswered. Nikhil Kothari's book was a superb and authoritative guide for all developers. ASP.NET 2.0 turns some of that book's best practices (largely implemented already under the hood of ASP.NET 1.x) into reusable classes and a new object model for data-bound controls.

This article highlights the main changes made between ASP.NET 1.x and ASP.NET 2.0 and outlines the way they are going to impact development through a couple of practical examples. Going forward, we will want to keep an eye on styles and themes in ASP.NET 2.0 control development. But that will perhaps provide the focus for another article in the near future. Stay tuned.

 

About the Author

Dino Esposito is a Wintellect instructor and consultant based in Italy. Author of Programming Microsoft ASP.NET and the more recent Introducing Microsoft ASP.NET 2.0 (both from Microsoft Press), he spends most of his time teaching classes on ASP.NET and ADO.NET and speaking at conferences. Tour Dino's blog at https://weblogs.asp.net/despos.

© Microsoft Corporation. All rights reserved.