Through reflection, the call is made to invoke the Get part of the DataSource property and return a reference to the actual data source. The data source is now known but the pager still needs to know how to manipulate it. A lot of effort went into making the pager presentation independent. Making it data source dependent would defeat the purpose of constructing a flexible control. A pluggable architecture ensures that the pager control will be able to handle all sorts of different data sources, .NET provided or custom.
The perfect solution to provide a robust, scalable and pluggable architecture can be achieved by using the [GoF] builder pattern.

The IDataSourceAdapter interface defines the most basic element, or plug, the pager needs to manipulate the data.
publicinterface IDataSourceAdapter
{
int TotalCount{get;}
object GetPagedData(int start,int end);
}
The TotalCount property returns the total number of elements in the data source before manipulating the data while the GetPagedData method manipulates the data source by returning a subset of the original data. For example: if the data source is a simple array containing 20 elements and the pager shows 10 elements per page, then a subset of this data would be elements 0-9 for page one and 10-19 for page two. A plug for a DataView type is provided by the DataViewAdapter.
internalclass DataViewAdapter:IDataSourceAdapter
{
private DataView _view;
internal DataViewAdapter(DataView view)
{
_view = view;
}
publicint TotalCount
{
get{return (_view == null)
? 0 : _view.Table.Rows.Count;} }
publicobject GetPagedData(int start, int end)
{
DataTable table = _view.Table.Clone();
for (int i = start;i<=end && i<=
TotalCount;i++)
{
table.ImportRow(_view[i-1].Row);
}
return table;
}
}
The DataViewAdapter implements the IDataSourceAdapter's GetPagedData method by cloning the original DataTable and then importing rows from the original DataTable to the cloned table. The class's visibility is intentionally set to internal in order to hide the implementation from the Web developer and provide an easier interface through the Builder classes.
publicabstractclass AdapterBuilder
{
privateobject _source;
privatevoid CheckForNull()
{
if (_source == null) thrownew NullReferenceException("You must
provide a valid source");
}
publicvirtualobject Source
{
get
{
CheckForNull();
return _source;}
set
{
_source = value;
CheckForNull();
}
}
publicabstract IDataSourceAdapter Adapter{get;}
}
The abstract AdapterBuilder class provides a more manageable interface to the IdataSourceAdapter type. By employing an extra level of abstraction, instead of using the IDataSourceAdapter directly, it provides an extra layer where pre processing instructions, before paging the data, can take place. The builder also allows the actual implementation, such as the DataViewAdapter to be hidden away from the pager user.
publicclass DataTableAdapterBuilder:AdapterBuilder
{
private DataViewAdapter
_adapter;
private DataViewAdapter
ViewAdapter
{
get
{
if (_adapter == null)
{
DataTable table = (DataTable)Source;
_adapter
= new DataViewAdapter(table.DefaultView);
}
return _adapter;
}
}
publicoverride IDataSourceAdapter Adapter
{
get{return ViewAdapter;}
}
}
publicclass DataViewAdapterBuilder:AdapterBuilder
{
private DataViewAdapter
_adapter;
private DataViewAdapter
ViewAdapter
{
get
{ //lazy instantiate
if (_adapter == null)
{
_adapter
= new DataViewAdapter((DataView)Source);
}
return _adapter;
}
}
publicoverride IDataSourceAdapter Adapter
{
get{return ViewAdapter;}
}
}
The DataView type and the DataTable type are so closely related that it might make sense to make a general DataAdapter. It would be enough to add another constructor that handles a DataTable. Alas when the user needs different functionality for a DataTable, the entire class would have to be replaced or inherited. By constructing a new builder that uses the same IdataSourceAdapter, the user has more freedom on how they choose to implement the adapter.
In the pager control, the look-up of the proper builder is handled by a type safe collection.
publicclass AdapterCollection:DictionaryBase
{
privatestring GetKey(Type key)
{
return key.FullName;
}
public AdapterCollection()
{}
publicvoid Add(Type key,AdapterBuilder value)
{
Dictionary.Add(GetKey(key),value);
}
publicbool Contains(Type key)
{
return Dictionary.Contains(GetKey(key));
}
publicvoid Remove(Type key)
{
Dictionary.Remove(GetKey(key));
}
public AdapterBuilder this[Type key]
{
get{return (AdapterBuilder)Dictionary[GetKey(key)];}
set{Dictionary[GetKey(key)]=value;}
}
}
The AdapterCollection relies on the type of the DataSource, which fits in perfectly with the BoundControl_DataBound method. The index key used is the Type.FullName method, ensuring the index key is unique for each type. This puts the responsibility on the AdapterCollection to contain only one builder for a given type. Adding the builder look-up to the BoundControl_DataBound method results in the following:
public AdapterCollection Adapters
{
get{return _adapters;}
}
privatebool HasParentControlCalledDataBinding
{
get{return _builder != null;}
}
privatevoid BoundControl_DataBound(object sender,System.EventArgs e)
{
if (HasParentControlCalledDataBinding) return;
Type type = sender.GetType();
_datasource = type.GetProperty("DataSource");
if (_datasource == null)
thrownew NotSupportedException("The Pager control
doesn't support controls that don't contain a datasource");
object data =
_datasource.GetGetMethod().Invoke(sender,null);
_builder =
Adapters[data.GetType()];
if (_builder == null)
thrownew NullReferenceException("There is no adapter installed to handle a
datasource of type "+data.GetType());
_builder.Source =
data;
BindParent();
}
The BoundControl_DataBound method also checks to see if the builder is already created with the HasParentControlCalledDataBinding. If it has, then it doesn't go through the exercise of finding the proper builder again. This of course assumes that the user doesn't call DataBinding with different DataSources, which of course they shouldn't. The Adapters table is initialized in the constructor.
public Pager()
{
_adapters = new AdapterCollection();
_adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());
_adapters.Add(typeof(DataView),new DataViewAdapterBuilder());
}
The
last method to implement is to call the BindParent to manipulate and return the data.
privatevoid BindParent()
{
_datasource.GetSetMethod().Invoke(BoundControl,
new object[]
{ _builder.Adapter.GetPagedData( StartRow,ResultsToShow*CurrentPage)});
}
The method is pretty simple since the actual manipulation of the data is done by the Adapter. Once finished, reflection is again used but this time to set the DataSource property of the presentation control. The behavior of the pager control is now nearly finished, but without proper presentation, it is not very useful.
As stated earlier the best way to achieve a solid separation of presentation from logic is to use templates, or more specifically the Itemplate interface. Indeed, MS realizes the power of templates and employs them almost everywhere, even in the page parser itself. Templates unfortunately aren't the easiest mechanism, and they take a while to learn, but there are many tutorials available that should ease the learning curve. Getting back to the pager control, the pager control contains the following buttons, First, Previous, Next, Last plus the individual pagers. The four navigation buttons are chosen from the ImageButton class instead of the LinkButton class. From a professional Web design point of view an image is more often useful than just a link.
public ImageButton FirstButton{get {return First;}}
public ImageButton LastButton{get {return Last;}}
public ImageButton PreviousButton{get {return Previous;}}
public ImageButton NextButton{get {return Next;}}
The individual pagers are created dynamically since they depend on the datasource and how many records and pagers should be shown per page. The pagers will be added to a Panel which allows the Web designer to specify where he would like to show the pagers. More on the creation of the pagers later, for now the pager control needs to provide a template to allow the user to customize the look and feel of the pager.
[TemplateContainer(typeof(LayoutContainer))]
public ITemplate Layout
{
get{return (_layout;}
set{_layout =value;}
}
publicclass LayoutContainer:Control,INamingContainer
{
public LayoutContainer()
{this.ID = "Page";}
}
The LayoutContainer class provides a holder for the template. The addition of the custom ID in a template container is always a good idea since it prevents problems that arise with events and how they're called by the page. The following UML diagram defines the presentation of the pager control.

The first step in creating a template is to define a simple layout in the aspx page:
<Layout>
<asp:ImageButtonid="First"Runat="server" AlternateText="first"/>
<asp:ImageButtonid="Previous"Runat="server"AlternateText="previous"/>
<asp:ImageButtonid="Next"Runat="server"AlternateText="next"/>
<asp:ImageButtonid="Last"Runat="server"AlternateText="last"/>
<asp:PanelID="Pager"Runat="server"/>
</Layout>
For the purpose of this example, the layout doesn't contain any formatting, such as tables etc ... But they can and should be included as shown later.
The Itemplate interface provides only one method, InstantiateIn, which parses the template and binds it with the holder.
privatevoid InstantiateTemplate()
{
_container = new LayoutContainer();
Layout.InstantiateIn(_container);
First = (ImageButton)_container.FindControl("First");
Previous =
(ImageButton)_container.FindControl("Previous");
Next = (ImageButton)_container.FindControl("Next");
Last = (ImageButton)_container.FindControl("Last");
Holder = (Panel)_container.FindControl("Pager");
this.First.Click
+= new System.Web.UI.ImageClickEventHandler(this.First_Click);
this.Last.Click
+= new System.Web.UI.ImageClickEventHandler(this.Last_Click);
this.Next.Click
+= new System.Web.UI.ImageClickEventHandler(this.Next_Click);
this.Previous.Click
+= new System.Web.UI.ImageClickEventHandler(this.Previous_Click);
}
The first thing the page control's InstatiateTemplate method does is instantiate the template, Layout.InstantiateIn(_container). The container is just another control that is used like any other control. Making use of this fact, the InstantiateTemplate method finds the four navigational buttons and the panel needed to hold the individual pagers. The buttons are found by their IDs. This is a small limitation placed on the pager control. The navigational buttons MUST have the pre-defined IDs, "First","Previous","Next","Last", "Pager", or they won't be found. Unfortunately this is the only alternative with the presentation structure chosen. The other alternative is for each of the buttons to inherit from the ImageButton class thereby defining a new type. Since each button will be of a different type then a recursive search through the container could be implemented to find a particular type forgoing the need to name the buttons properly. But with proper documentation such a small requirement shouldn't pose problems.
Once the four buttons are found, then the proper event handlers are bound to them. A very important decision must be made as to when the InstantiateTemplate should be called. Normally such a method would be called in the CreateChildControls method, since it basically does just that, creates child controls. Since the pager control doesn't ever change its child controls, then it doesn't need the functionality provided by the CreateChildControls method to change its rendering state based on some event. The quicker the child controls get rendered the better. A good place then to call the instantiate method is in the OnInit event.
protectedoverridevoid OnInit(EventArgs e)
{
_boundcontrol = Parent.FindControl(BindToControl);
BoundControl.DataBinding
+= new EventHandler(BoundControl_DataBound);
InstantiateTemplate();
Controls.Add(_container);
base.OnInit(e);
}