Tải bản đầy đủ (.pdf) (77 trang)

Pro Server Controls and AJAX Components phần 5 pptx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.21 MB, 77 trang )

CHAPTER 7 ■ SERVER CONTROL DATA BINDING
283
• System.Web.UI.WebControls.DataBoundControl: This can serve as a base class when
displaying data in list or tabular form. The designer DataBoundControlDesigner class is
configured on this base class via the Designer attribute.
• System.Web.UI.WebControls.CompositeDataBoundControl: This class inherits from
DataBoundControl and can serve as the base class for tabular data bound controls that
are composed of other server controls.
• System.Web.UI.WebControls.HierarchicalDataBoundControl: This one can serve
as a base class to create data bound controls that work with classes that implement
the IHierarchicalDataSource interface and classes that derive from the
HierarchicalDataSourceControl and HierarchicalDataSourceView classes.
There certainly may be scenarios where complete control is required and the preceding
base classes are limiting in some way, in which case a control developer can always simply
inherit from Control or WebControl. Otherwise, we recommend that developers consider these
base classes as a first option, since inheriting from them can save time. In the next section, we
take a look at a sample control that inherits from the DataBoundControl base class.
The Repeater Control
The case study we present to help explain data binding creates a replica of the Repeater control
built into ASP.NET. The Repeater control is a data-bound server control that takes advantage of
templates to generate the display for the data source. It is a complex control that requires a fair
amount of source code, but this effort is worth the ease of use data binding provides to the user
of a data bindable server control.
The Repeater control includes five templates: HeaderTemplate, FooterTemplate,
SeparatorTemplate, ItemTemplate, and AlternatingItemTemplate. We provided the first three
templates types in our TemplateMenu control. For clarity, those three templates do not take
advantage of data binding. We are adding data binding capabilities to the ItemTemplate and
AlternatingItemTemplate templates.
The ItemTemplate and AlternatingItemTemplate templates are applied to each row of data
retrieved from the data source based on an alternating pattern. The SeparatorTemplate template is
placed between the item templates to keep things looking nice. The diagram in Figure 7-2 shows


how the templates determine the output of the control rendering process.
Our Repeater control implements a fairly sophisticated system of events that provide rich
functionality: ItemCommand, ItemCreated, and ItemDataBound. ItemCommand is an event raised by
our Repeater control that aggregates bubbled command events raised by subordinate command
controls such as an ASP.NET Button control. We discuss these events in detail in the section
titled “Repeater Control Event Management” later in this chapter.
The ItemCreated event is raised each time a RepeaterItem control is created. This gives the
client of the event an opportunity to modify or change the final control output in the template
dynamically. ItemDataBound gives the same opportunity, except it is raised after any data binding
has been performed on a template. This event is limited to the ItemTemplate and
AlternatingItemTemplate templates, because the header and footer templates do not support
data binding.
Cameron_865-2C07.fm Page 283 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
284
CHAPTER 7
■ SERVER CONTROL DATA BINDING
Figure 7-2. The Repeater control and its templates
The RepeaterItem Container Control
RepeaterItem is a building block used by the Repeater control to create its content. It is based
on the System.Web.UI.Control base class and serves as the primary container for instantiating
templates and working with events.
The following code snippet shows how the RepeaterItem control is declared, inheriting
from Control and implementing the INamingContainer interface to prevent name collisions on
child controls:
public class RepeaterItem : Control, INamingContainer
{

public RepeaterItem(int itemIndex, ListItemType itemType, object dataItem)
{

this.itemIndex = itemIndex;
this.itemType = itemType;
this.dataItem = dataItem;
}


}
The private data members are instantiated by the constructor. These fields are exposed as
public properties as well:
private object dataItem;
public object DataItem
{
I]ne]=j`ano
=j]Pnqfehhk
=jpkjekIknajk
Pdki]oD]n`u
?dneopej]>anchqj`
D]jj]Ikko
Bn`nemqa?epa]qt
I]npejOkiian
H]qnaj_aHa^ed]j
Ahev]^apdHej_khj
Re_pkne]=odsknpd
O]haoNalnaoajp]pera
Ksjan
Ksjan
O]haoNalnaoajp]pera
Kn`an=`iejeopn]pkn
O]haoNalnaoajp]pera
I]ngapejcI]j]can

