A Crash Course on ASP.NET Control Development: Building Data-Bound Controls

 

Dino Esposito
Solid Quality Learning

November 2005

Applies to:
   Microsoft ASP.NET 2.0

Summary: You will frequently need to use ASP.NET controls to display data. The general way of doing this is to use data-bound controls. Creating your own data-bound controls is a bit more work than is required for "normal" controls, but will make it easier for you to connect them to databases and other data sources. (21 printed pages)

C#: Click here to download the code sample for this article.

Visual Basic: Click here to download the code sample for this article.

Contents

Introduction
Base Classes for Data-Bound Controls
The Data-Bound Headline Control
Performing Data Binding
Rendering
List Controls
The HyperLinkList Control
The IRepeatInfoUser Interface
Conclusion

Introduction

A data-bound control is like any other server control except that it sports a few additional properties to connect to an external data source. When bound to a data source, the control has its own user interface modified and adapted to the contents of the source. For example, a data-bound drop-down list control ends up listing as many items as there are records in the results of a query.

Specific properties of all data-bound controls are DataSource and DataSourceID. They are mutually exclusive properties aimed at binding the control to a data source. Additional properties might be required based on the expected behavior of the control. In addition, a data-bound control needs a mechanism that takes data out of the data source and uses that to build the user interface.

In ASP.NET 2.0, most of the required code has been integrated in a couple of base classes—BaseDataBoundControl and DataBoundControl. Deriving from one of these classes is the first step for developers willing to build custom data-bound controls.

Base Classes for Data-Bound Controls

BaseDataBoundControl derives from WebControl and extends it with the two aforementioned properties DataSource and DataSourceID. In addition, the class overrides the method DataBind. The DataBind method is common to all controls and represents the entry point in any control's data binding pipeline.

public override void DataBind()
{
   this.PerformSelect();
}

PerformSelect is one of the two methods on the BaseDataBoundControl class that are abstract. The other method is ValidateDataSource.

protected abstract void PerformSelect();
protected abstract void ValidateDataSource(object dataSource);

The former fetches the data out of the specified data source; the latter validates the bound data source object to ensure that it has one of the supported formats. Here's the typical implementation of ValidateDataSource:

protected override void ValidateDataSource(object dataSource)
{
   if (((dataSource != null) && !(dataSource is IListSource)) && 
      (!(dataSource is IEnumerable) && !(dataSource is IDataSource)))
   {
        throw new InvalidOperationException();
   }
}

As you can see, the method verifies that the data source is not null and implements one of the following interfaces: IListSource, IEnumerable, IDataSource.

Richer in code and functionality is the DataBoundControl class—a direct child of the BaseDataBoundControl class. The class adds one more public property—DataMember—and overrides the two abstract methods of the parent class. More importantly, the class adds meat and potatoes to the code of BaseDataBoundControl, making the data binding mechanism fully functional.

If you want to build a custom data-bound control, DataBoundControl is a good place to start from. As a first example, let's consider the Headline control that I built in the previous article. In brief, the Headline control is a two-row table displaying title and text of a news headline with an optional icon and a button to toggle the text on and off. Figure 1 provides a preview of the control.

Aa479308.ccc3_fig01(en-us,MSDN.10).gif

Figure 1. The Headline control in action

Wouldn't it be nice if you could bind title and text to the fields of your data source? Let's tweak the control to do just this.

The Data-Bound Headline Control

The control inherits from DataBoundControl and implements the marker interface INamingContainer. Please refer to the previous article for a detailed explanation of why you need to have INamingContainer.

public class Headline : DataBoundControl, INamingContainer
{
   :
}

In ASP.NET 2.0, each data-bound control has two possible types of data source—enumerable collections and data source controls. Enumerable collections can be any class that implements either IListSource or IEnumerable. Data source controls are components new to ASP.NET 2.0 such as SqlDataSource or ObjectDataSource that implement the IDataSource interface. You use DataSource to bind the control to an enumerable collection; you resort to DataSourceID to associate it with a data source control within the same page.

public object DataSource { ... }
public string DataSourceID { ... }

