woensdag 11 april 2012

Creating a strongly typed Repeater Control in ASP.NET using generics

The Repeater is one of my favorite and most used controls when developing websites in ASP.NET Webforms. It is the best tool at hand for generating dynamic lists based on collections of data. I'm also very fond of using databinding syntax in .aspx files avoiding code-behind files because in my opinion it increases readability and manageability.

One of the main drawbacks when using the Repeater in conjunction with databinding syntax is the need for casting the DataItem property to it's actual type:


I would like to share my take on a strongly typed Repeater which removes the need for casting objects, increases productivity and readability and unlocks intellisense!


The solution

The solution requires you to create two base classes which includes the core functionality and then for every typed Repeater create a simple inherited class which defines the type. As you might have noticed in the above screenshot I introduced a new property called TypedItem. You are free to create a name which suits your needs e.g. Container.I for improved readability.

Setting up the base classes:
1. Create a generic Repeater base class

This is the main workhorse of the solution. It was neccessary to override the InitializeItem method of the Repeater class because the default Repeater works with the private variables for the templates. This implementation uses the properties for each template which is required because we will be overriding the ItemTemplate and AlternatingItemTemplate for our typed Repeaters.

Also the CreateItem method is overriden to actually create a generic RepeaterItem<T> which will be created in step 2.

using System.Web.UI;
    using System.Web.UI.WebControls;

    public class GenericRepeaterBase<T> : Repeater where T : class
    {
        protected override RepeaterItem CreateItem(int itemIndex, ListItemType itemType)
        {
            return CreateRepeaterItem(itemIndex, itemType);
        }

        private static GenericRepeaterItem<T> CreateRepeaterItem(int itemIndex, ListItemType itemType)
        {
            return new GenericRepeaterItem<T>(itemIndex, itemType);
        }

        protected override void InitializeItem(RepeaterItem item)
        {
            ITemplate template = (ITemplate)null;
            switch (item.ItemType)
            {
                case ListItemType.Header:
                    template = HeaderTemplate;
                    break;
                case ListItemType.Footer:
                    template = FooterTemplate;
                    break;
                case ListItemType.Item:
                    template = ItemTemplate;
                    break;
                case ListItemType.AlternatingItem:
                    template = AlternatingItemTemplate;
                    if (template != null)
                        break;
                    else
                        goto case 2;
                case ListItemType.Separator:
                    template = SeparatorTemplate;
                    break;
            }
            if (template == null)
                return;
            template.InstantiateIn((Control)item);
        }
    }

2. Create a generic RepeaterItem base class

This class contains the property that you use while working with the Repeater. You can rename the TypedItem property as you wish.

using System.Web.UI.WebControls;

    public class GenericRepeaterItem<T> : RepeaterItem where T : class
    {
        public GenericRepeaterItem(int itemIndex, ListItemType itemType)
            : base(itemIndex, itemType)
        {

        }

        public T TypedItem
        {
            get { return (T)this.DataItem; }
        }
    }

Usage:
3. Create a typed Repeater

Now for every Repeater that you would like to use strongly typed you must create an easy implementation of the GenericRepeaterBase<T> class. The example below creates the PersonRepeater mentioned before.

The PersistenceMode attribute needs to be redefined because it is not automatically inherited. The TemplateContainer attribute and the generic type definition on the class make the neccessary definitions for the Repeater to work.

public class PersonRepeater : GenericRepeaterBase<GenericRepeaterItem<Person>>
    {
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(GenericRepeaterItem<Person>))]
        public override ITemplate ItemTemplate { get; set; }

        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(GenericRepeaterItem<Person>))]
        public override ITemplate AlternatingItemTemplate { get; set; }
    }

4. Use it!

public class Person
    {
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
    }

<%@ Register TagPrefix="asp" Namespace="TypedRepeater" Assembly="TypedRepeater" %>
    <asp:PersonRepeater runat="server">
        <ItemTemplate>
            Name: <% #Container.TypedItem.Name %><br />
            Date of birth: <%#Container.TypedItem.DateOfBirth.ToShortDateString() %>
        </ItemTemplate>
    </asp:PersonRepeater>