Ksjan
Ksjan
=__kqjpejcI]j]can
O]haoNalnaoajp]pera
=hbna`oBqppangeopa
=j]PnqfehhkAil]na`]`koudah]`ko
=jpkjekIknajkP]mqane]
=nkqj`pdaDknj
>anchqj`ooj]^^gl
>h]qanOaa@aheg]paooaj
>hkj`ao``ohlnaapbeho
>he`k?kie`]olnal]n]`]o
>kj]ll#
>kppki)@khh]nI]ngapo
>#o>aran]cao
Da]`anPailh]pa
EpaiPailh]pa
=hpanj]pejcEpaiPailh]pa
BkkpanPailh]pa
J]ia
Nala]pan?kjpnkh
Pepha ?kil]ju
Cameron_865-2C07.fm Page 284 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
285
get
{
return dataItem;
}

set
{
dataItem = value;
}
}
private int itemIndex;
public int ItemIndex
{
get
{
return itemIndex;
}
}
private ListItemType itemType;
public ListItemType ItemType
{
get
{
return itemType;
}
}
ItemIndex exposes the relative position of the RepeaterItem control with respect to its siblings
underneath the parent Repeater control. ItemType borrows the ListItemType enumeration
from the System.Web.UI.WebControl namespace to identify the purpose of the RepeaterItem
control. The following code shows a reproduction of the enumeration definition in the System.
Web.UI.WebControls namespace:
enum ListItemType
{
Header,
Footer,

Item,
AlternatingItem,
SelectedItem,
EditItem,
Separator,
Pager
}
The last property exposed by the RepeaterItem class is DataItem. For RepeaterItem child
controls that are bound to a data source (i.e., ItemTemplate or AlternatingItemTemplate
Cameron_865-2C07.fm Page 285 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
286
CHAPTER 7
■ SERVER CONTROL DATA BINDING
RepeaterItems), DataItem will reference a particular row in the collection that makes up the
data source. This permits us to use the Container.DataItem syntax in a data-binding expression:
<ItemTemplate>
<% Container.DataItem[Name] %>
</ItemTemplate>
Command Events and the RepeaterItem Control
The RepeaterItem control plays a key role in ensuring that Command events are bubbled up to
the parent Repeater control so that it can raise an ItemCommand event to the outside world.
The following code takes Command events that are bubbled and wraps the events in a custom
RepeaterCommandEventArgs object to provide additional information on the event’s source:
protected override bool OnBubbleEvent(object source, EventArgs e)
{
CommandEventArgs ce = e as CommandEventArgs;
if (ce != null)
{
RepeaterCommandEventArgs rce = new

RepeaterCommandEventArgs(this, source, ce);
RaiseBubbleEvent(this, rce);
return true;
}
else
return false;
}
The OnBubbleEvent member function performs a typecast to validate that it is indeed a
Command event, instantiates a RepeaterCommandEventArgs class, and then sends it on up to the
Repeater control through the RaiseBubbleEvent method. The return value of true indicates to
ASP.NET that the event was handled. Later on, we show the code in Repeater that handles the
bubbled event and raises its own Command event.
We create a custom EventArgs class to make working with the Repeater control easier, as
shown in Listing 7-1. Instead of having to search through all the controls that are in the Repeater’s
Control collection, we can narrow it down to just the RepeaterItem control of interest.
Listing 7-1. The RepeaterCommand Event Class File
using System;
using System.Web.UI.WebControls;
namespace ControlsBook2Lib.Ch07
{
public delegate void RepeaterCommandEventHandler(object o,
RepeaterCommandEventArgs rce);
Cameron_865-2C07.fm Page 286 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
287
public class RepeaterCommandEventArgs : CommandEventArgs
{
public RepeaterCommandEventArgs(RepeaterItem item, object commandSource,
CommandEventArgs originalArgs) : base(originalArgs)

{
this.item = item;
this.commandSource = commandSource;
}
private RepeaterItem item;
public RepeaterItem Item
{
get
{
return item;
}
}
private object commandSource;
public object CommandSource
{
get
{
return commandSource;
}
}
}
}
The source of the event is available in the RepeaterCommandEventArgs class via the
CommandSource property. The RepeaterItem container control that houses the CommandSource
property is reachable through the Item property. It allows us to identify and programmatically
manipulate the exact block of content that was the source of the event. Our code for this control
also defines a delegate named RepeaterCommandEventHandler to work with the custom EventArgs
class. Listing 7-2 shows the full listing for the RepeaterItem control.
Listing 7-2. The RepeaterItem Control Class File
using System;

using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace ControlsBook2Lib.Ch07
{
Cameron_865-2C07.fm Page 287 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
288
CHAPTER 7
■ SERVER CONTROL DATA BINDING
public class RepeaterItem : Control, INamingContainer
{
[ToolboxItem(false)]
public RepeaterItem(int itemIndex, ListItemType itemType, object dataItem)
{
this.itemIndex = itemIndex;
this.itemType = itemType;
this.DataItem = dataItem;
}
public object DataItem { get; set; }
private int itemIndex;
public int ItemIndex
{
get
{
return itemIndex;
}
}
private ListItemType itemType;

public ListItemType ItemType
{
get
{
return itemType;
}
}
protected override bool OnBubbleEvent(object source, EventArgs e)
{
CommandEventArgs ce = e as CommandEventArgs;
if (ce != null)
{
RepeaterCommandEventArgs rce = new
RepeaterCommandEventArgs(this, source, ce);
RaiseBubbleEvent(this, rce);
return true;
}
else
return false;
}
}
Cameron_865-2C07.fm Page 288 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
289
public delegate void RepeaterItemEventHandler(object o,
RepeaterItemEventArgs rie);
public class RepeaterItemEventArgs : EventArgs
{
public RepeaterItemEventArgs(RepeaterItem item)

{
this.item = item;
}
private RepeaterItem item;
public RepeaterItem Item
{
get
{
return item;
}
}
}
}
In the next section, we discuss the implementation details of our version of the Repeater
server control.
The Repeater Control Architecture
Now that we have the main building block of our Repeater control ready for action, we can
move on to the core logic of our control. As shown in the following code, Repeater inherits from
System.Web.UI.WebControls.DataBoundControl and implements INamingContainer to prevent
control ID conflicts like its RepeaterItem sibling. The ParseChildren attribute set to true on the
Repeater class enables the use of template properties. PersistChildren is set to false to prevent
child controls from being persisted as nested inner controls; they are instead persisted as nested
elements. The Designer attribute associates a custom designer named RepeaterDesigner that
provides template editing design-time support. We discuss RepeaterDesigner further in
Chapter 11.
[ToolboxData("<{0}:repeater runat=server></{0}:repeater>"), ParseChildren(true),
PersistChildren(false),
Designer(typeof(ControlsBook2Lib.Ch11.Design.RepeaterDesigner))]
public class Repeater : DataBoundControl, INamingContainer
{

The heart of the architecture behind Repeater is two methods: CreateChildControls and
PerformDataBinding. Both of these member functions create the control hierarchy for Repeater, but
each does so as a result of two fundamentally different scenarios. First, here is the code for the
DataBind method:
Cameron_865-2C07.fm Page 289 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
290
CHAPTER 7
■ SERVER CONTROL DATA BINDING
public override void DataBind()
{
this.PerformSelect();
}
Starting in ASP.NET 2.0, the PerformSelect method performs the work to load the data as
listed here:
protected override void PerformSelect()
{
if (!IsBoundUsingDataSourceID)
{
OnDataBinding(EventArgs.Empty);
}
GetData().Select(CreateDataSourceSelectArguments(),
OnDataSourceViewSelectCallback);
RequiresDataBinding = false;
MarkAsDataBound();
OnDataBound(EventArgs.Empty);
}
Depending on whether the control is bound using an IDataSource control introduced in
ASP.NET 2.0 or any other DataSource control determines how PerformSelect executes. The
OnDataBinding call must occur before the GetData call if not bound with an IDataSource-based

control, which is where the check on IsBoundUsingDataSourceID is necessary at the beginning
of the method. The GetData method retrieves the DataSourceView object from the IDataSource
associated with the data-bound control so OnDataBinding is called prior to GetData. Finally, the
DataBound event is raised.
The method GetData is called within PerformSelect and takes a callback method as a param-
eter. The callback method is OnDataSourceViewSelectCallback, which calls PerformDataBinding to
build out the control via the CreateControlHierarchy method. Once again, whether the control
is bound to an IDataSource-based control or not determines how the control tree is built by
passing in different parameters to CreateControlHierarchy.
As you would guess, DataBind takes precedence as a control-loading mechanism when
binding to a data source. It is called on the web form after the data source has been linked to
the control.
The first task of DataBind is to fire the data-binding event OnDataBinding. If the Repeater
control is binding to a design-time data source, firing this event in DataBind is required for the
control to see the selected design-time data source at runtime.
Next, DataBind starts with a clean slate, clearing the current set of controls and any ViewState
values that are lingering, after which the control is ready to track ViewState. As shown in the
preceding code, once the table has been set, DataBind builds the child control hierarchy
based on the data source through the CreateControlHierarchy method. It then sets the
ChildControlsCreated property to true to let ASP.NET know that the control is populated. This
prevents the framework from calling CreateChildControls after DataBind.
Cameron_865-2C07.fm Page 290 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
291
We next discuss how CreateChildControls handles control creation. Here is the code for
CreateChildControls:
override protected void CreateChildControls()
{
Controls.Clear();

if (ViewState["ItemCount"] != null)
{
CreateControlHierarchy(false);
}
ClearChildViewState();
}
You have already encountered CreateChildControls in all the composite controls samples
so far in the book. It is called whenever the control needs to render itself outside of a DataBind.
The code implementation uses the CreateControlHierarchy helper method to do the dirty work as
in the DataBind method. The single difference is that the code in CreateChildControls checks
the ViewState ItemCount property. If ItemCount is not null, this indicates that we need to re-create
the control hierarchy using postback control ViewState values. Figure 7-3 illustrates the difference
between DataBind and CreateChildControls.
Figure 7-3. DataBind versus CreateChildControls
We pass a Boolean value to CreateControlHierarchy to indicate whether it needs to use the
data source to build up the control hierarchy or whether it should try to rebuild the control
hierarchy from ViewState at the beginning of a postback cycle. For CreateChildControls, we
pass in false to CreateChildHierarchy if ItemCount is present in ViewState.
The data binding process is controlled by three properties: DataSourceID, DataMember, and
DataSource. Notice that none of these properties are declared directly in our custom Repeater
control. Our Repeater control inherits from DataBoundControl, where much of the data binding
functionality is handled by the base class itself. The DataSourceID is set as the DataSource when
using an IDataSource-based control first introduced in ASP.NET 2.0, such as the SqlDataSource
class. DataSourceID appears in the Properties window, but DataSource does not, though
DataSource is still a public property that can be set in code.
Sa^>nksoan Sa^Oanran Nala]pan
@]p])>ej`sepd
@]p]Okqn_a
?na]pa?deh`?kjpnkho]j`
NapnearaEjbkni]pekj

