Generics in ASP .NET? Why?
Anyone who has ever built a WebForms based application has no doubt come across the well-worn Repeater
control. A staple of web programming, it allows for the display of a collection of items, and exposes the items through the Eval
or Bind
methods.
Under the hood, Eval
and Bind
work through Reflection in order to provide late binding support. This is also what you see if you reference properties such as:
<%# Container.DataItem.PropertyNameHere %>
The reason for this is that ASP.NET allows transparent late binding, so even though it believes that Container.DataItem
is typed as 'Object
', the evaluation will only occur at runtime. This leads to all sorts of problems such as:
- Pages breaking when a property is changed, and no warnings at compile-time.
- Reduced performance, as every data-field display point becomes a Reflection call.
If we could tell ASP.NET what the type of the data items actually are, though, then during site start-up/compilation, we would be able to ensure the right types are being used, and no late-binding ever occurs. Compile time checking of all pages, master-pages, and user control binding tags sounds good?
If that's not enough for you, what about full intellisense support?
Generics in ASP.NET? How?
During start-up and the first hit to a page in a given folder, ASP.NET performs batch compilation of your ASPX files. That is, the conversion of them and the content therein into actual .NET code that will be run. The trouble previously with ASP.NET Generics was that there is no syntax support in ASP.NET to specify a generic type, for example:
<asp:Repeater ID="Repeater1" runat="Server" />
is all well and good, but you can't say:
<asp:GenericRepeater<String> ID="StringRepeater" />
As a result, we're going to have to find a workaround. I've ended up taking a very similar route to another tutorial that gives a typed Repeater
implementation, by Andrey Shchekin. You can find his tutorial here. The way this works is as follows:
- We intercept compile-time construction of pages using a
ControlBuilder
sub-class assigned to a non-generic class. - This
ControlBuilder
's Init
method switches the non-generic type for a real generic type, using type-names read from properties of the non-generic control. - We wrap the real type inside a
TypeDelegator
that intercepts any calls to properties of the type (such as the templated containers). - Whenever ASP.NET requests the
TemplateContainer
attribute of the type, we give it back a dynamically constructed instance that contains the correct strong-typing information.
In the example code, I've created a 'GenericRepeater
' that displays a strongly typed collection and the items therein. I have then sub-classed that GenericRepeater
with <Object, Object>
in order to create an ObjectRepeater
like so:
[ControlBuilder(typeof(GenericTemplatedControlBuilder))]
[GenericControlType(typeof(GenericRepeater<,>), "CollectionTypeName", "ItemTypeName")]
public class ObjectRepeater : GenericRepeater<IEnumerable<Object>, Object>
The ObjectRepeater
inherits the real generic repeater type, and the attributes tell ASP.NET to use the special control builder instead of the base one. The GenericTemplatedControlBuilder
then looks for the GenericControlType
attribute, and will activate the 'real' GenericRepeater
instance with the two type parameters.
Inside the generic repeater implementation, we mark up the various ITemplate
properties with other attributes:
[^__strong__^GenericTemplateContainerParameter(typeof(IndexedDataContainer<,>), 0, 1)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ItemTemplate { get; set; }
The GenericTemplateContainerParameter
declaration tells the system what type of data container to instantiate, and also the indices of the generic parameters to pass down. Thus, since IndexedDataContainer
takes two generic parameters, we are telling to take generic parameters 0 (TCollectionType
) and 1 (TItemType
) from the GenericRepeater
as the relevant parameters.
When ASP.NET scans the properties of the control, we have now switched the control for a generic control and instructed our GenericControlPropertyDelegator
to remap all calls for TemplateContainer
to our real, generic template containers.
The Final Product
Now that we have everything in play, let's define a simple ASP.NET page that uses the control:
And, there you have it, a simple generics framework for ASP.NET that allows you to deal with pretty much any generic control scenario, and is not specifically tied down to the example for use with Repeater
s.
Notes About the Code
The example code supplied does not actually implement any post-back support, so don't be surprised if you need to add some code to load/save data in the viewstate and so forth.
Additionally, if you're going to reference this code on your own Web Applications, please ensure you take the web.config
elements across to register the CFC tag prefix.