Page 1 of 1

Creating a strongly typed templated control

#1 tidyui  Icon User is offline

  • New D.I.C Head
  • member icon

Reputation: 1
  • View blog
  • Posts: 7
  • Joined: 13-May 11

Posted 18 May 2011 - 04:51 AM

Strongly typed templated controls

The full source code for this tutorial is available at my github - https://github.com/tidyui/TidyUI.Web

A key feature to me in ASP.NET for giving the designer a fair chance of creating the layout he/she want's without having to meddle with the code-behind is templated controls. We have all worked with Repeaters, and if you don't walk down the errorenous road of doing half of your databinding in the OnItemDataBound event, it's a clean separation between responsibilities.

The only problem with the Repeater is that it works with untyped data, giving the poor fellow working in the aspx-files no hint on what data is actually available without looking in the code-behind anyways.

The solution to this is of course strongly typed templated controls. In the following example I'll use the following class as my data container. It could be a simple class for a post in a CMS system.

public class Post {
    public string Title { get ; set ; }
    public string Body { get ; set ; }
}


To create a strongly typed control for this data container we need to create two classes, one for the data item, and one for the actual web control that we'll use in our web forms.

/// <summary>
/// Item control.
/// </summary>
public class PostRepeaterItem : Control, INamingContainer, ITemplateItem<Post> {
    public Post DataItem { get ; set ; }
}

/// <summary>
/// Main web control.
/// </summary>
[ToolboxData("<{0}:PostRepeater runat=server></{0}:PostRepeater>")]
public class PostRepeater : TemplateRepeater<PostRepeaterItem, Post> {
    [TemplateContainer(typeof(PostRepeaterItem)), PersistenceMode(PersistenceMode.InnerProperty)]
    public override ITemplate ItemTemplate { get ; set ; }

    [TemplateContainer(typeof(PostRepeaterItem)), PersistenceMode(PersistenceMode.InnerProperty)]
    public override ITemplate AlternateItemTemplate { get ; set ; }
}


As we can see the main web control extends the generic class TemplateRepeater to which we supply the item type and the type of the data object. The item implements the generic interface ITemplateItem. Now let's inspect those base-classes to see what happens beneath the surface.

public interface ITemplateItem<T>
{
    #region Properties
    /// <summary>
    /// Gets/sets the data item.
    /// </summary>
    T DataItem { get ; set ; }
    #endregion
}

[ToolboxData("<{0}:TemplateRepeater runat=server></{0}:TemplateRepeater>")]
public abstract class TemplateRepeater<TControl, TData> : WebControl, INamingContainer where TControl : Control
{
    #region Properties
    /// <summary>
    /// Gets/sets the data collection to use for data bind.
    /// </summary>
    [Category("Behaviour"), DefaultValue(null)]
    public IEnumerable<TData> DataSource { get ; set ; }

    /// <summary>
    /// Gets/sets the item template.
    /// </summary>
    public abstract ITemplate ItemTemplate { get ; set ; }

    /// <summary>
    /// Gets/sets the item template.
    /// </summary>
    public abstract ITemplate AlternateItemTemplate { get ; set ; }
    #endregion

    #region Events
    /// <summary>
    /// This event is triggered each time an item is data bound.
    /// </summary>
    public event GenericEventHandler<TControl> OnItemDataBound ;
    #endregion

    /// <summary>
    /// Databind the available items.
    /// </summary>
    public override void DataBind() {
        for (int n = 0; n < this.DataSource.Count(); n++) {
            TControl item = Activator.CreateInstance<TControl>() ;
            if (item is ITemplateItem<TData>)
                ((ITemplateItem<TData>)item).DataItem = this.DataSource.ElementAt(n) ;

            if (this.AlternateItemTemplate != null && n % 2 == 0)
                this.AlternateItemTemplate.InstantiateIn(item) ;
            else this.ItemTemplate.InstantiateIn(item) ;

            this.Controls.Add(item) ;

            if (this.OnItemDataBound != null)
                OnItemDataBound(this, new GenericEventArgs<TControl>(item)) ;
        }
        base.DataBind() ;
    }

    /// <summary>
    /// Renders the control to the given html writer
    /// </summary>
    /// <param name="writer">The html writer</param>
    protected override void Render(HtmlTextWriter writer) {
        if (this.CssClass != "")
            writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass) ;
        writer.RenderBeginTag(HtmlTextWriterTag.Div) ;
        RenderContents(writer) ;
        writer.RenderEndTag() ;
    }
}



If we look at the DataBind method we can see how the TemplateRepeater uses it's two generic types to create instances of the item control and set its typed DataItem to the items in the DataSource collection. If you have attached an event handler to the OnItemDataBound event, even the event argument passed to this method
will have a typed data item.

And given that you in your web.config has added a new key in the pages/controls section pointing out the assembly and namespace where your web control is located and has given it a tagprefix you will now be able to use it in your aspx-file. In the following example the tagprefix tidy has been given to the controls.

<tidy:PostRepeater ID="rptPosts" runat="server">
    <ItemTemplate>
        <h2><%# Container.DataItem.Title %></h2>
        <p><%# Container.DataItem.Body %></p>
    </ItemTemplate>
</tidy:PostRepeater>


That's basically it. You've now created a flexible and easy to use, strongly typed repeater.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1