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:protecte
Trang 1• 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
Trang 2Figure 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{
}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
=jpkjekIknajk Pdki]oD]n`u
?dneopej]>anchqj`
D]jj]Ikko Bn`nemqa?epa]qt I]npejOkiian H]qnaj_aHa^ed]j Ahev]^apdHej_khj Re_pkne]=odsknpd
O]haoNalnaoajp]pera Ksjan
Ksjan O]haoNalnaoajp]pera Kn`an=`iejeopn]pkn O]haoNalnaoajp]pera I]ngapejcI]j]can Ksjan
Ksjan
= kqjpejcI]j]can O]haoNalnaoajp]pera
Trang 3private int itemIndex;
public int ItemIndex
private ListItemType itemType;
public ListItemType ItemType
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
Trang 4RepeaterItems), 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);
Trang 5public 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
private object commandSource;
public object 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
Trang 6public class RepeaterItem : Control, INamingContainer {
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;
} }
Trang 7public delegate void RepeaterItemEventHandler(object o,
private RepeaterItem item;
public RepeaterItem Item
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
Trang 8public 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 eter The callback method is OnDataSourceViewSelectCallback, which calls PerformDataBinding to build out the control via the CreateControlHierarchy method Once again, whether the control
param-is bound to an IDataSource-based control or not determines how the control tree param-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
Trang 9We next discuss how CreateChildControls handles control creation Here is the code for CreateChildControls:
override protected void CreateChildControls()
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
@]p])>ej`sepd
@]p]Okqn_a
?na]pa?deh`?kjpnkho]j`
NapnearaEjbkni]pekj bnkiReasOp]pa
Ejepe]hNamqaop
?kjpnkhDPIH+ReasOp]pa
?kjpnkhDPIH+ReasOp]pa Lkop^]_g+ReasOp]pa
Trang 10In the next section, we dissect CreateControlHierarchy by breaking the code into sized chunks as part of the discussion.
bite-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
Trang 11The 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
Qoa@]p]ReasI]j]canpkoa]n_d pda@]p]OapÑo@]p]P]^ha?khha_pekj pkbej`pda_knna_p@]p]P]^hapd]p i]p_daopda`]p]Iai^anr]hqa
l]ooa`ejpkNaokhra@]p]Okqn_a*
Bknoeilha`]p]okqn_a_khha_pekjo hega=nn]ukn=nn]uHeop(fqop_]op
`]p]Okqn_al]n]iapanpkEAjqian]^ha*
EilhaiajpanokbEHeopOkqn_a _]jnapqnj]jEHeoppd]p _kjp]ejo]_khha_pekjkb
EHeopk^fa_po*
Trang 12ITypedList 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];
}
Trang 13else //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");
appro-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
Trang 14namespace 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);
}
Trang 15private class DummyDataSourceEnumerator : IEnumerator
{
private int count;
private int index;
public DummyDataSourceEnumerator(int count)
string[] numbers = new string[] { one,two,three };
foreach (string number in numbers)
Trang 16Client 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
Trang 17call 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
ListItemType itemType = ListItemType.Item;
foreach (object dataItem in (IEnumerable)ds)
nating item, and a separator by alternating between ItemTemplate and AlternatingItemTemplate,
as well as including a RepeaterItem control implementing SeparatorTemplate between each
Trang 18The 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) {
selectedTemplate = itemTemplate;
itemType = ListItemType.Item;
}
Trang 19RepeaterItem item = new RepeaterItem(itemIndex, itemType, dataItem);
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);
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
Trang 20if (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];
Trang 21#region Template Code
private ITemplate headerTemplate;
[Browsable(false), TemplateContainer(typeof(RepeaterItem)),
PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate HeaderTemplate
Trang 22{ 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;
} }
Trang 23private ITemplate alternatingItemTemplate;
private RepeaterItem CreateItem(int itemIndex, ListItemType
itemType, bool dataBind, object dataItem)
Trang 24selectedTemplate = 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
Trang 25// 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
// 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);
}
Trang 26protected override void PerformDataBinding(IEnumerable data) {
else CreateControlHierarchy(true, false, data);
ChildControlsCreated = true;
}
protected override void ValidateDataSource(object dataSource) {
if (((dataSource != null) && !(dataSource is IListSource)) &&
(!(dataSource is IEnumerable) && !(dataSource is IDataSource))) {
throw new InvalidOperationException();
} } public override void DataBind() {
this.PerformSelect();
} private List<RepeaterItem> items; //private collection backing Items property private void CreateControlHierarchy(bool useData, bool
usingIDataSource, IEnumerable data) {
items = new List<RepeaterItem>();
IEnumerable ds = null;
if (HeaderTemplate != null) {
RepeaterItem header = CreateItem(-1, ListItemType.Header, false, null); }
Trang 27ListItemType itemType = ListItemType.Item;
foreach (object dataItem in (IEnumerable)ds)
{
if (index != 0)
{
RepeaterItem separator = CreateItem(-1,
ListItemType.Separator, false, null);
Trang 28if (FooterTemplate != null) {
RepeaterItem footer = CreateItem(-1, ListItemType.Footer, false, null); }
if (useData) {
ViewState["ItemCount"] = ((ds != null) ? count : -1);
} } override protected void CreateChildControls() {
Controls.Clear();
if (ViewState["ItemCount"] != null) {
CreateControlHierarchy(false, false, null);
} ClearChildViewState();
} public override ControlCollection Controls {
get { EnsureChildControls();
return base.Controls;
} } private static readonly object ItemCommandKey = new object();
public event RepeaterCommandEventHandler ItemCommand {
add { Events.AddHandler(ItemCommandKey, value);
} remove { Events.RemoveHandler(ItemCommandKey, value);
} }
Trang 29private static readonly object ItemCreatedKey = new object();
public event RepeaterItemEventHandler ItemCreated
private static readonly object ItemDataBoundKey = new object();
public event RepeaterItemEventHandler ItemDataBound
Trang 30if (repeaterCommandEventDelegate != null) {
repeaterCommandEventDelegate(this, rce);
} } protected virtual void OnItemCreated(RepeaterItemEventArgs rie) {
RepeaterItemEventHandler repeaterItemEventDelegate = (RepeaterItemEventHandler)Events[ItemCreatedKey];
if (repeaterItemEventDelegate != null) {
repeaterItemEventDelegate(this, rie);
} } protected virtual void OnItemDataBound(RepeaterItemEventArgs rie) {
RepeaterItemEventHandler repeaterItemEventDelegate = (RepeaterItemEventHandler)Events[ItemDataBoundKey];
if (repeaterItemEventDelegate != null) {
repeaterItemEventDelegate(this, rie);
} } }}Now that we have covered the construction of our version of the Repeater control, in the next section we put it to the test to see if it behaves in a similar manner to the built-in ASP.NET Repeater server control
Data Binding with the Repeater Control
Our long journey to build a Repeater control replica is complete Now, we need to take it for a test drive with a variety of NET collection types and a design-time DataSet to prove that the core feature set works as advertised
The Databound Repeater web form has five Repeater controls that are attached to five different collection types: Array, ArrayList, SqlDataReader, DataSet, and an IDataSource-based control The form also has a button on it to exercise the postback capabilities of the Repeater control to show how the control remembers its previous content without having to perform an additional data bind The UI for the web form is shown in Figure 7-6
Trang 31Figure 7-6 The rendered Databound Repeater web form
Listings 7-6 and 7-7 show the full code for the web form
Listing 7-6 The DataboundRepeater Web Form aspx File
<%@ Page Language="C#"
MasterPageFile="~/MasterPage/ControlsBook2MasterPage.Master"
AutoEventWireup="true" CodeBehind="DataBoundRepeater.aspx.cs"
Inherits="ControlsBook2Web.Ch07.DataBoundRepeater"
Title="DataBound Repeater Demo" %>
<%@ Register TagPrefix="apress" Namespace="ControlsBook2Lib.Ch07"
Assembly="ControlsBook2Lib" %>
Trang 32<asp:Content ID="Content1" ContentPlaceHolderID="ChapterNumAndTitle" runat="server"> <asp:Label ID="ChapterNumberLabel" runat="server"
Width="14px">7</asp:Label> <asp:Label ID="ChapterTitleLabel" runat="server" Width="360px">
Server Control Data Binding</asp:Label>
Trang 33<div style="display: inline; font-weight: bold;
color: yellow; background-color: red">
<%# DataBinder.Eval(Container.DataItem,"ContactName") %></div>
</ItemTemplate>
<AlternatingItemTemplate>
<div style="display: inline; font-weight: bold;
color: yellow; background-color: blue">
Trang 34} </style>
Trang 35public partial class DataBoundRepeater : System.Web.UI.Page
{
protected System.Data.SqlClient.SqlDataAdapter dataAdapterEmp;
protected System.Data.SqlClient.SqlCommand sqlSelectCommand1;
protected DataSetEmp dataSetEmp;
protected void Page_Load(object sender, EventArgs e)
Trang 36SqlCommand cmd = new SqlCommand("SELECT CustomerID, ContactName, ContactTitle, CompanyName FROM Customers WHERE CustomerID LIKE '[AB]%'", conn);
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dr;
} private void FillEmployeesDataSet(DataSet ds) {
SqlConnection conn = new SqlConnection(WebConfigurationManager.ConnectionStrings["NorthWindDB"].ConnectionString);
conn.Open();
SqlDataAdapter da = new SqlDataAdapter("SELECT EmployeeID, FirstName, LastName, Title FROM Employees WHERE EmployeeID < 5",
conn);
da.Fill(ds, "Employees");
conn.Close();
} }}
In the next section, we test the events published by the Replica Repeater server control we created in this chapter
Advanced Interaction with the Repeater Control
The previous web form demonstrates that our Repeater control is capable of binding to a variety
of data sources The Advanced Repeater web form takes this a few steps further Instead of just binding a SqlDataReader to a Repeater control, the Advanced Repeater web form hooks into the ItemCreated and ItemDataBound events of our Repeater control to dynamically alter its output.The Advanced Repeater web form dynamically adds an ASP.NET Label control to each RepeaterItem row in its ItemCreated handler:
protected void repeaterRdrCust_ItemCreated(object o, ControlsBook2Lib.Ch07.RepeaterItemEventArgs rie){
ControlsBook2Lib.Ch07.RepeaterItem item = rie.Item;
if (item.ItemType == ListItemType.Item) {
Label lblID = new Label();
lblID.ID = "lblID";
item.Controls.Add(lblID);
item.Controls.Add(new LiteralControl(" "));
}
Trang 37Once the control data binds, it changes the value of the added Label control to the CustomerID value of the current row in the SqlDataReader:
protected void repeaterRdrCust_ItemDataBound(object o,
ControlsBook2Lib.Ch07.RepeaterItemEventArgs rie)
{
ControlsBook2Lib.Ch07.RepeaterItem item = rie.Item;
DbDataRecord row = (DbDataRecord)item.DataItem;
Figure 7-7 The rendered Advanced Repeater web form
Listings 7-8 and 7-9 present the full code for the web form
Trang 38Listing 7-8 The AdvancedRepeater aspx Page File
<%@ Page Language="C#"
MasterPageFile="~/MasterPage/ControlsBook2MasterPage.Master"
AutoEventWireup="true" CodeBehind="AdvancedRepeater.aspx.cs"
Inherits="ControlsBook2Web.Ch07.AdvancedRepeater"
Title="Advanced Repeater Control Demo" %>
<%@ Register TagPrefix="apress" Namespace="ControlsBook2Lib.Ch07"
Assembly="ControlsBook2Lib" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ChapterNumAndTitle" runat="server"> <asp:Label ID="ChapterNumberLabel" runat="server"
Width="14px">7</asp:Label> <asp:Label ID="ChapterTitleLabel" runat="server" Width="360px">
Server Control Data Binding</asp:Label>