bnkiReasOp]pa
Ejepe]hNamqaop
?kjpnkhDPIH+ReasOp]pa
?kjpnkhDPIH+ReasOp]pa
Lkop^]_g+ReasOp]pa
Cameron_865-2C07.fm Page 291 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
292
CHAPTER 7
■ SERVER CONTROL DATA BINDING
In the next section, we dissect CreateControlHierarchy by breaking the code into bite-
sized chunks as part of the discussion.
The CreateControlHierarchy Method
CreateControlHierarchy contains the most complicated logic in the Repeater control. It has
logic that covers creating the header and footer section of the control, along with the data-bound
item content. The first part of CreateControlHierarchy creates the header section of the control:
private void CreateControlHierarchy(bool useDataSource)
{
items = new ArrayList();
IEnumerable ds = null;
if (HeaderTemplate != null)
{
RepeaterItem header = CreateItem(-1, ListItemType.Header, false, null);
}
The preceding code checks for the presence of a HeaderTemplate template, and if it exists,
it creates a header RepeaterItem via CreateItem. CreateItem is the code that handles the actual
RepeaterItem creation and adds it to the Repeater’s Controls collection.
The items field is an ArrayList containing the RepeaterItem collection for the
RepeaterControl. It is declared as a private field under the Repeater class:
private ArrayList items = null;

You can think of this as a secondary collection of child controls like the Controls collection
but one that is filtered to include just the RepeaterItem containers that represent data from the
data source.
After the header is created, CreateControlHierarchy creates the core data-oriented
RepeaterItem child controls. The first step in the process is resolving the DataSource. If
CreateControlHierarchy is called from the PerformDataBinding method, the useDataSource
Boolean parameter will be set to true and the usingIDataSource parameter will be false or
true depending on whether the control is bound to an IDataSource-based control. Otherwise,
if CreateControlHierarchy is called from CreateChildControls, useDataSource and
usingIDataSource will be set to false:
private void CreateControlHierarchy(bool useData, bool
usingIDataSource, IEnumerable data)
We now move on to discuss how the Repeater control resolves its data source and builds
up its control hierarchy as it data binds.
The DataSourceHelper Class and Data Binding
When building the control hierarchy as a result of data binding, we use a helper class named
DataSourceHelper to resolve the DataSource to something that supports the IEnumerable inter-
face. You can use this code directly to perform the same task in your data-bound custom server
controls.
Cameron_865-2C07.fm Page 292 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
293
The ResolveDataSource method of the DataSourceHelper class detects the interfaces
supported by the data source and will walk into the DataMember field of the DataSource if
necessary. For collections such as arrays based on System.Array, ArrayList, and the DataReader
classes of ADO.NET, ResolveDataSource performs a simple cast to IEnumerable.
Complex IListSource data collections such as the DataSet account for the bulk of the work
in ResolveDataSource. For DataSet, we need to drill down into its child collections based on the
DataMember passed into the control. Here is how DataSet is declared:

public class DataSet : MarshalByValueComponent, IListSource,
ISupportInitialize, ISerializable
The IListSource interface implemented by the DataSet provides a way to determine if
there are multiple DataTable child collections by checking the value of the Boolean
ContainsListCollection property. If the class implementing IListSource supports a bindable
list, we need to use the ITypedList interface to bind to it at runtime. The DataViewManager class
provides just such a bindable list for the DataTables that make up a DataSet. DataViewManager
has the following declaration:
public class DataViewManager : MarshalByValueComponent,
IBindingList, IList, ICollection, IEnumerable, ITypedList
The GetList method of the IListSource interface implemented by the DataSet class
returns an instance of the ITypedList interface implemented by the DataViewManager class
through casting to the appropriate interface. We use the ITypedList interface to dynamically
bind to the correct data source. Figure 7-4 provides a diagram of the process required to handle
an ITypedList data source such as a DataSet.
Figure 7-4. Resolving IListSource data sources
@]p]Okqn_aDahlan
Naokhra@]p]Okqn_a$k^fa_p`]p]Okqn_a(opnejc`]p]Iai^an%
EHeopOkqn_a
EPula`Heop
EHeop
@]p]Okqn_aDahlan
?h]oo
@]p]Oap
@]p]P]^ha
@]p]ReasI]j]can
?da_gpkoaaebEHeopOkqn_a
_kjp]ejo]heop_khha_pekj*
Qoa@]p]ReasI]j]canpkoa]n_d
pda@]p]OapÑo@]p]P]^ha?khha_pekj

pkbej`pda_knna_p@]p]P]^hapd]p
i]p_daopda`]p]Iai^anr]hqa
l]ooa`ejpkNaokhra@]p]Okqn_a*
Bknoeilha`]p]okqn_a_khha_pekjo
hega=nn]ukn=nn]uHeop(fqop_]op
`]p]Okqn_al]n]iapanpkEAjqian]^ha*
EilhaiajpanokbEHeopOkqn_a
_]jnapqnj]jEHeoppd]p
_kjp]ejo]_khha_pekjkb
EHeopk^fa_po*
Cameron_865-2C07.fm Page 293 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
294
CHAPTER 7
■ SERVER CONTROL DATA BINDING
ITypedList gives us the ability to dynamically find properties exposed by a class.
DataViewManager, as part of its ITypedList implementation, exposes the DataTables as properties
in its DataViewSettingCollection. The code checks the dynamic properties of DataViewManager
to see if it can retrieve the DataViewSetting property that matches the DataMember passed into
the Repeater control. If the DataMember is blank, we choose the first DataTable in the DataSet.
Listing 7-3 presents the full source code for the DataSourceHelper class.
Listing 7-3. The DataSourceHelper Class File
using System;
using System.Collections;
using System.ComponentModel;
namespace ControlsBook2Lib.Ch07
{
public class DataSourceHelper
{
public static object ResolveDataSource(object dataSource, string dataMember)

{
if (dataSource == null)
return null;
if (dataSource is IEnumerable)
{
return (IEnumerable)dataSource;
}
else if (dataSource is IListSource)
{
IList list = null;
IListSource listSource = (IListSource)dataSource;
list = listSource.GetList();
if (listSource.ContainsListCollection)
{
ITypedList typedList = (ITypedList)list;
PropertyDescriptorCollection propDescCol =
typedList.GetItemProperties(new PropertyDescriptor[0]);
if (propDescCol.Count == 0)
throw new Exception("ListSource without DataMembers");
PropertyDescriptor propDesc = null;
//Check to see if dataMember has a value, if not, default to first
//property (DataTable) in the property collection (DataTableCollection)
if ((dataMember == null) || (dataMember.Length < 1))
{
propDesc = propDescCol[0];
}
Cameron_865-2C07.fm Page 294 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
295

else //If dataMember is set, try to find it in the property collection
propDesc = propDescCol.Find(dataMember, true);
if (propDesc == null)
throw new Exception("ListSource missing DataMember");
object listitem = list[0];
//Get the value of the property (DataTable) of interest
object member = propDesc.GetValue(listitem);
if ((member == null) || !(member is IEnumerable))
throw new Exception("ListSource missing DataMember");
return (IEnumerable)member;
}
else
return (IEnumerable)list;
}
return null;
}
}
}
The end result is a PropertyDescriptor that allows us to dynamically retrieve the appro-
priate data for binding. For the DataSet, this gives us a reference to a DataTable. We cast the
result to IEnumerable and return it to the control so that we can continue the data binding process.
PropertyDescriptor, PropertyDescriptorCollection, and IListSource are all members of
the System.ComponentModel namespace. This namespace plays a critical role in performing
dynamic lookups and enhancing the design-time experience of controls. We focus on the
design time support, including data binding design time support, in Chapter 11.
The DummyDataSource Class and Postback
If CreateControlHierarchy is not in the midst of a DataBind, it needs to determine whether or
not it is in a postback environment. We can check this by looking for the ItemCount variable in
ViewState. If it is present, we create a DummyDataSource object that is appropriately named,
because it serves as a placeholder to rehydrate the control state that was originally rendered