A data-bound control must support both scenarios. The infrastructure provided by DataBoundControl blurs any distinction between enumerable collections and data source controls. All that you have to do is override the PerformDataBinding method.

protected override void PerformDataBinding(IEnumerable data)

The parameter passed to this method is the list of data to bind to the control—no matter where this data comes from. PerformDataBinding is called from DataBoundControl's implementation of PerformSelect. It's this internal code that does different things if DataSource or DataSourceID is set. From the data-bound control's standpoint, there is no difference and all that is received is a collection of data items from which to build the user interface.

The first time the control displays its output, it passes through the binding mechanism. So data is read from the source, the control tree is built and the markup generated. What happens, however, when the page with the control is posted back? A well-designed control has the capability of retrieving its display data from the viewstate, provided that viewstate is enabled for the page and the control.

Take a look at Figure 1 again. The first time the control displays title and text that are read from the data source. When the user clicks to expand or collapse the view, there's no need to re-read needed data from the data source. You can cache the value or, like all built-in controls do, put it in the viewstate. Which values exactly? All the data-bound values you need to rebuild the control's user interface.

The typical approach you use to easily manage bound data across page postbacks consists in creating a class that represents the data item. In this case, you can create a HeadlineItem class with as many properties as there are bound values. HeadlineItem will have only two properties—Title and Text:

public class HeadlineItem 
{
   private string _text;
   private string _title;
   public HeadlineItem()
   {
   }
   public HeadlineItem(string title, string text)
   {
      _text = text;
      _title = title;
   }
   public string Text {
      get {return _text;}
      set {_text = value;}
   }
   public string Title {
      get { return _title; }
      set { _title = value; }
   }
}

The idea is neat and simple. You create an instance of this class for each request. The first time the class will be populated with data read from the data source. At the end of the request the state of object is persisted in the viewstate. When the page posts back, a new instance of the class will be created and restored with the values found in the viewstate. Should the postback event contain code that requires a new binding to another record, the new values will overwrite the old values. This pattern is essential for data-bound controls, in ASP.NET 2.0 as well as ASP.NET 1.x. Let's see how to implement it.

The Headline control has a new property DataItem defined as follows:

private HeadlineItem _dataItem;
private HeadlineItem DataItem
{
   get
   {
      if (_dataItem == null)
      {
         _dataItem = new HeadlineItem();
         if (base.IsTrackingViewState)
            _dataItem.TrackViewState();
      }
      return _dataItem;
   }
}

The property is initialized on a per-request basis and immediately tracked for viewstate changes. Tracking the viewstate means that the control records changes to viewstate properties so that when the viewstate for the control is serialized at the end of the request all changes are correctly saved. This is an optimization measure aimed at minimizing the amount of data going to the viewstate.

Typical control properties that use the viewstate as the storage medium have a different implementation. Here's an example:

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

Why is DataItem different? And more importantly, where's the code that persists DataItem to the viewstate?

There are two ways for properties to implement viewstate management. The most common approach is the one shown for the DataTitleField property. The value of the property is read from, and saved to, the viewstate explicitly through the control's ViewState collection. This approach works great for primitive types such as strings, numbers, booleans, and dates.

Custom classes such as HeadlineItem are better to implement viewstate persistence themselves. Functionally speaking, the code below would work as well:

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

It is essential that the HeadlineItem must be marked as serializable. In fact, it will be serialized through the binary formatter and the resulting bytes stored to the viewstate. This is normally less efficient—from a space and time perspective—than implementing a custom state management layer within the class. To account for viewstate management, the implementation of HeadlineItem must be extended to implement an additional interface—IStateManager.

public class HeadlineItem : IStateManager
{
    private bool _marked;
    :
} 

A possible implementation of IStateManager is shown below:

public bool IsTrackingViewState
{
   get { return _marked; }
}
public void LoadViewState(object state)
{
   if (state != null)
   {
      Pair p = (Pair) state;
      _title = (string) p.First;
      _text = (string) p.Second;
   }
}
public object SaveViewState()
{
   return new Pair(_title, _text);
}
public void TrackViewState()
{
   _marked = true;
}      

