In Depth ASP.NET using ADO.NET: Part I


In this article we will discuss a number of ways to retrieve, show, and update data with ASP.NET forms using ADO.NET. Also, we will have a clear idea about the most common server controls in ASP.NET. In particular, with this article we will cover ASP.NET server controls, ADO.NET DataSource, and creating Templated DataBound Controls, ASP.NET forms, using data with controls. We will review each of the proceeding server controls in detail. We will learn how to raise and handle some of the controls events, how to retrieve attributes from the control programmatically on the server, and finally we will demonstrate how to bind to control to a data source. Finally, we will have a complete example of retrieving, showing and updating by using server controls of ASP.NET.

Characteristically, when working with information or data from users we need to validate the data they provide us. For instance, if we have a field in our form where the user must enter his date of birth, we probably will want to control that user so that he enters a valid date. In this article we will not be validating the data collected, but we will use several server controls to handle data.

Construction Data with ASP.NET and ADO.NET

An ASP.NET server control derives directly or indirectly from System.Web.UI.Control. This base class belongs to the System.Web.UI namespace, which contains the elements common to all ASP.NET server controls. Three commonly used controls Page, UserControl, and LiteralControl are belong to System.Web.UI. Control developers generally do not instantiate Page or derive from Page. Page is important because every ASP.NET page is compiled to a Page control by the ASP.NET page framework,. Control developers also usually do not work with UserControl. User controls are developed using the same programming model as ASP.NET pages and are saved as .ascx text files. As it allows text to be encapsulated as a control, Control developers use LiteralControl widely.

IIS needs to be properly configured with FrontPage extensions for ASP.NET pages to execute. The required version of FrontPage extensions needs to be installed during the .NET SDK install or the Visual Studio .NET install. To verify this, open the Internet Information Services Manager and right-click the default Web site, and select Check Server Extensions from the All Task menu.

By extending an existing Web server control, by combining existing Web server controls, or by creating a control that derives from the base class System.Web.UI.WebControls.WebControl, we can develop a custom Web server control.

We will find typical scenarios in which we are likely to develop our own controls and provide links to other topics for further information in the following list.

We have created an ASP.NET page that provides a user interface that we want to reuse in another application. We would like to create a server control that encapsulates the user interface (UI) but we do not want to write additional code. ASP.NET allows us to save our page as a user control without writing a single additional line of code. For details, see Web Forms User Controls.

We would like to develop a compiled control that combines the functionality of two or more existing controls. For example, we need a control that encapsulates a button and a text box. We can do this using control composition, as described in Developing a Composite Control.

An existing ASP.NET server control almost meets our requirements but lacks some required features. We can customize an existing control by deriving from it and overriding its properties, methods, or events.

None of the existing ASP.NET server controls (or their combinations) meets our requirements. In that case, we can create a custom control by deriving from one of the base control classes. These classes provide all the plumbing needed by an ASP.NET server control, thus allowing us to focus on programming the features we need. To get started, see Developing Custom Controls: Key Concepts and Developing a Simple ASP.NET Server Control
Many custom controls include a combination of scenarios, where we combine custom controls that we have designed with existing ASP.NET server controls.

The ASP.NET server controls that provide a user interface are organized into two namespaces; System.Web.UI.HtmlControls and System.Web.UI.WebControls. While the Web server controls are richer and more abstract, the HTML server controls map directly to HTML elements.

Most Web server controls derive directly or indirectly from the base class System.Web.UI. WebControls.WebControl. On the other hand, the four controls in the upper-right corner (Literal, PlaceHolder, Repeater, and Xml) derive from System.Web.UI.Control. The controls on the left map to HTML elements. The controls in the center are for validating form input. Also the controls that are in the center provide rich functionality, such as the Calendar and the AdRotator controls. The controls which provide data binding support are on the right.

Before we start to talk about the server control, we will have some additional information about DataBound controls. Let's take a look at how we can develop a Templated DataBound control.

Developing a Templated DataBound Control

Using the ASP.NET data binding syntax, it is easy to bind a property of a control to a single data item (or expression). In this section we will address a more complex scenario: developing a control that has Templated properties which bind to a data source and is a collection type (System.Collections.ICollection or System.Collections.IEnumerable). Templates enable a page developer to customize the presentation of data that is bound to the control. The Repeater and DataList controls are examples of Templated DataBound controls.

A templated databound control has a data source property of type ICollection or IEnumerable and one or more properties of type ITemplate. The logical container for one of the template properties defines a property to bind data to. The control implements its databinding logic in the DataBind method that inherits from Control. It overrides ChildControlsCreated to re-create the hierarchy of child controls upon postback. These steps are explained in greater detail in the following discussion.

To develop a templated databound control, define a control that implements System.Web.UI.INamingContainer.