and sent back to the web server via postback. Listing 7-4 provides the class source code for
DummyDataSource.
Listing 7-4. The DummyDataSource Class File
using System;
using System.Collections;
using System.ComponentModel;
Cameron_865-2C07.fm Page 295 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
296
CHAPTER 7
■ SERVER CONTROL DATA BINDING
namespace ControlsBook2Lib.Ch07
{
internal sealed class DummyDataSource : ICollection
{
public DummyDataSource(int dataItemCount)
{
this.Count = dataItemCount;
}
public int Count { get; set; }
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 DummyDataSourceEnumerator(Count);
}
Cameron_865-2C07.fm Page 296 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
297
private class DummyDataSourceEnumerator : IEnumerator
{
private int count;
private int index;
public DummyDataSourceEnumerator(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;
}
}
}
}
DummyDataSource implements the necessary collection interfaces to be compatible with the
rendering logic in Repeater. The key ingredients are implementation of the IEnumerable and
IEnumeration interfaces. As an example of how this works, this code snippet enumerates a
string array:
string[] numbers = new string[] { one,two,three };
foreach (string number in numbers)
{
// action

}
IEnumerable signifies that the collection supports enumeration via constructs, such as the
foreach statement in C#. The method to get the enumerator from an IEnumerable collection is
GetEnumerator. It returns an IEnumerator interface.
Cameron_865-2C07.fm Page 297 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
298
CHAPTER 7
■ SERVER CONTROL DATA BINDING
Client code uses the IEnumerator interface to move around the collection. MoveNext advances
the cursor, and the Current property allows the client to grab the item pointed to by the cursor
in the collection. The following code shows what really goes on when you use foreach in C#:
IEnumerator enum = numbers.GetEnumerator();
string number = null;
while (enum.MoveNext())
{
number = enum.Current;
// action
}
DummyDataSource implements its enumerator as a private nested class named
DummyDataSourceEnumerator. It returns an instance of this class from its GetEnumerator method.
Figure 7-5 illustrates the role that the DummyDataSource class plays during postback.
The dummy collection source is initialized by passing in the count of items to the
DummyDataSource constructor. When a client retrieves the enumerator, it will iterate through
that count of items, returning a null value. This may seem pointless, but it is enough to prime
the pump inside CreateControlHierarchy to rehydrate the RepeaterItem controls from ViewState
during postback. Once the controls are added, each RepeaterItem control can retrieve its former
contents using ViewState and postback data. We now move on to how the Repeater control
creates its content when data binding to a data source.
Figure 7-5. Using DummyDataSource