It consists of three methods—SaveViewState, LoadViewState, TrackViewState—and a Boolean property. The first two save and restore contents from the viewstate. The last method turns on viewstate tracking. Let's focus on SaveViewState.

In this method you decide how to persist any key information contained in the class. The method is expected to return an object that will then be saved in the ViewState collection. Creating and returning this object is normally faster than using the binary formatter. What you do in the body of SaveViewState is arbitrary to some extent; what matters is that you return an object that contains the state of the object. In this case, you have only two properties to persist—Title and Text. You typically use arrays or perhaps collections. In this case, I use a Pair object, which is a special and super-optimized array of two Object elements.

The object returned by SaveViewState becomes the input argument of LoadViewState. In this method, you extract information from the state object and restore values.

At this point, you have a viewstate-serializable HeadlineItem class to fully represent any data bound to the Headline control. Note, though, that the HeadlineItem class is not the data source object. Instead, it is an intermediate buffer designed to contain information coming from the data source. The class aids the building of the control's output by storing significant data source information in a ready-to-use format. For example, you typically bind a table record to the control. Of all the columns possibly available in the record(s), only two are significant for the Headline control—one for the title and one for the text. The HeadlineItem helper class stores just this information in a viewstate-persistent way.

How would you select data source fields for building the control's user interface? In this case, you need a headline title and text. DataTitleField and DataTextField are string properties to indicate the name of the data source columns from which to read title and text for the headline control.

Performing Data Binding

Where do you fill the DataItem property of the Headline control? In other words, where do you copy information from the bound data source into an instance of the HeadlineItem helper class? You do this in the override of the PerformDataBinding method:

protected override void PerformDataBinding(IEnumerable data)
{
   IEnumerator e = data.GetEnumerator();
   e.MoveNext();

   string displayTitle = Title;
   string displayText = Text;
   if (!String.IsNullOrEmpty(DataTitleField))
    displayTitle = (string) DataBinder.GetPropertyValue(
            e.Current, DataTitleField);
   if (!String.IsNullOrEmpty(DataTextField))
   displayText = (string) DataBinder.GetPropertyValue(
            e.Current, DataTextField);
   
   DataItem.Title = displayTitle;
   DataItem.Text = displayText;
}