public class TemplatedList : WebControl, INamingContainer {...}
Define a property of type System.Web.UI.ITemplate.
[TemplateContainer(typeof(ListItemTemplated))]
public virtual ITemplate ItemTemplate
{
get
{
return itemTemplate;
}
set
{
itemTemplate = value;
}

The logical container for the template (specified in the TemplateContainerAttribute attribute) must have a property to bind data to. This property is named DataItem by convention. See Developing a Templated Control for details about logical containers for a template property. The following example defines a container for the template property.

public class ListItemTemplated : TableRow, INamingContainer
{
private object dataItem;
public virtual object DataItem
{
get
{
return dataItem;
}
set
{
dataItem = value;
}
}

To provide databinding logic, override the DataBind method (inherited from Control). We need have the following steps:

  • To invoke the handlers (attached by the page) that evaluate databinding expressions on our control, invoke the OnDataBinding method of the base class.
  • Clear the Controls collection.
  • Clear the ViewState of the child controls.
  • Create the child controls using the data source.
  • Signal to the ASP.NET page framework to track the ViewState for our control.

The following example performs these steps. To perform the actual work of creating the child controls ChildControlsCreatedHierarchy is a helper method. See step 5 for details.

public override void DataBind()
{
// Controls with a data source property perform their
// custom databinding by overriding DataBind to
// evaluate any data-binding expressions on the control
// itself.
base.OnDataBinding(EventArgs.Empty);
// Reset the control's state.
Controls.Clear();
ClearChildViewState();
// Create the control hierarchy using the data source.
CreateControlHierarchy(true);
ChildControlsCreated = true;
TrackViewState();
}

To recreate the child controls in a postback scenario, override ChildControlsCreated. This involves creating the control hierarchy using the view state instead of the data source and clearing the Controls collection. The actual work of creating the child controls is hidden in the CreateControlHierarchy method described in step 5.

protected override void ChildControlsCreated()
{
Controls.Clear();
if (ViewState["ItemCount"] != null)
{
// Create the control hierarchy using the view state
// (not the data source).
CreateControlHierarchy(false);
}
}

Identify a data source that has null elements and when creating the control hierarchy on postback use this data source instead of the real data source. Steps 3 and 4 create the controls hierarchy using the data source and the saved view state, respectively. For the common elements of these two steps a simple data source enables a control to implement a single code path.

Please keep in mind that this step (step 5) describes implementation details utilized by the databound ASP.NET controls in the .NET Framework. The BasicDataSource class and the CreateControlHierarchy method discussed below are not in the .NET Framework but have to be defined by a control developer. We are not required to implement these elements; however, it is recommended that we use this or a similar technique to provide a common code path for creating the control hierarchy.

The following code fragment defines a simple data source.

internal sealed class BasicDataSource : ICollection
{
private int dataItemCount;
public BasicDataSource(int dataItemCount)
{
this.dataItemCount = dataItemCount;
}
// Implement other methods of the ICollection interface.
public IEnumerator GetEnumerator()
{
return new BasicDataSourceEnumerator(dataItemCount);
}
private class BasicDataSourceEnumerator : IEnumerator
{
private int count;
private int index;
public BasicDataSourceEnumerator(int count)
{
this.count = count;
this.index = -1;
}
public object Current
{
get
{
return null;
}
}
// Define other methods of the IEnumerator interface.
}

BasicDataSource can be used to define the CreateControlHierarchy method, as follows.

private void CreateControlHierarchy(bool useDataSource)
{
IEnumerable dataSource = null;
int count = -1;
if (useDataSource == false)
{
// ViewState must have a non-null value for ItemCount because this is checked
// in ChildControlsCreated.
count = (int)ViewState["ItemCount"];
if (count != -1)
{
dataSource = new BasicDataSource(count);
}
}
else
{
dataSource = this.dataSource;
}
if (dataSource != null)
{
int index = 0;
count = 0;
foreach (object dataItem in dataSource)
{
// Invoke a private helper method to create each item.
CreateItem(...);
count++;
index++;
}
}
if (useDataSource)
{
// Save the number of items contained for use in round trips.
ViewState["ItemCount"] = ((dataSource != null) ? count : -1);
}
}

The actual work of instantiating the template and binding the DataItem property to the data source is done by the CreateItem method. How the CreateItem method is implemented in the Templated Databound Control Sample is shown in the following code fragment. Note that the CreateItem method is an implementation detail and is not defined in the .NET Framework.

private ListItemTemplated CreateItem(Table table, int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
{
ListItemTemplated item = new ListItemTemplated(itemIndex, itemType);
ListItemTemplatedEventArgs e = new ListItemTemplatedEventArgs(item);
if (itemTemplate != null)
{
itemTemplate.InstantiateIn(item.Cells[0]);
}
if (dataBind)
{
item.DataItem = dataItem;
}
OnItemCreated(e);
table.Rows.Add(item);
if (dataBind)
{
item.DataBind();
OnItemDataBound(e);
item.DataItem = null;
}
return item;
}
Finally here is a complete list:

// TemplatedList.cs.
namespace CustomControls
{
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Diagnostics;
using System.Web.UI;
using System.Web.UI.WebControls;
DefaultEvent("SelectedIndexChanged"),
DefaultProperty("DataSource"),
Designer("CustomControls.Design.TemplatedListDesigner, CustomControls.Design", typeof(IDesigner))
public class TemplatedList : WebControl, INamingContainer
{
#region Statics and Constants
private static readonly object EventSelectedIndexChanged = new object();
private static readonly object EventItemCreated = new object();
private static readonly object EventItemDataBound = new object();
private static readonly object EventItemCommand = new object();
#endregion
#region
Member variables
private IEnumerable dataSource;
private TableItemStyle itemStyle;
private TableItemStyle alternatingItemStyle;
private TableItemStyle selectedItemStyle;
private ITemplate itemTemplate;
#endregion
#region
Properties
[
Category("Style"),
Description("The style to be applied to alternate items."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty),
]
public virtual TableItemStyle AlternatingItemStyle
{
get
{
if (alternatingItemStyle == null)
{
alternatingItemStyle = new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)alternatingItemStyle).TrackViewState();
}
return alternatingItemStyle;
}
}
[
Bindable(true),
Category("Appearance"),
DefaultValue(-1),
Description("The cellpadding of the rendered table.")
]
public virtual int CellPadding
{
get
{
if (ControlStyleCreated == false)
{
return -1;
}
return ((TableStyle)ControlStyle).CellPadding;
}
set
{
((TableStyle)ControlStyle).CellPadding = value;
}
}
[
Bindable(true),
Category("Appearance"),
DefaultValue(0),
Description("The cellspacing of the rendered table.")
]
public virtual int CellSpacing
{
get
{
if (ControlStyleCreated == false)
{
return 0;
}
return ((TableStyle)ControlStyle).CellSpacing;
}
set
{
((TableStyle)ControlStyle).CellSpacing = value;
}
}
[
Bindable(true),
Category("Data"),
DefaultValue(null),
Description("The data source used to build up the control."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public IEnumerable DataSource
{
get
{
return dataSource;
}
set
{
dataSource = value;
}
}
[
Bindable(true),
Category("Appearance"),
DefaultValue(GridLines.None),
Description("The grid lines to be exposed in the rendered table.")
]
public virtual GridLines GridLines
{
get
{
if (ControlStyleCreated == false)
{
return GridLines.None;
}
return ((TableStyle)ControlStyle).GridLines;
}
set
{
((TableStyle)ControlStyle).GridLines = value;
}
}
[
Category("Style"),
Description("The style to be applied to all items."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty),
]
public virtual TableItemStyle ItemStyle
{
get
{
if (itemStyle == null)
{
itemStyle = new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)itemStyle).TrackViewState();
}
return itemStyle;
}
}
[
Browsable(false),
DefaultValue(null),
Description("The content to be shown in each item."),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(ListItemTemplated))
]
public virtual ITemplate ItemTemplate
{
get
{
return itemTemplate;
}
set
{
itemTemplate = value;
}
}
[
Bindable(true),
DefaultValue(-1),
Description("The index of the selected item.")
]
public virtual int SelectedIndex
{
get
{
object o = ViewState["SelectedIndex"];
if (o != null)
return(int)o;
return -1;
}
set
{
if (value < -1)
{
throw new ArgumentOutOfRangeException();
}
int oldSelectedIndex = SelectedIndex;
ViewState["SelectedIndex"] = value;
if (HasControls())
{
Table table = (Table)Controls[0];
ListItemTemplated item;
if ((oldSelectedIndex != -1) && (table.Rows.Count > oldSelectedIndex))
{
item = (ListItemTemplated)table.Rows[oldSelectedIndex];
if (item.ItemType != ListItemType.EditItem)
{
ListItemType itemType = ListItemType.Item;
if (oldSelectedIndex % 2 != 0)
itemType = ListItemType.AlternatingItem;
item.SetItemType(itemType);
}
}
if ((value != -1) && (table.Rows.Count > value))
{
item = (ListItemTemplated)table.Rows[value];
item.SetItemType(ListItemType.SelectedItem);
}
}
}
}
[
Category("Style"),
Description("The style to be applied to the selected item."),
DesignerSerializationVisibility
(DesignerSerializationVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty),
]
public virtual TableItemStyle SelectedItemStyle
{
get
{
if (selectedItemStyle == null)
{
selectedItemStyle = new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)selectedItemStyle).TrackViewState();
}
return selectedItemStyle;
}
}
#endregion
#region
Events
protected virtual void OnItemCommand(TemplatedListCommandEventArgs e)
{
TemplatedListCommandEventHandler onItemCommandHandler = (TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}
protected virtual void OnItemCreated(ListItemTemplatedEventArgs e)
{
ListItemTemplatedEventHandler onItemCreatedHandler = (ListItemTemplatedEventHandler)Events[EventItemCreated];
if (onItemCreatedHandler != null) onItemCreatedHandler(this, e);
}
protected virtual void OnItemDataBound(ListItemTemplatedEventArgs e)
{
ListItemTemplatedEventHandler onItemDataBoundHandler = (ListItemTemplatedEventHandler)Events[EventItemDataBound];
if (onItemDataBoundHandler != null) onItemDataBoundHandler(this, e);
}
protected virtual void OnSelectedIndexChanged(EventArgs e)
{
EventHandler handler = (EventHandler)Events[EventSelectedIndexChanged];
if (handler != null) handler(this, e);
}
[
Category("Action"),
Description("Raised when a CommandEvent occurs within an item.")
]
public event TemplatedListCommandEventHandler ItemCommand
{
add
{
Events.AddHandler(EventItemCommand, value);
}
remove
{
Events.RemoveHandler(EventItemCommand, value);
}
}
[
Category("Behavior"),
Description("Raised when an item is created and is ready for
customization.")
]
public event ListItemTemplatedEventHandler ItemCreated
{
add
{
Events.AddHandler(EventItemCreated, value);
}
remove
{
Events.RemoveHandler(EventItemCreated, value);
}
}
[
Category("Behavior"),
Description("Raised when an item is databound.")
]
public event ListItemTemplatedEventHandler ItemDataBound
{
add
{
Events.AddHandler(EventItemDataBound, value);
}
remove
{
Events.RemoveHandler(EventItemDataBound, value);
}
}
[
Category("Action"),
Description("Raised when the SelectedIndex property has
changed.")
]
public event EventHandler SelectedIndexChanged
{
add
{
Events.AddHandler(EventSelectedIndexChanged, value);
}
remove
{
Events.RemoveHandler(EventSelectedIndexChanged, value);
}
}
#endregion
#region
Methods and Implementation
protected override void ChildControlsCreated()
{
Controls.Clear();
if (ViewState["ItemCount"] != null)
{
// create the control hierarchy using the view state (and
// not the datasource)
CreateControlHierarchy(false);
}
}
private void CreateControlHierarchy(bool useDataSource)
{
IEnumerable dataSource = null;
int count = -1;
if (useDataSource == false)
{
// ViewState must have a non-null value for ItemCount because this is
// checked
// in ChildControlsCreated
count = (int)ViewState["ItemCount"];
if (count != -1)
{
dataSource = new BasicDataSource(count);
}
}
else
{
dataSource = this.dataSource;
}
if (dataSource != null)
{
Table table = new Table();
Controls.Add(table);
int selectedItemIndex = SelectedIndex;
int index = 0;
count = 0;
foreach (object dataItem in dataSource)
{
ListItemType itemType = ListItemType.Item;
if (index == selectedItemIndex)
{
itemType = ListItemType.SelectedItem;
}
else if (index % 2 != 0)
{
itemType = ListItemType.AlternatingItem;
}
CreateItem(table, index, itemType, useDataSource, dataItem);
count++;
index++;
}
}
if (useDataSource)
{
// Save the number of items contained for use in round-trips.
ViewState["ItemCount"] = ((dataSource != null) ? count : -1);
}
}
protected override Style CreateControlStyle()
{
// Since the TemplatedList control renders an HTML table,
// an instance of a TableStyle is used as the control style.
TableStyle style = new TableStyle(ViewState);
// Set up default initial state.
style.CellSpacing = 0;
return style;
}
private ListItemTemplated CreateItem(Table table, int itemIndex,
ListItemType itemType, bool dataBind, object dataItem)
{
ListItemTemplated item = new ListItemTemplated(itemIndex, itemType);
ListItemTemplatedEventArgs e = new ListItemTemplatedEventArgs(item);
if (itemTemplate != null)
{
itemTemplate.InstantiateIn(item.Cells[0]);
}
if (dataBind)
{
item.DataItem = dataItem;
}
OnItemCreated(e);
table.Rows.Add(item);
if (dataBind)
{
item.DataBind();
OnItemDataBound(e);
item.DataItem = null;
}
return item;
}
public override void DataBind()
{
// Controls with a data-source property perform their custom databinding// by overriding DataBind.
// Evaluate any databinding expressions on the control itself.
base.OnDataBinding(EventArgs.Empty);
// Reset the control state.
Controls.Clear();
ClearChildViewState();
// Create the control hierarchy using the data source.
CreateControlHierarchy(true);
ChildControlsCreated = true;
TrackViewState();
}
protected override void LoadViewState(object savedState)
{
// Customized state management to handle saving state of contained
// objects.
if (savedState != null)
{
object[] FirstState = (object[])savedState;
if (FirstState[0] != null)
base.LoadViewState(FirstState[0]);
if (FirstState[1] != null)
((IStateManager)ItemStyle).LoadViewState(FirstState[1]);
if (FirstState[2] != null)
((IStateManager)SelectedItemStyle).LoadViewState(FirstState[2]);
if (FirstState[3] != null)
((IStateManager)AlternatingItemStyle).LoadViewState(FirstState[3]);
}
}
protected override bool OnBubbleEvent(object source, EventArgs e)
{
// Handle events raised by children by overriding OnBubbleEvent.
bool handled = false;
if (e is TemplatedListCommandEventArgs)
{
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;
OnItemCommand(ce);
handled = true;
if (String.Compare(ce.CommandName, "Select", true) == 0)
{
SelectedIndex = ce.Item.ItemIndex;
OnSelectedIndexChanged(EventArgs.Empty);
}
}
return handled;
}
private void PrepareControlHierarchy()
{
if (HasControls() == false)
{
return;
}
Debug.Assert(Controls[0] is Table);
Table table = (Table)Controls[0];
table.CopyBaseAttributes(this);
if (ControlStyleCreated)
{
table.ApplyStyle(ControlStyle);
}
// The composite alternating item style, so do just one
// merge style on the actual item.
Style altItemStyle = null;
if (alternatingItemStyle != null)
{
altItemStyle = new TableItemStyle();
altItemStyle.CopyFrom(itemStyle);
altItemStyle.CopyFrom(alternatingItemStyle);
}
else
{
altItemStyle = itemStyle;
}
int rowCount = table.Rows.Count;
for (int i = 0; i < rowCount; i++)
{
ListItemTemplated item = (ListItemTemplated)table.Rows[i];
Style compositeStyle = null;
switch (item.ItemType)
{
case ListItemType.Item:
compositeStyle = itemStyle;
break;
case ListItemType.AlternatingItem:
compositeStyle = altItemStyle;
break;
case ListItemType.SelectedItem:
{
compositeStyle = new TableItemStyle();
if (item.ItemIndex % 2 != 0)
compositeStyle.CopyFrom(altItemStyle);
else
compositeStyle.CopyFrom(itemStyle);
compositeStyle.CopyFrom(selectedItemStyle);
}
break;
}
if (compositeStyle != null)
{
item.MergeStyle(compositeStyle);
}
}
}
protected override void Render(HtmlTextWriter writer)
{
// Apply styles to the control hierarchy
// and then render it out.
// Apply styles during render phase, so the user can change styles
// after calling DataBind, without the property changes ending
// up in view state.
PrepareControlHierarchy();
RenderContents(writer);
}
protected override object SaveViewState()
{
// Customized state management to handle saving state of contained
// objects like styles.
object baseState = base.SaveViewState();
object itemStyleState = (itemStyle != null) ? ((IStateManager)itemStyle).SaveViewState() : null;
object selectedItemStyleState = (selectedItemStyle != null)
? ((IStateManager)selectedItemStyle).SaveViewState() : null;
object alternatingItemStyleState = (alternatingItemStyle != null)
? ((IStateManager)alternatingItemStyle).SaveViewState() : null;
object[] FirstState = new object[4];FirstState[0] = baseState;
FirstState[1] = itemStyleState;
FirstState[2] = selectedItemStyleState;
FirstState[3] = alternatingItemStyleState;
return FirstState;
}
protected override void TrackViewState()
{
// Customized state management to handle saving state of contained
//objects like styles.
base.TrackViewState();
if (itemStyle != null)
((IStateManager)itemStyle).TrackViewState();
if (selectedItemStyle != null)
((IStateManager)selectedItemStyle).TrackViewState();
if (alternatingItemStyle != null)
((IStateManager)alternatingItemStyle).TrackViewState();
}
#endregion

public class ListItemTemplated : TableRow, INamingContainer
{
private int itemIndex;
private ListItemType itemType;
private object dataItem;
public ListItemTemplated(int itemIndex, ListItemType itemType)
{
this.itemIndex = itemIndex;
this.itemType = itemType;
Cells.Add(new TableCell());
}
public virtual object DataItem
{
get
{
return dataItem;
}
set
{
dataItem = value;
}
}
public virtual int ItemIndex
{
get
{
return itemIndex;
}
}
public virtual ListItemType ItemType
{
get
{
return itemType;
}
}
protected override bool OnBubbleEvent(object source, EventArgs e)
{
if (e is CommandEventArgs)
{
// Add the information about an Item to the CommandEvent.
TemplatedListCommandEventArgs args =
new TemplatedListCommandEventArgs(this, source, (CommandEventArgs)e);
RaiseBubbleEvent(this, args);
return true;
}
return false;
}
internal void SetItemType(ListItemType itemType)
{
this.itemType = itemType;
}
}
public sealed class TemplatedListCommandEventArgs : CommandEventArgs
{
private ListItemTemplated item;
private object commandSource;
public TemplatedListCommandEventArgs(ListItemTemplated item, object commandSource, CommandEventArgs originalArgs) :
base(originalArgs)
{
this.item = item;
this.commandSource = commandSource;
}
public ListItemTemplated Item
{
get
{
return item;
}
}
public object CommandSource
{
get

{
return commandSource;
}
}
}
public delegate void TemplatedListCommandEventHandler(object source, TemplatedListCommandEventArgs e);
public sealed class ListItemTemplatedEventArgs : EventArgs
{
private ListItemTemplated item;
public ListItemTemplatedEventArgs(ListItemTemplated item)
{
this.item = item;
}
public ListItemTemplated Item
{
get
{
return item;
}
}
}
public delegate void ListItemTemplatedEventHandler
(object sender, ListItemTemplatedEventArgs e);
internal sealed class BasicDataSource : ICollection
{
private int dataItemCount;
public BasicDataSource(int dataItemCount)
{
this.dataItemCount = dataItemCount;
}
public int Count
{
get
{
return dataItemCount;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public bool IsSynchronized
{
get
{
return false;
}
}
public object SyncRoot
{
get
{
return this;
}
}
public void CopyTo(Array array, int index)
{
for (IEnumerator e = this.GetEnumerator(); e.MoveNext();)
array.SetValue(e.Current, index++);
}
public IEnumerator GetEnumerator()
{
return new BasicDataSourceEnumerator(dataItemCount);
}
private class BasicDataSourceEnumerator : IEnumerator
{
private int count;
private int index;
public BasicDataSourceEnumerator(int count)
{
this.count = count;

this.index = -1;
}
public object Current
{
get
{
return null;
}
}
public bool MoveNext()
{
index++;
return index < count;
}
public void Reset()
{
this.index = -1;

private ListItemTemplated CreateItem(Table table, int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
{
ListItemTemplated item =
new ListItemTemplated(itemIndex, itemType);
ListItemTemplatedEventArgs e =
new ListItemTemplatedEventArgs(item);
if (itemTemplate != null)
{
itemTemplate.InstantiateIn(item.Cells[0]);
}
if (dataBind)
{
item.DataItem = dataItem;
}
OnItemCreated(e);
table.Rows.Add(item);
if (dataBind)
{
item.DataBind();
OnItemDataBound(e);
item.DataItem =
null;
}
return item;
}
Finally here
is a complete list:
// TemplatedList.cs.
namespace CustomControls

{
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Diagnostics;
using System.Web.UI;
using System.Web.UI.WebControls;
DefaultEvent("SelectedIndexChanged"),
DefaultProperty("DataSource"),
Designer("CustomControls.Design.TemplatedListDesigner, CustomControls.Design",

typeof
(IDesigner))
]
public class TemplatedList : WebControl, INamingContainer
{#region Statics and Constants

private
static readonly object EventSelectedIndexChanged = new object();
private static readonly object EventItemCreated = new object();
private static readonly object EventItemDataBound = new object();
private static readonly object EventItemCommand = new object();
#endregion
#region
Member variables
private IEnumerable dataSource;
private TableItemStyle itemStyle;
private TableItemStyle alternatingItemStyle;
private TableItemStyle selectedItemStyle;
private ITemplate itemTemplate;
#endregion
#region Properties
Category("Style"),
Description("The style to be applied to alternate items."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(
true),
PersistenceMode(PersistenceMode.InnerProperty),
public virtual TableItemStyle AlternatingItemStyle
{
get
{
if (alternatingItemStyle == null)
{
alternatingItemStyle =
new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)alternatingItemStyle).TrackViewState();
}
return alternatingItemStyle;
}
}
}
Bindable(
true),
Category("Appearance"),DefaultValue(-1),
Description("The cellpadding of the rendered table.")
public virtual int CellPadding
{
get
{
if (ControlStyleCreated == false)
{
return -1;
}
return ((TableStyle)ControlStyle).CellPadding;
}
set
{
((TableStyle)ControlStyle).CellPadding =
value;
}
}
[
Bindable(
true),
Category("Appearance"),
DefaultValue(0),
Description("The cellspacing of the rendered table.")
public virtual int CellSpacing
{
get
{
if (ControlStyleCreated == false)
{
return 0;

}
return ((TableStyle)ControlStyle).CellSpacing;

}
set
{
((TableStyle)ControlStyle).CellSpacing =
value;
}
}
[
Bindable(
true),
Category("Data"),
DefaultValue(
null),
Description("The data source used to build up the control."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public IEnumerable DataSource
{
get
{
return dataSource;
}

set
{
dataSource =
value;
}
}
[
Bindable(
true),
Category("Appearance"),
DefaultValue(GridLines.None),
Description("The grid lines to be exposed in the rendered table.")
]
public virtual GridLines GridLines
{
get
{

if (ControlStyleCreated == false)
{
return GridLines.None;
}
return ((TableStyle)ControlStyle).GridLines;
}
set
{
((TableStyle)ControlStyle).GridLines =
value;
}

}
[
Category("Style"),
Description("The style to be applied to all items."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(
true),
PersistenceMode(PersistenceMode.InnerProperty),
]
public virtual TableItemStyle ItemStyle

{
get
{
if (itemStyle == null)
{
itemStyle =
new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)itemStyle).TrackViewState();
}
return itemStyle;
}
}
[
Browsable(
false),
DefaultValue(
null),Description("The content to be shown in each item."),

PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(
typeof(ListItemTemplated))
]
public virtual ITemplate ItemTemplate
{
get
{
return itemTemplate;
}
set
{
itemTemplate =
value;
}
}
[
Bindable(
true),
DefaultValue(-1),
Description("The index of the selected item.")
public virtual int SelectedIndex
{
get
{
object o = ViewState["SelectedIndex"];
if (o != null)
return(int)o;
return -1;
}
set
{

if (value < -1)
{
throw new ArgumentOutOfRangeException();
}
int oldSelectedIndex = SelectedIndex;
ViewState["SelectedIndex"] =
value;
if (HasControls())
{
Table table = (Table)Controls[0];
ListItemTemplated item;
if ((oldSelectedIndex != -1) && (table.Rows.Count > oldSelectedIndex))
{
item = (ListItemTemplated)table.Rows[oldSelectedIndex];
if (item.ItemType != ListItemType.EditItem)
{
ListItemType itemType = ListItemType.Item;
if (oldSelectedIndex % 2 != 0)
itemType = ListItemType.AlternatingItem;
item.SetItemType(itemType);}

}
if ((value != -1) && (table.Rows.Count > value)) {
item = (ListItemTemplated)table.Rows[
value];
item.SetItemType(ListItemType.SelectedItem);
}
}
}
}
[
Category("Style"),
Description("The style to be applied to the selected item."),
DesignerSerializationVisibility
(DesignerSerializationVisibility.Content),
NotifyParentProperty(
true),
PersistenceMode(PersistenceMode.InnerProperty),
]
public virtual TableItemStyle SelectedItemStyle
{
get
{
if (selectedItemStyle == null)
{
selectedItemStyle =
new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)selectedItemStyle).TrackViewState();}

return
selectedItemStyle;
}
}
#endregion
#region
Events
protected virtual void OnItemCommand(TemplatedListCommandEventArgs e)
{
TemplatedListCommandEventHandler onItemCommandHandler =
TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}
protected virtual void OnItemCreated(ListItemTemplatedEventArgs e)
{
ListItemTemplatedEventHandler onItemCreatedHandler =
ListItemTemplatedEventHandler)Events[EventItemCreated];
if (onItemCreatedHandler != null) onItemCreatedHandler(this, e);
}
protected virtual void OnItemDataBound(ListItemTemplatedEventArgs e)
{
ListItemTemplatedEventHandler onItemDataBoundHandler =
ListItemTemplatedEventHandler)Events[EventItemDataBound];
if (onItemDataBoundHandler != null) onItemDataBoundHandler(this, e);
}
protected virtual void OnSelectedIndexChanged(EventArgs e)
{
EventHandler handler = (EventHandler)Events[EventSelectedIndexChanged];
if (handler != null) handler(this, e);
}
[
Category("Action"),Description("Raised when a CommandEvent occurs within an
item.")
]
public event TemplatedListCommandEventHandler ItemCommand
{
add
{
Events.AddHandler(EventItemCommand,
value);
}
remove
{
Events.RemoveHandler(EventItemCommand,
value);
}
}
[
Category("Behavior"),Description("Raised when an item is created and is ready for
customization.")
]
public event ListItemTemplatedEventHandler ItemCreated
{
add
{
Events.AddHandler(EventItemCreated,
value);
}
remove
{
Events.RemoveHandler(EventItemCreated,
value);
}
}
[
Category("Behavior"),
Description("Raised when an item is databound.")
public event ListItemTemplatedEventHandler ItemDataBound
{
add
{
Events.AddHandler(EventItemDataBound,
value);
}
remove
{
Events.RemoveHandler(EventItemDataBound,
value);
}
}[

Category("Action"),
Description("Raised when the SelectedIndex property has
changed.")
]
public event EventHandler SelectedIndexChanged
{
add
{
Events.AddHandler(EventSelectedIndexChanged,
value);
}
remove
{
Events.RemoveHandler(EventSelectedIndexChanged,
value);
}
}
#endregion
#region
Methods and Implementation
protected override void ChildControlsCreated()
{
Controls.Clear();
if (ViewState["ItemCount"] != null)
{
// create the control hierarchy using the view state (and
// not the datasource)
CreateControlHierarchy(false);
}
}
private void CreateControlHierarchy(bool useDataSource)

{
IEnumerable dataSource =
null;
int count = -1;
if (useDataSource == false)
{
// ViewState must have a non-null value for ItemCount because this is
// checked
// in ChildControlsCreated
count = (int)ViewState["ItemCount"];if (count != -1)
{
dataSource =
new BasicDataSource(count);
}

else 
{
dataSource = this.dataSource;
}
if (dataSource != null)
{
Table table = new Table();
Controls.Add(table);
int selectedItemIndex = SelectedIndex;
int index = 0;
count = 0;
foreach (object dataItem in dataSource)
{
ListItemType itemType = ListItemType.Item;
if (index == selectedItemIndex)
{
itemType = ListItemType.SelectedItem;
}
else if (index % 2 != 0)
{
itemType = ListItemType.AlternatingItem;
}
CreateItem(table, index, itemType, useDataSource, dataItem);
count++;
index++;
}
}
if (useDataSource)
{
// Save the number of items contained for use in round-trips.
ViewState["ItemCount"] = ((dataSource != null) ? count : -1);
}
}
protected override Style CreateControlStyle()
{
// Since the TemplatedList control renders an HTML table,
// an instance of a TableStyle is used as the control style.
TableStyle style = new TableStyle(ViewState);
// Set up default initial state.
style.CellSpacing = 0;
return style;
}
private ListItemTemplated CreateItem(Table table, int itemIndex,
ListItemType itemType, bool dataBind, object dataItem)
{
ListItemTemplated item = new ListItemTemplated(itemIndex, itemType);
ListItemTemplatedEventArgs e = new ListItemTemplatedEventArgs(item);
if (itemTemplate != null)
{
itemTemplate.InstantiateIn(item.Cells[0]);
}
if (dataBind)
{
item.DataItem = dataItem;
}
OnItemCreated(e);
table.Rows.Add(item);
if (dataBind)
{
item.DataBind();
OnItemDataBound(e);
item.DataItem = null;
}
return item;
}
public override void DataBind()
{
// Controls with a data-source property perform their custom databinding
// by overriding DataBind.
// Evaluate any databinding expressions on the control itself.
base.OnDataBinding(EventArgs.Empty);
// Reset the control state.
Controls.Clear();
ClearChildViewState();
// Create the control hierarchy using the data source.
CreateControlHierarchy(true);
ChildControlsCreated = true;
TrackViewState();
}
protected override void LoadViewState(object savedState)
{
// Customized state management to handle saving state of contained
// objects.
if (savedState != null)
{
object[] FirstState = (object[])savedState;
if (FirstState[0] != null)
base.LoadViewState(FirstState[0]);
if (FirstState[1] != null)
((IStateManager)ItemStyle).LoadViewState(FirstState[1]);
if (FirstState[2] != null)
((IStateManager)SelectedItemStyle).LoadViewState(FirstState[2]);
if (FirstState[3] != null)
((IStateManager)AlternatingItemStyle).LoadViewState(FirstState[3]);
}
}
protected override bool OnBubbleEvent(object source, EventArgs e)
{
// Handle events raised by children by overriding OnBubbleEvent.
bool handled = false;
if (e is TemplatedListCommandEventArgs)
{
TemplatedListCommandEventArgs ce =
(TemplatedListCommandEventArgs)e;
OnItemCommand(ce);
handled = true;
if (String.Compare(ce.CommandName, "Select", true) == 0)
{
SelectedIndex = ce.Item.ItemIndex;
OnSelectedIndexChanged(EventArgs.Empty);
}
}
return handled;
}
private void PrepareControlHierarchy()
{
if (HasControls() == false)
{
return;
}Debug.Assert(Controls[0] is Table);
Table table = (Table)Controls[0];
table.CopyBaseAttributes(this);
if (ControlStyleCreated)
{
table.ApplyStyle(ControlStyle);
}
// The composite alternating item style, so do just one
// merge style on the actual item.
Style altItemStyle = null;
if (alternatingItemStyle != null)
{
altItemStyle = new TableItemStyle();
altItemStyle.CopyFrom(itemStyle);
altItemStyle.CopyFrom(alternatingItemStyle);
}
else
{
altItemStyle = itemStyle;
}
int rowCount = table.Rows.Count;
for (int i = 0; i < rowCount; i++)
{
ListItemTemplated item = (ListItemTemplated)table.Rows[i];
Style compositeStyle = null;
switch (item.ItemType)
{
case ListItemType.Item:
compositeStyle = itemStyle;
break;
case ListItemType.AlternatingItem:
compositeStyle = altItemStyle;
break;
case ListItemType.SelectedItem:
{
compositeStyle = new TableItemStyle();
if (item.ItemIndex % 2 != 0)
compositeStyle.CopyFrom(altItemStyle);
else
compositeStyle.CopyFrom(itemStyle);
compositeStyle.CopyFrom(selectedItemStyle);
}
break;
}
if (compositeStyle != null)
{
item.MergeStyle(compositeStyle);
}
}
}
protected override void Render(HtmlTextWriter writer)
{
// Apply styles to the control hierarchy
// and then render it out.
// Apply styles during render phase, so the user can change styles
// after calling DataBind, without the property changes ending
// up in view state.
PrepareControlHierarchy();
RenderContents(writer);
}
protected override object SaveViewState()
{
// Customized state management to handle saving state of contained
// objects like styles.
object baseState = base.SaveViewState();
object itemStyleState = (itemStyle != null) ? ((IStateManager)itemStyle).SaveViewState() :
null;
object selectedItemStyleState = (selectedItemStyle != null)
? ((IStateManager)selectedItemStyle).SaveViewState() : null;
object alternatingItemStyleState = (alternatingItemStyle != null)
? ((IStateManager)alternatingItemStyle).SaveViewState() : null;
object[] FirstState = new object[4];
FirstState[0] = baseState;
FirstState[1] = itemStyleState;
FirstState[2] = selectedItemStyleState;
FirstState[3] = alternatingItemStyleState;
return FirstState;
}
protected override void TrackViewState()
{
// Customized state management to handle saving state of contained
//objects like styles.
base.TrackViewState();
if (itemStyle != null)
(IStateManager)itemStyle).TrackViewState();
if (selectedItemStyle != null)
(IStateManager)selectedItemStyle).TrackViewState();
if (alternatingItemStyle != null)
((IStateManager)alternatingItemStyle).TrackViewState();
}
#endregion
}
public class ListItemTemplated : TableRow, INamingContainer
{
private int itemIndex;
private ListItemType itemType;
private object dataItem;
public ListItemTemplated(int itemIndex, ListItemType itemType)
{
this.itemIndex = itemIndex;
this.itemType = itemType;
Cells.Add(new TableCell());
}
public virtual object DataItem
{
get
{
return dataItem;
}
set
{
dataItem = value;
}
}
public virtual int ItemIndex
{
get
{
return itemIndex;
}
}
public virtual ListItemType ItemType
{
get
{
return itemType;
}
}
protected override bool OnBubbleEvent(object source, EventArgs e)
{
if (e is CommandEventArgs)
{
// Add the information about an Item to the CommandEvent.
TemplatedListCommandEventArgs args =
ew TemplatedListCommandEventArgs(this, source, (CommandEventArgs)e);
RaiseBubbleEvent(this, args);
return true;
}
return false;
}
internal void SetItemType(ListItemType itemType)
{
this.itemType = itemType;
}
}
public sealed class TemplatedListCommandEventArgs : CommandEventArgs
{
private ListItemTemplated item;
private object commandSource;
public TemplatedListCommandEventArgs(ListItemTemplated item, object
CommandSource, CommandEventArgs originalArgs) :
base(originalArgs)
{
this.item = item;
this.commandSource = commandSource;
}
public ListItemTemplated Item
{
get
{
return item;
}
}
public object CommandSource
{
get
{
return commandSource;
}
}
}
public delegate void TemplatedListCommandEventHandler(object source,
TemplatedListCommandEventArgs e);
public sealed class ListItemTemplatedEventArgs : EventArgs
{
private ListItemTemplated item;
public ListItemTemplatedEventArgs(ListItemTemplated item)
{
this.item = item;
}
public ListItemTemplated Item
{
get
{
return item;
}
}
}
public delegate void ListItemTemplatedEventHandler
(object sender, ListItemTemplatedEventArgs e);
internal sealed class BasicDataSource : ICollection
{
private int dataItemCount;
public BasicDataSource(int dataItemCount)
{
this.dataItemCount = dataItemCount;
}
public int Count
{
get
{
return dataItemCount;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public bool IsSynchronized
{
get
{
return false;
}
}
public object SyncRoot
{
get
{
return this;
}
}
public void CopyTo(Array array, int index)
{
for (IEnumerator e = this.GetEnumerator(); e.MoveNext();)
array.SetValue(e.Current, index++);
}
public IEnumerator GetEnumerator()
{
return new BasicDataSourceEnumerator(dataItemCount);
}
private class BasicDataSourceEnumerator : IEnumerator
{
private int count;
private int index;
public BasicDataSourceEnumerator(int count)
{
this.count = count;
this.index = -1;
}
public object Current
{
get
{
return null;
}
}
public bool MoveNext()
{
index++;
return index < count;
}
public void Reset()
{
this.index = -1;
}
}
}
}
}

continue article