Creating the Middle Content
Once we have a valid object in the DataSource property, we can continue the task of creating
the RepeaterItem controls in CreateControlHierarchy, as shown in the following code. If the
previous step failed, the DataSource will be null, and no content gets rendered. However, if the
Nala]pan]i`
@qiiu@]p]Okqn_a
ReasOp]pa
Epai?kqjp @qiiu@]p]Okqn_a
?kqjp9/
Nala]pan
Nala]panEpai
Nala]panEpai
Nala]panEpai
Nala]pan
Nala]panEpai
Nala]panEpai
Nala]panEpai
Cameron_865-2C07.fm Page 298 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
299
call to ResolveDataSource is successful, the code loops through the DataSource named ds using
a foreach construct to create RepeaterItem controls. Like the header section of the Repeater
control, the CreateItem method does the bulk of work in configuring each RepeaterItem.
if (ds != null)
{
int index = 0;
count = 0;
RepeaterItem item;
ListItemType itemType = ListItemType.Item;

foreach (object dataItem in (IEnumerable)ds)
{
if (index != 0)
{
RepeaterItem separator = CreateItem(-1, ListItemType.Separator, false, null);
}
item = CreateItem(index, itemType, useData, dataItem);
items.Add(item);
index++;
count++;
if (itemType == ListItemType.Item)
itemType = ListItemType.AlternatingItem;
else
itemType = ListItemType.Item;
}
}
The looping code also keeps track of the index of the RepeaterItem and the total count of
controls added to the Controls collection. It meets our specification of having an item, an alter-
nating item, and a separator by alternating between ItemTemplate and AlternatingItemTemplate,
as well as including a RepeaterItem control implementing SeparatorTemplate between each
data item.
The final section of CreateControlHierarchy is the portion that creates the footer for our
Repeater implementation:
if (FooterTemplate != null)
{
RepeaterItem footer = CreateItem(-1, ListItemType.Footer, false, null);
}
if (useData)
{
ViewState["ItemCount"] = ((ds != null) ? count : -1);

}
Cameron_865-2C07.fm Page 299 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
300
CHAPTER 7
■ SERVER CONTROL DATA BINDING
The last if-then construct stores the count of RepeaterItem controls in ViewState so we
can rehydrate DummyDataSource on postback. We drill into the CreateItem method in the next
section.
Creating the RepeaterItem Control in CreateItem
Much of the previous code in CreateControlHierarchy offloaded work to CreateItem. CreateItem is
tasked with doing quite a few things beyond just creating a RepeaterItem control: it handles
template instantiation and raises the ItemDataBound and ItemCreated events.
The first portion of CreateItem checks the ListItemType so that it can determine the right
enumeration to use with the RepeaterItem control:
private RepeaterItem CreateItem(int itemIndex, ListItemType itemType,
bool dataBind, object dataItem)
{
ITemplate selectedTemplate;
switch (itemType)
{
case ListItemType.Header:
selectedTemplate = headerTemplate;
break;
case ListItemType.Item:
selectedTemplate = itemTemplate;
break;
case ListItemType.AlternatingItem:
selectedTemplate = alternatingItemTemplate;
break;

case ListItemType.Separator:
selectedTemplate = separatorTemplate;
break;
case ListItemType.Footer:
selectedTemplate = footerTemplate;
break;
default:
selectedTemplate = null;
break;
}
if ((itemType == ListItemType.AlternatingItem) &&
(alternatingItemTemplate == null))
{
selectedTemplate = itemTemplate;
itemType = ListItemType.Item;
}
Cameron_865-2C07.fm Page 300 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
301
RepeaterItem item = new RepeaterItem(itemIndex, itemType, dataItem);
if (selectedTemplate != null)
{
selectedTemplate.InstantiateIn(item);
}
OnItemCreated(new RepeaterItemEventArgs(item));
Controls.Add(item);
if (dataBind)
{
item.DataBind();

OnItemDataBound(new RepeaterItemEventArgs(item));
}
return item;
}
The code next instantiates a RepeaterItem control with the index of the object, the
ListItemType, and a reference to the data source. For RepeaterItem instances that are based on
the HeaderTemplate, FooterTemplate, and SeparatorTemplate templates, the dataItem parameter
will be null. Only the ItemTemplate- and AlternatingItemTemplate-based RepeaterItem controls
are linked to a row in the data source:
RepeaterItem item = new RepeaterItem(itemIndex, itemType, dataItem);
if (selectedTemplate != null)
{
selectedTemplate.InstantiateIn(item);
}
OnItemCreated(new RepeaterItemEventArgs(item));
Controls.Add(item);
At this point in CreateItem, the RepeaterItem control is fully populated, and we raise the
ItemCreated event through the OnItemCreated method to allow interested clients to react to the
creation process. They can then add additional controls to our RepeaterItem to customize its
content if necessary. After this event is raised, we add the RepeaterItem control to the Controls
collection of the Repeater class.
If we are data binding, the code calls DataBind on the RepeaterItem to resolve its data binding
expressions to the piece of data attached to its DataItem property. We also raise an event via
OnItemDataBound, as shown in the following code. This causes any data binding expressions in
the templates to resolve to the particular row in the data source and get needed data for the
final render process.
Cameron_865-2C07.fm Page 301 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
302
CHAPTER 7

■ SERVER CONTROL DATA BINDING
if (dataBind)
{
item.DataBind();
OnItemDataBound(new RepeaterItemEventArgs(item));
}
return item;
}
The last step is to return the RepeaterItem so that the calling code can add it to the items
ArrayList maintained by Repeater.
Accessing RepeaterItem Instances After Creation
CreateControlHierarchy, along with CreateItem, does a great job of creating RepeaterItem
instances and adding them to the Controls collection and the items generic List, providing
access to a read-only collection to give access to the RepeaterInfo instances without having to
create a custom collection class of RepeaterItems.
The Items property on Repeater uses a collection of type generic List<> to allow easy access
to the RepeaterItems. Note that items is a private field for the Items property that we also use
in CreateControlHierarchy. We now move on to discuss the various events that the Repeater
control implements.
Repeater Control Event Management
Repeater exposes an ItemCommand event, an ItemCreated event, and an ItemDataBound event. We
use the Events collection provided by System.Web.UI.Control to track registered client delegates.
The following code for the ItemCommand event is reproduced in a similar manner for the
ItemCreated and ItemDataBound events:
private static readonly object ItemCommandKey = new object();
public event RepeaterCommandEventHandler ItemCommand
{
add
{
Events.AddHandler(ItemCommandKey, value);

}
remove
{
Events.RemoveHandler(ItemCommandKey, value);
}
}
The On-prefixed protected methods use standard event techniques to notify the delegates
that subscribe to the event when it is fired. The following OnItemCommand is mirrored by
OnItemDataBound and OnItemCreated:
protected virtual void OnItemCommand(RepeaterCommandEventArgs rce)
{
RepeaterCommandEventHandler repeaterCommandEventDelegate =
(RepeaterCommandEventHandler) Events[ItemCommandKey];
Cameron_865-2C07.fm Page 302 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
303
if (repeaterCommandEventDelegate != null)
{
repeaterCommandEventDelegate(this, rce);
}
}
ItemCommand requires an extra step to handle the RepeaterCommand events bubbled up from
child RepeaterItem controls. To wire into the event bubbling, it implements OnBubbleEvent:
protected override bool OnBubbleEvent(object source, EventArgs e)
{
RepeaterCommandEventArgs rce = e as RepeaterCommandEventArgs;
if (rce != null)
{
OnItemCommand(rce);

return true;
}
else
return false;
}
OnBubble traps the RepeaterCommand events and raises them as ItemCommand events to event
subscribers. Listing 7-5 shows the final source code for the Repeater control class.
Listing 7-5. The Repeater Control Class File
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design.WebControls;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using ControlsBook2Lib.Ch11.Design;
namespace ControlsBook2Lib.Ch07
{
[ToolboxData("<{0}:repeater runat=server></{0}:repeater>"),
ParseChildren(true), PersistChildren(false),
Designer(typeof(ControlsBook2Lib.Ch11.Design.RepeaterDesigner))]
public class Repeater : DataBoundControl, INamingContainer
{
#region Template Code
private ITemplate headerTemplate;
[Browsable(false), TemplateContainer(typeof(RepeaterItem)),
PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate HeaderTemplate
Cameron_865-2C07.fm Page 303 Monday, February 18, 2008 4:07 PM

Simpo PDF Merge and Split Unregistered Version -
304
CHAPTER 7
■ SERVER CONTROL DATA BINDING
{
get
{
return headerTemplate;
}
set
{
headerTemplate = value;
}
}
private ITemplate footerTemplate;
[Browsable(false), TemplateContainer(typeof(RepeaterItem)),
PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate FooterTemplate
{
get
{
return footerTemplate;
}
set
{
footerTemplate = value;
}
}
private ITemplate itemTemplate;
[Browsable(false), TemplateContainer(typeof(RepeaterItem)),

PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ItemTemplate
{
get
{
return itemTemplate;
}
set
{
itemTemplate = value;
}
}
Cameron_865-2C07.fm Page 304 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
305
private ITemplate alternatingItemTemplate;
[Browsable(false), TemplateContainer(typeof(RepeaterItem)),
PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate AlternatingItemTemplate
{
get
{
return alternatingItemTemplate;
}
set
{
alternatingItemTemplate = value;
}
}

private ITemplate separatorTemplate;
[Browsable(false), TemplateContainer(typeof(RepeaterItem)),
PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate SeparatorTemplate
{
get
{
return separatorTemplate;
}
set
{
separatorTemplate = value;
}
}
private RepeaterItem CreateItem(int itemIndex, ListItemType
itemType, bool dataBind, object dataItem)
{
ITemplate selectedTemplate;
switch (itemType)
{
case ListItemType.Header:
selectedTemplate = headerTemplate;
break;
Cameron_865-2C07.fm Page 305 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
306
CHAPTER 7
■ SERVER CONTROL DATA BINDING
case ListItemType.Item:
selectedTemplate = itemTemplate;

break;
case ListItemType.AlternatingItem:
selectedTemplate = alternatingItemTemplate;
break;
case ListItemType.Separator:
selectedTemplate = separatorTemplate;
break;
case ListItemType.Footer:
selectedTemplate = footerTemplate;
break;
default:
selectedTemplate = null;
break;
}
if ((itemType == ListItemType.AlternatingItem) &&
(alternatingItemTemplate == null))
{
selectedTemplate = itemTemplate;
itemType = ListItemType.Item;
}
RepeaterItem item = new RepeaterItem(itemIndex, itemType, dataItem);
if (selectedTemplate != null)
{
selectedTemplate.InstantiateIn(item);
}
OnItemCreated(new RepeaterItemEventArgs(item));
Controls.Add(item);
if (dataBind)
{
item.DataBind();

OnItemDataBound(new RepeaterItemEventArgs(item));
}
return item;
}
#endregion
Cameron_865-2C07.fm Page 306 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -
CHAPTER 7 ■ SERVER CONTROL DATA BINDING
307
[Browsable(false)]
public List<RepeaterItem> Items
{
get
{
EnsureChildControls();
return items;
}
}
protected override void PerformSelect()
{
// Call OnDataBinding here if bound to a data source using the
// DataSource property (instead of a DataSourceID), because the
// databinding statement is evaluated before the call to GetData.
if (!IsBoundUsingDataSourceID)
{
OnDataBinding(EventArgs.Empty);
}
// The GetData method retrieves the DataSourceView object from
// the IDataSource associated with the data-bound control.
GetData().Select(CreateDataSourceSelectArguments(),

OnDataSourceViewSelectCallback);
// The PerformDataBinding method has completed.
RequiresDataBinding = false;
MarkAsDataBound();
// Raise the DataBound event.
OnDataBound(EventArgs.Empty);
}
private void OnDataSourceViewSelectCallback(IEnumerable retrievedData)
{
// Call OnDataBinding only if it has not already been
// called in the PerformSelect method.
if (IsBoundUsingDataSourceID)
{
OnDataBinding(EventArgs.Empty);
}
// The PerformDataBinding method binds the data in the
// retrievedData collection to elements of the data-bound control.
PerformDataBinding(retrievedData);
}
Cameron_865-2C07.fm Page 307 Monday, February 18, 2008 4:07 PM
Simpo PDF Merge and Split Unregistered Version -

×