Creating strongly typed repeater

When you develop ASP.Net application, you quickly discover that the basic ASP:Repeater is a killer control, although it looks quite dumb at first look, compared to DataList or the powerfull GridView, but actually its simplicity, and it’s lightness offer the flexibility required to do all those funcky stuff you only find in enterprise developpement …

Ok, in this perfect world there is yet a dark side: the repeater is a late binding control and use a lot of reflection, which make things a lot more slower, and unhandy, because you do not have auto completion… So my idea was to generate some strongly typed Repeater, as part of the .netTiers framework. A repeater will be generated for each Table and View, and will work in conjunction with the actual strongly typed DataSource.

Templated control

To create these repeaters, we actually create a ASP.NET templated control, with the following ITemplate ( to match standard repeater), i mean HeaderTemplate, ItemTemplate, AlternatingItemTemplate and FooterTemplate. Here is the code of my repeater for a simple "Product" entity:

ParseChildren(true)]
[ToolboxData("<{0}:ProductRepeater runat="server"></{0}:ProductRepeater>")]
public class ProductRepeater : Control, System.Web.UI.INamingContainer
{
public ProductRepeater()
{
}

public override ControlCollection Controls
{
get
{
this.EnsureChildControls();
return base.Controls;
}
}

private ITemplate m_headerTemplate;
[Browsable(false)]
[TemplateContainer(typeof(ProductItem))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate HeaderTemplate
{
get { return m_headerTemplate; }
set { m_headerTemplate = value; }
}

private ITemplate m_itemTemplate;
[Browsable(false)]
[TemplateContainer(typeof(ProductItem))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate ItemTemplate
{
get { return m_itemTemplate; }
set { m_itemTemplate = value; }
}


private ITemplate m_altenateItemTemplate;
[Browsable(false)]
[TemplateContainer(typeof(ProductItem))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate AlternatingItemTemplate
{
get { return m_altenateItemTemplate; }
set { m_altenateItemTemplate = value; }
}

private ITemplate m_footerTemplate;
[Browsable(false)]
[TemplateContainer(typeof(ProductItem))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate FooterTemplate
{
get { return m_footerTemplate; }
set { m_footerTemplate = value; }
}

[Category("Data")]
public virtual string DataSourceID
{
get
{
if (ViewState["DataSourceID"] == null)
{
return string.Empty;
}
return (string)ViewState["DataSourceID"];
}
set
{
ViewState["DataSourceID"] = value;
}
}

System.Collections.IEnumerable m_currentView;

private System.Collections.IEnumerable ConnectToDataSourceView()
{
if (m_currentView == null)
{
NetTiers.SQLiteDemo.Web.Data.ProductDataSource datasource = null;
Control ctl = this.Page.FindControl(DataSourceID);
if (ctl == null)
{
throw new System.Web.HttpException("Datasource does not exists");
}
datasource = ctl as NetTiers.SQLiteDemo.Web.Data.ProductDataSource;
if (datasource == null)
{
throw new System.Web.HttpException("Datasource must be data control");
}

System.Collections.IEnumerable dsv = datasource.GetEntityList(); //this.DataMember);
if (dsv == null)
{
throw new System.Web.HttpException("View not found");
}
m_currentView = dsv;
}
return m_currentView;
}


protected override void CreateChildControls()
{
if (ChildControlsCreated)
{
return;
}
Controls.Clear();

System.Collections.IEnumerable datas = (System.Collections.IEnumerable)ConnectToDataSourceView();

if (datas != null)
{
if (m_headerTemplate != null)
{
Control headerItem = new Control();
m_headerTemplate.InstantiateIn(headerItem);
Controls.Add(headerItem);
}

int pos = 0;
foreach (object o in datas)
{
NetTiers.SQLiteDemo.Entities.Product entity = o as NetTiers.SQLiteDemo.Entities.Product;
ProductItem container = new ProductItem(entity);

if (m_itemTemplate != null && (pos % 2) == 0)
{
m_itemTemplate.InstantiateIn(container);
}
else
{
if (m_altenateItemTemplate != null)
{
m_altenateItemTemplate.InstantiateIn(container);
}
else if (m_itemTemplate != null)
{
m_itemTemplate.InstantiateIn(container);
}
else
{
// no template !!!
}
}
Controls.Add(container);
pos++;
}

if (m_footerTemplate != null)
{
Control footerItem = new Control();
m_footerTemplate.InstantiateIn(footerItem);
Controls.Add(footerItem);
}
ChildControlsCreated = true;
}
}

protected override void OnPreRender(EventArgs e)
{
base.DataBind();
}

#region Design time

internal string RenderAtDesignTime()
{
return "TODO create a designer";
}

#endregion
}

The code is a bit long but it’s mostly due to the templates properties. The main logic is in the two methods "ConnectToDataSourceView" and "CreateChildControls". The first take the given datasourceId and search for the control in the page, then it get the entity list from it. The second a bit more tricky, it creates the children controls depending of the differents templates. Please note that for the ItemTemplate and AlternatingItemTemplate we loop on each entity, we create a container for this entity and send it to the current ITemplate to render it.

Strongly typed TemplateContainer

It’s probably not obvious here, but one of the most intersting stuff is the TemplateContainer attribute on the ITemplate properties: we use them to indicate the use of strongly typed template container. Here is the code of the Product Template container:

<">

[System.ComponentModel.ToolboxItem(false)]
public class ProductItem : System.Web.UI.Control, System.Web.UI.INamingContainer
{
private NetTiers.SQLiteDemo.Entities.Product _entity;

public ProductItem()
: base()
{ }

public ProductItem(NetTiers.SQLiteDemo.Entities.Product entity)
: base()
{
_entity = entity;
}

[System.ComponentModel.Bindable(true)]
public System.Int64 Id
{
get { return _entity.Id; }
}
[System.ComponentModel.Bindable(true)]
public System.String Name
{
get { return _entity.Name; }
}
[System.ComponentModel.Bindable(true)]
public System.String Description
{
get { return _entity.Description; }
}
[System.ComponentModel.Bindable(true)]
public System.Int64 CategoryId
{
get { return _entity.CategoryId; }
}

}

The template container acts as a decorator, it takes our entity in the constructor, and exposes the entity properties as container properties: that the "magic" part, now we have a strongly typed Container in our web page, no more Container.Eval("Name"), but <%# Container.Name %>, with full intellisense !!! and of course, no more reflection.

Use the repeater

as we’ve said the control is very friendly and offer intelisense on the child templates, and on the container properties, here is a sample of use:

<data:ProductRepeater
ID="productRepeater1"
runat="server"
DataSourceID="productDataSource1" >
<HeaderTemplate>
<h1>List of product</h1>
</HeaderTemplate>
<ItemTemplate>
<b><%# Container.Name %></b><br />
<%# Container.Description %>
<hr />
</ItemTemplate>
</data:ProductRepeater>
<data:ProductDataSource
ID="productDataSource1"
runat="server"
SelectMethod="GetAll">
</data:ProductDataSource>

Conclusion

Hopefully this article will help you to write some templated control.
note: adding a typed designer would be a great improvement, will be for a next session.

You’ll find attached a zip file containing the corresponding codesmith template.

John Roland
http://www.serialcoder.net/
http://www.nettiers.com/
http://predicatet.blogspot.com/