I hope this code brings you lots of joy! ;)

3 opmerkingen:

  1. Well, i had to say that this approach is good.
    But you still need to define a class for each repeater, which is very annoying. Here is an article which takes one step
    further and eliminates the need for derived PersonRepeater class. http://www.codeproject.com/Articles/18049/A-Typed-Repeater-in-ASP-NET
    I've used it in my project and it works perfectly.

    BeantwoordenVerwijderen
  2. @Alexey:
    It's a matter of preference. From that approach I don't like the way it's implemented. I'd rather create a simple strongly typed definition in code then through an error-prone attribute property.

    This is besides the fact that I didn't get that solution working :)

    BeantwoordenVerwijderen
  3. I love it!
    This is one of the things I have been trying to figure out. I get an odd error, though. I have translated it to VB and changed it over to one of my own custom objects.

    Everything seems to work as expected until I actual run the page and try to render. On the databinding of the repeater, I get the following error:

    System.InvalidCastException was unhandled by user code
    HResult=-2147467262
    Message=Unable to cast object of type 'SDCore.GenericRepeaterItem`1[SDCore.GenericRepeaterItem`1[SDCore.StateEntity]]' to type 'SDCore.GenericRepeaterItem`1[SDCore.StateEntity]'.

    Imports System.Web.UI
    Imports System.Web.UI.WebControls

    Public Class GenericRepeaterBase(Of T As Class)
    Inherits Repeater
    Protected Overrides Function CreateItem(itemIndex As Integer, itemType As ListItemType) As RepeaterItem
    Return CreateRepeaterItem(itemIndex, itemType)
    End Function

    Private Shared Function CreateRepeaterItem(itemIndex As Integer, itemType As ListItemType) As GenericRepeaterItem(Of T)
    Return New GenericRepeaterItem(Of T)(itemIndex, itemType)
    End Function

    Protected Overrides Sub InitializeItem(item As RepeaterItem)
    Dim template As ITemplate = DirectCast(Nothing, ITemplate)
    Select Case item.ItemType
    Case ListItemType.Header
    template = HeaderTemplate
    Exit Select
    Case ListItemType.Footer
    template = FooterTemplate
    Exit Select
    Case ListItemType.Item
    template = ItemTemplate
    Exit Select
    Case ListItemType.AlternatingItem
    template = AlternatingItemTemplate
    If template IsNot Nothing Then
    Exit Select
    Else
    template = FooterTemplate
    Exit Select
    End If
    Case ListItemType.Separator
    template = SeparatorTemplate
    Exit Select
    End Select
    If template Is Nothing Then
    Return
    End If
    template.InstantiateIn(DirectCast(item, Control))
    End Sub
    End Class

    Public Class GenericRepeaterItem(Of T As Class)
    Inherits RepeaterItem
    Public Sub New(itemIndex As Integer, itemType As ListItemType)

    MyBase.New(itemIndex, itemType)
    End Sub

    Public ReadOnly Property TypedItem() As T
    Get
    Return DirectCast(Me.DataItem, T)
    End Get
    End Property
    End Class

    Public Class StateRepeater
    Inherits GenericRepeaterBase(Of GenericRepeaterItem(Of StateEntity))

    _
    _
    Public Overrides Property ItemTemplate As ITemplate
    Get
    Return MyBase.ItemTemplate
    End Get
    Set(value As ITemplate)
    MyBase.ItemTemplate = value
    End Set
    End Property

    _
    _
    Public Overrides Property AlternatingItemTemplate As ITemplate
    Get
    Return MyBase.AlternatingItemTemplate
    End Get
    Set(value As ITemplate)
    MyBase.AlternatingItemTemplate = value
    End Set
    End Property


    End Class

    BeantwoordenVerwijderen