The Headline control is not a list control, as it only displays a single record at a time. The data argument passed to PerformDataBinding is a collection and contains as many items as there are records in the bound data source. If more than one record is bound, only the first will be processed. For a list control, you set up a for-each loop and populate a collection of HeadlineItem objects (as we'll see in the next example). For a single-record control, you get an enumerator and move to the first record.

IEnumerator e = data.GetEnumerator();
e.MoveNext();

The bound data source item is e.Current. The type of this object depends on the bound data collection. If you bound the control to a DataTable or DataView, e.Current is a DataRowView object. If you bound the control to, say, a string collection, it will be a string. If you used a custom collection, then e.Current is an instance of the custom collection element. Each type may require a different approach to read data.

The DataBinder helper class provides a few methods to read property information from virtually any data object. It provides a common API that works with any bound data. How is this possible? The DataBinder methods make extensive of .NET Reflection and query the type for a property with the specified name. The GetPropertyValue method takes the object—e.Current—and the name of the property (DataTitleField and DataTextField) and return the corresponding value in the data source record. The read values are finally stored in the DataItem for further use. If DataItem is null, a new instance will be created on the fly as the get accessor of the property shown earlier demonstrates.

Rendering

In the rendering engine of the control, you simply use DataItem properties wherever you need to display title and text of the control. It is important to note that at rendering time DataItem is guaranteed to be non-null and properly filled with data. If the logic of the host page binds the control to data, PerformDataBinding is invoked and DataItem is initialized and filled as expected. If the page undergoes a postback, no explicit data binding operation takes place. However, a postback event implies a non-null viewstate. This means that the contents DataItem had last time the page was served is stored in the viewstate. Thanks to the IStateManager methods implemented in DataItem, the viewstate contents are restored and DataItem is up and running at rendering time. The key fact is that you don't have to distinguish between explicit data binding and viewstate restoration. A step-by-step guide can be outlined as follows:

  1. Write a helper, data item(s) class that represents how the control's rendering engine needs bound data.
  2. Make this class self-serializable in the viewstate through the methods of the IStateManager interface.
  3. Define a property in the control that references the data item class.
  4. Implement PerformDataBinding, access bound data, and store values into the property that references the data item class.
  5. At rendering time, in either Render or CreateChildControls, access the data item class and build the markup. Step 2 and 4 guarantee by design that the data item will be fully functional and properly initialized at this time, no matter what postback events occur.

To use the data-bound version of the Headline control in a sample page, you can implement something similar to the following sample code:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ConnectionString='<%$ConnectionStrings:LocalNWind %>'
        SelectCommand="SELECT lastname, notes FROM employees" />
<cc1:Headline ID="Headline1" runat="server" DataSourceID="SqlDataSource1" 
        DataTextField="Notes" DataTitleField="Lastname" />

The results of the query on the Employees table are bound to the control. The field Lastname is used to bind the title of the headline control. The field Notes fills the body, as shown in Figure 2.

Aa479308.ccc3_fig02(en-us,MSDN.10).gif

Figure 2. The data-bound Headline control

List Controls

In the ASP.NET framework there are several data-bound controls to display a list of data—DropDownList, ListBox, CheckBoxList, just to name a few. List controls assign are typically bound to a data source and assign a fixed and immutable piece of user interface to each bound data item. For example, a CheckBoxList control creates a checkbox control for each bound element. What if you need to create a custom list control, that is, a control that assigns a custom piece of a control tree to each bound item? You create a new data-bound control and make it inherit from ListControl.

Most ASP.NET list controls inherit from ListControl, including CheckBoxList and the newest BulletedList controls. If you're going to create a completely custom list control—say, a HeadlineList control—ListControl is not necessarily the best class to start with. To understand why, let's consider a simpler example—a list of hyperlinks.

public class HyperLinkSimpleList : ListControl
{
   :
}

Once you inherit from ListControl you're halfway done. ListControl works by repeating a given control tree for each bound data item. The ControlToRepeat internal property specifies the repeated UI block—in this case a HyperLink.

private HyperLink _controlToRepeat;
private HyperLink ControlToRepeat
{
   get
   {
      if (_controlToRepeat == null)
      {
         _controlToRepeat = new HyperLink();
         Controls.Add(_controlToRepeat);
      }
      return _controlToRepeat;
   }
}

Next, you override the method Render to iterate on the list of bound items and repeat the control. Here's a quick example:

protected override void Render(HtmlTextWriter writer)
{
    foreach(ListItem item in Items)
    {
   HyperLink ctl = ControlToRepeat;
   ctl.Text = item.Text;
   ctl.NavigateUrl = item.Value;
      ctl.RenderControl(writer);
    }
}

No other code is required. Any downsides? Just one, but usually you can choose an alternate route. The major problem is with the representation of bound items. ListControl defines a collection property named Items. The property is of type ListItemCollection. Members of this collection are objects of type ListItem and have two string properties: Text and Value. The more complex the control tree is that you use to build the list, the less ListItem meets your programming needs. To represent a hyperlink, you typically need the following:

  • A String object for the text of the hyperlink
  • A String object for the URL to navigate
  • A String object for the tooltip
  • A String object for the target frame

You cannot use a ListItem to represent a data item with more than two properties. Not to mention the naming problem you may run across even if you restrict the data item to two properties. (For a hyperlink, it could be text and URL only.) But why use the term Value to represent the URL? That's why ListControl is fine as long as you have relatively simple control trees to repeat and data items with no more than two properties. For more complex (or simply more realistic) scenarios, you need to inherit from DataBaseControl and slightly generalize the code of Headline.

To build this custom data-bound control, let's use the five-step guide outlined earlier in the article. Step 1 and Step 2 dictate that you create a helper data items class to represent the bound item. Since the HyperLinkList is bound to a variety of data items, the class we need here must be a collection of individual data items. Here's the HyperLinkItem class that represents the individual hyperlink.

public class HyperLinkItem  
{
   private string _text;
   private string _url;
   private string _tooltip;
   public HyperLinkItem()
   {
   }
   public HyperLinkItem(string url, string text, string tooltip)
   {
      _text = text;
      _url = url;
      _tooltip = tooltip;
   }
   public string Text
   {
      get { return _text; }
      set { _text = value; }
   }
   public string Tooltip
   {
      get { return _tooltip; }
      set { _tooltip = value; }
   }
   public string Url
   {
      get { return _url; }
      set { _url = value; }
   }
}

The class counts three bindable properties to describe the hyperlink—text, URL, and tooltip. The next step consists in defining a collection class of HyperLinkItem. With generics, creating such a class is straightforward.

public class HyperLinkItemCollection : List<HyperLinkItem>
{
}

Who should implement viewstate capabilities? A list control will sport a property to group all data items that are currently bound. This property is generally called Items (arbitrary name if you don't inherit from ListControl) and is implemented as a collection.

private HyperLinkItemCollection _items;
public virtual HyperLinkItemCollection Items
{
   get
   {
      if (_items == null)
      {
         _items = new HyperLinkItemCollection();
         if (base.IsTrackingViewState)
            _items.TrackViewState();
      }
      return _items;
   }
}

As the code already shows, the collection class must implement IStateManager. Having HyperLinkItem to implement IStateManager too is certainly not wrong, but that code will never be used. Here's a possible IStateManager implementation in the collection class.

public class HyperLinkItemCollection : List<HyperLinkItem>, IStateManager
{
   private bool _marked;
   public HyperLinkItemCollection()
   {
      _marked = false;
   }
   public bool IsTrackingViewState
   {
      get { return _marked; }
   }
   public void LoadViewState(object state)
   {
      if (state != null)
      {
         Triplet t = (Triplet) state;
         Clear();

         string[] rgUrl = (string[])t.First;
         string[] rgText = (string[])t.Second;
         string[] rgTooltip = (string[])t.Third;

         for (int i = 0; i < rgUrl.Length; i++)
         {
           Add(new HyperLinkItem(rgUrl[i], 
                        rgText[i], rgTooltip[i]));
         }
      }
   }
   public object SaveViewState()
   {
      int numOfItems = Count;
      object[] rgTooltip = new string[numOfItems];
      object[] rgText = new string[numOfItems];
      object[] rgUrl = new string[numOfItems];

      for (int i = 0; i < numOfItems; i++)
      {
         rgTooltip[i] = this[i].Tooltip;
         rgText[i] = this[i].Text;
         rgUrl[i] = this[i].Url;
      }

      return new Triplet(rgUrl, rgText, rgTooltip);
   }
   public void TrackViewState()
   {
      _marked = true;
   }
}

The SaveViewState method needs to save the three item properties for each bound item. So it creates three string arrays with as many entries as there are elements in the collection. Each array receives tooltip, text, and URL respectively. The three arrays are grouped in a Triplet object and saved into the viewstate. What if you have four arrays to save? You can either use an array of four elements instead of a Triplet or, perhaps, a pair of Pair objects. How data is packed is completely up to you.

You also define three string properties to let control users specify which data source field should be used for tooltips, text, or URLs. The properties are: DataTextField, DataUrlField, and DataTooltipField. Here's a sample implementation:

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

Other data field properties have a similar implementation.

In the PerformDataBinding override, you simply populate the Items collection with the information received as an argument.

protected override void PerformDataBinding(IEnumerable dataSource)
{
  base.PerformDataBinding(dataSource);

  string urlField = DataUrlField;
  string textField = DataTextField;
  string tooltipField = DataTooltipField;

  if (dataSource != null)
  {
   foreach (object o in dataSource)
   {
        HyperLinkItem item = new HyperLinkItem();
     item.Url = DataBinder.GetPropertyValue(o, urlField, null);
     item.Text = DataBinder.GetPropertyValue(o, textField, null);
     item.Tooltip = DataBinder.GetPropertyValue(o, tooltipField, null);
     Items.Add(item);
   } 
  }
}

Let's briefly recap what we have built thus far. You have a data-bound list control with a public Items collection property that accepts HyperLinkItem objects. You can fill this collection both explicitly using the canonical methods of collections or through data binding. Each element in the Items collection originates a hyperlink. Text, URL, and tooltip are attributes of the hyperlink that can be associated with data source columns. The binding is established through a bunch of DataXXXField properties. The Items collection saves itself to the viewstate.

There are two questions still unanswered. First, how do Items restore from the viewstate? Second, how is rendering implemented?

The capabilities of the Items property to restore from the viewstate would be useless if the control didn't explicitly invoke LoadViewState and SaveViewState on the collection. This won't happen by default, though. You need to override LoadViewState and SaveViewState on the control too and order similar calls on the collection. Here's how:

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

SaveViewState returns a pair of objects—the result of the same method call on the parent class and the result of calling SaveViewState on Items. The same pair is then deserialized in LoadViewState. This means that in case of a postback the Items collection will be correctly restored from the last valid state.

The IRepeatInfoUser Interface

Many built-in list controls let you determine the final layout by acting on a few properties such as RepeatColumns, RepeatDirection, and RepeatLayout. Using these properties you can instruct the control to render horizontally or vertically for a given number of columns, by flowing output or building a table. You can add the same capabilities to your own controls too by implementing the IRepeatInfoUser interface.

The interface is a relatively simple one, made of four properties and a couple of methods. Here's a sample implementation.

bool IRepeatInfoUser.HasFooter
{
   get { return false; }
}
bool IRepeatInfoUser.HasHeader
{
   get { return false; }
}
bool IRepeatInfoUser.HasSeparators
{
   get { return false; }
}
int IRepeatInfoUser.RepeatedItemCount
{
   get { return this.Items.Count; }
}
Style IRepeatInfoUser.GetItemStyle(ListItemType itemType, int repeatIndex)
{
   return null;
}

The second method is RenderItem and works in conjunction with the Render method of the control. The Render method must explicitly support the variable layout rendering, as below:

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

The RenderRepeater method of the RepeatInfo helper class internally calls into the methods of the IRepeatInfoUser object specified as its second argument—the control itself. In the end, it calls into the following code:

void IRepeatInfoUser.RenderItem(ListItemType itemType, int repeatIndex, 
            RepeatInfo repeatInfo, HtmlTextWriter writer)
{
   HyperLink ctl = ControlToRepeat;
   int i = repeatIndex;
   ctl.ID = i.ToString();
   ctl.Text = Items[i].Text;
   ctl.NavigateUrl = Items[i].Url;
   ctl.ToolTip = Items[i].Tooltip;
   ctl.RenderControl(writer);
}

The use of an internal property named ControlToRepeat is a form of optimization. Instead of creating a new hyperlink control for each iteration, you create it only once. Note that this trick is not possible if instead of explicitly rendering out the markup you were building a control tree. In this case, in fact, you need to add to the control tree distinct instances of controls. Figure 3 shows the HyperLinkList control at design time.

Aa479308.ccc3_fig03(en-us,MSDN.10).gif

Figure 3. The HyperLinkList control in Visual Studio 2005

The following code binds an instance of the control to a SQL data source, for the results shown in Figure 4.

<cc1:HyperLinkList runat="server" ID="HyperLinkList1" 
     DataSourceID="SqlDataSource1" DataTooltipField="Notes" 
     DataTextField="Lastname" DataUrlField="Lastname" 
     RepeatColumns="3" RepeatDirection="Vertical"  />

Figure 4. The HyperLinkList control in action.

Conclusion

Data-bound controls are much easier to write in ASP.NET 2.0 than in previous versions. This is mostly due to a good number of intermediate classes that give you convenient starting points to build new controls. In this article, we examined the steps needed to build data-bound controls that are capable of displaying data from enabled sources and to persist it across postbacks. We also took a look at list controls and the advanced interfaces required to obtain rich layouts.

List controls are perhaps the simplest of data-bound controls. Iterative, grid-like, templated, and composite controls are the next step forward, and that would require another article.

 

About the author

Dino Esposito is a Solid Quality Learning mentor and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch at cutting@microsoft.com or join the blog at https://weblogs.asp.net/despos.

© Microsoft Corporation. All rights reserved.