In my travels I’ve had a chance to spread the good word of Web control development to many around the country; and I’ve also had a chance to meet many programmers who have been writing controls for a while.
While it’s awesome to get a chance to talk code with people who enjoy the same areas of .NET that I do, I notice that there is plenty in the Web control arena that they are not familiar with. Control templates are probably the quintessential example of this. I urge you to read this article and learn about what is probably the most important feature of custom Web control development that contributes to writing extensible controls.
This article assumes a basic knowledge of custom Web control development as explained in “Custom Web Controls Demystified” published in CoDe Magazine Sept/Oct 2005 and Nov/Dec 2005. In the interest of space I will not repeat the basics of Web control development such as how properties work or how to add styling. I strongly encourage that you check out the previous issues and read the Web control tutorial articles I wrote.
Control templates allow points of visual extensibility within Web controls.
When I set out to train myself in .NET technologies (back in Beta 1), I was amazed at not only what the framework can do for you but also at how it enables you to extend the framework. I’ve always been a fan of building points of extensibility into my applications and with the .NET Framework, Microsoft has proven that they are as well. Building points of extensibility where other developers can inject their own components is now a common approach to application architecture with several patterns such as providers and plug-ins. So in continuing my series of articles on custom Web control development, I want to introduce you to a technology that allows you to inject points of extensibility into your Web controls called control templates. Along the way, I hope to shed some light on how some of the Microsoft ASP.NET controls perform some of their magic.
Though I will speak in the context of ASP.NET 2.0, everything I will talk about applies to ASP.NET 1.1, with the exception of template editing toward the end. That’s a great new facility introduced in 2.0.
Visual Extensibility
When Microsoft architected ASP.NET around the concept of controls (yes, Microsoft embedded them into the architecture), they gave some of us endless possibilities in introducing some pretty extreme encapsulation and reusability into our Web applications. I say “some of us” because not everyone has embraced writing their own custom Web controls yet (I still have more work to do on that front). However, many developers with experience developing custom controls don’t seem to use templates in any of them.
Web control templates allow you, the control developer, to inserts points of visual extensibility into your custom controls. A Web page developer can use these area place holders to add more child controls to their composite controls. For example, consider the ASP.NET DataGrid (1.1) and GridView (2.0). The GridView allows you to create bound columns and template columns. Bound columns allow you to directly map to a field from a data source. A template column allows you to add other Web controls to that grid’s column, thus fully customizing its rendered appearance (and functionality).
If you drag a new GridView onto a Web Form and set up a couple of bound columns along with a template column, you will have the ability to enter “template editing” (more about that later) and drag other Web controls into this column. Now every rendered row in the grid will contain an instance of those Web controls in that particular column. You’ve seen this in use in many situations by way of additional links, buttons, and even checkboxes and textboxes being displayed in grid rows on a Web page. In fact, if you’ve played with template columns in the Web grids a little, you’re probably aware that each template column gives you several templates to work with, each displaying its contents in a different place relative to that column. I am referring to the ItemTemplate, EditItemTemplate, HeaderTemplate, and FooterTemplate. Microsoft defined these in the template column, thus letting you decide what extra controls you want to display on a standard grid row, on a row that is in “edit” mode, in the columns heading area, and the columns footer area.
The GridView shows how you can use control templates in a repeating fashion, but they don’t have to be as complex as that. In fact, I will show you examples of simple templates that you can insert into any custom control and at any place. This will prove that my original definition of control templates is true-describing them as areas of your control reserved for visual extensibility.
So now I’ll show you how to add template functionality to a custom Web control. I’ll refer you once again to my previous articles. In Part 2 I built a composite control called EmailContact and that is the control to which I will add template functionality. This the first step in my six-step plan to embed template functionality into custom Web controls. The steps are:
- Identify the control you want to enhance.
- Create a template container.
- Create template member variables and properties.
- Add template containers to the control hierarchy.
- Handle event-bubbling.
- Apply design-time functionality.
These steps will account for the remaining agenda in this article.
Template Containers
A template container is actually another composite control that you’ll use to contain the template(s) that you will define. Often you’ll use a panel control for this purpose but for reasons that I will explain later, I like to use another custom control. This control must inherit from the WebControl class and implement the INamingContainer interface which makes it a valid Web control and also gives it the ability to properly and uniquely name any contained child controls. This control will contain the child controls that a page developer will drag into that template later. Remember, a template will be represented as an area of your control that a page developer will use into which to drag other controls, thereby extending your control visually. This code snippet illustrates how I’ll create an empty composite control that will serve as my template container.
Public Class TemplateItem
Inherits _
System.Web.UI.WebControls.CompositeControl
End Class
Note that I don’t even have to have the ToolboxData attribute set since I won’t actually drag and drop this control onto a form. Also notice that I’m inheriting from the CompositeControl class which is new in ASP.NET 2.0. This automatically inherits from WebControl and implements INamingContainer. So you have an empty control that will serve as the container for your template(s) and whose Controls collection you’ll use (or should I say, the page developer will use).
EventHandler delegate has an overload that accepts an event argument object type in a generic. This eliminates the need to create a custom delegate to handle a custom event argument object.
I’ll leave the template container control as-is for now in order to get running with the first template example, but I’ll get back to it later on. Now I’ll show you how to define your templates.
Defining Templates
Since I want to add template functionality to my EmailContact control, I have to start by determining where I want this. If you go back to the articles where I developed this control you will see that it renders a form for a user to fill out like you would see in a “Contact Us” scenario (Figure 1). The control displayed a heading at the top of the form and a “send” button below the form fields. I want to add two templates to this control; one directly below the heading and one directly above the button. I want to give the page developer the ability to put whatever he or she wants in these areas, thus enhancing what the control will display. In fact, since this control has the ability to turn off the heading and the button, the positions of these two templates can give the page developer complete flexibility in what will display around the e-mail form.
Now you know that I want to add two templates to this control and where I want to add them. It will not come as a surprise that I will call them HeaderTemplate and FooterTemplate. So let’s talk about what these names mean.
The names HeaderTemplate and FooterTemplate are going to be exposed as properties in my EmailContact control, and are to represent the templates I will offer page developers so they can extend this control. However, while in the past I taught you how to create Web control properties using the ViewState storage technique, I’ll code these properties using member variables as if they were contained in ordinary objects. You don’t need to include ViewState in this because the page developer will fill these properties during page-design time. This means that their contents will be coded into the ASPX page so state management is unnecessary.
So I’ll start by creating two member variables of the type ITemplate.
Protected _HeaderTemplate As ITemplate
Protected _FooterTemplate As ITemplate
This interface defines one method called InstantiateIn which I will use shortly. Now I’ll create two properties that expose these member variables in the standard fashion that object properties do all the time.
Public Property HeaderTemplate() As ITemplate
Get
Return _HeaderTemplate
End Get
Set(ByVal value As ITemplate)
_HeaderTemplate = value
End Set
End Property
Public Property FooterTemplate() As ITemplate
Get
Return _FooterTemplate
End Get
Set(ByVal value As ITemplate)
_FooterTemplate = value
End Set
End Property
The properties (or member variables) do not need a concrete instantiation because they will receive one as soon as the page developer fills the template.
Believe it or not, we’re half way there. I’ve created a control to act as my template container (if you’re still confused as to why I will soon make this clear), and I’ve created a couple of properties to represent my templates. Before I actually use these properties and the container control, I have to decorate the properties with the TemplateContainer and PersistenceMode attributes.
<TemplateContainer(GetType(TemplateItem)),
PersistenceMode(PersistenceMode.InnerProperty)> _
Public Property HeaderTemplate() As ITemplate
Get
Return _HeaderTemplate
End Get
Set(ByVal value As ITemplate)
_HeaderTemplate = value
End Set
End Property
<TemplateContainer(GetType(TemplateItem)),
PersistenceMode(PersistenceMode.InnerProperty)> _
Public Property FooterTemplate() As ITemplate
Get
Return _FooterTemplate
End Get
Set(ByVal value As ITemplate)
_FooterTemplate = value
End Set
End Property
The TemplateContainer attribute tells this property where the template it represents will be contained. I’ve mentioned that repeatedly already and when I add the templates to my control hierarchy, things will become clearer on this topic. The second attribute, PersistenceMode merits a more detailed explanation.
If you’ve read my previous Web control articles, I used an attribute on properties of the Style object so it may be a little familiar to you. Let me start by reminding you what happens when you drag an EmailContact Web control onto a Web Form. If you switch to ASPX view, you would see the control like this:
<dnd:EmailContact2 ID="EmailContact1"
runat="server">
</dnd:EmailContact2>
The PersistenceMode attribute, when used with the enum value PersistenceMode.InnerProperty, orders this property to display as an inner tag within the tags that make this control. The properties I created, HeaderTemplate and FooterTemplate, will by default, display as attributes in the EmailContact2 tags displayed above. By marking it as an “InnerProperty”, they will display like this:
<dnd:EmailContact2 ID="EmailContact1"
runat="server">
<HeaderTemplate></HeaderTemplate>
<FooterTemplate></FooterTemplate>
</dnd:EmailContact2>
When you add controls to these templates, the reasons for this will become clear.
Adding to the Control Hierarchy
Now it’s time to insert these template properties into the EmailContact control’s control hierarchy. This control hierarchy includes all the child controls that make up the EmailContact control, including the heading and the “send” button.
Event bubbling allows the bubbling up of unhandled events and their management from one centralized location.
The first thing I have to do is decide logically where I want these templates displayed. I did that already when I decided that I wanted the HeaderTemplate to display directly below the e-mail form’s heading, and the FooterTemplate directly above the “send” button. But now I also have to decide where this will happen, physically in the code.
As I taught in previous articles, composite controls get their control hierarchy built in a method override called CreateChildControls. There, all the child controls get added to the Controls collection property as well as some literal text, typically in the form of HTML table code used to position the child controls. In the case of the EmailContact control, I built an HTML table in this method and inserted all the child controls in between table row tags. Here’s an example of what this looks like:
With Me.Controls
.Add(New LiteralControl("<table>"))
.Add(New LiteralControl("<tr><td>"))
.Add(lblHeading)
.Add(New LiteralControl("</td></tr>"))
.Add(New LiteralControl("<tr><td>"))
.Add(ctlFromEmail)
.Add(New LiteralControl("</td></tr>"))
…………
.Add(New LiteralControl("<tr><td>"))
.Add(btnSend)
.Add(New LiteralControl("</td></tr>"))
.Add(New LiteralControl("</table>"))
End With
So inserting my template properties will literally mean adding a couple of new rows to the table. However, you’ll recall that I declared the template properties as type ITemplate and did not instantiate them. I told you that when the page developer uses my templates, they will get instantiated. I need to account for this not taking place by checking the template properties for null values. If they are not null (nothing), it means that the page developer has used them and I can add their contents to the control hierarchy. If the properties evaluate as null then the template does not contain anything on the form and I don’t need to do anything with it.
So a condition check is the first thing I have to do before I add anything to the control hierarchy. Once this condition passes, I then have to create an instance of the container control I created earlier, TemplateItem. You’ve been taught in the past that a composite control’s Controls collection contains all the control’s children. These children are other Web controls, so the need for the TemplateItem container control is becoming more obvious.
Let’s take a look at the code the inserts the HeaderTemplate into the EmailContact control’s control hierarchy, and then I’ll continue explaining it. I’m including some of the surrounding code so you get an idea of where I am inserting it.
.Add(lblHeading)
.Add(New LiteralControl("</td></tr>"))
If _HeaderTemplate IsNot Nothing Then
Dim headerTemplateItem As TemplateItem = _
New TemplateItem
headerTemplateItem.ID = "headerTemplateItem"
_HeaderTemplate.InstantiateIn( _
headerTemplateItem)
.Add(New LiteralControl("<tr><td>"))
.Add(headerTemplateItem)
.Add(New LiteralControl("</td></tr>"))
End If
.Add(New LiteralControl("<tr><td>"))
.Add(ctlFromEmail)
Notice that first I’m creating an instance of the container control and setting its ID property. This is a common practice with any composite control’s children which I taught you in my previous articles. The next thing you see is where the hook up happens between the container and the template property. Remember I told you that the ITemplate interface defines the InstantiateIn method and here is where I use it.
_HeaderTemplate.InstantiateIn( _
headerTemplateItem)
This line of code takes the HeaderTemplate property, along with all its contents (whatever the page developer added to it), and instantiates it all inside the container control instance. Now that I have an instance of an actual Web control, headerTemplateItem, I treat it like any other Web control that I want to build into a control’s children hierarchy. So I do this by simply adding it to the Controls collection like everything else; and like everything else I use HTML table elements to correctly position it.
So not to bore you too much, I’ll save you the agony of watching me do exactly the same thing for the FooterTemplate. The only difference would be the name of the object I use to instantiate the TemplateItem control; I would use one named footerTemplateItem.
Now you know how to create and add templates to custom Web controls; at least in the most basic way. I still have some more to talk about but first let me show you how to use what you’ve created so far.
Using Templates from the Page
Remember this code?
<dnd:EmailContact2 ID="EmailContact1"
runat="server">
<HeaderTemplate></HeaderTemplate>
<FooterTemplate></FooterTemplate>
</dnd:EmailContact2>
It’s what the control will render on the ASPX page when you drop it on the form. I can add any other Web controls I want in-between any of the two template tags; remember these represent the two template properties. So I’m going to do just that. I’ll add a checkbox and textbox to the header template and a link button to the footer template.
<HeaderTemplate>
<asp:CheckBox ID="CheckBox1" runat="server" />
<asp:TextBox ID="TextBox1"
runat="server"></asp:TextBox>
</HeaderTemplate>
<FooterTemplate>
<asp:LinkButton ID="LinkButton1"
runat="server">LinkButton</asp:LinkButton>
</FooterTemplate>
When I switch to design view, I’ll see something like you see in Figure 2.
So now you see the page developer (me in this case) has added controls to my control completely unbeknownst to me. OK, visually that’s all fine and dandy, but the page developer has to be able to do something with these, right? I’m glad you agree, so next I’m going to teach you how to access these controls from the page’s code-behind page so they can become functional.
Accessing Template Contents
Template content is just like a Web control’s child controls, in the way that they cannot be “directly” accessed from the hosting page’s code-behind page. By “directly” I mean as easy as saying:
TextBox1.Text = "Miguel"
Remember, a control can only directly access its immediate Controls collection. From the code in my EmailContact control, I can access all the children I put in there. From the code-behind of a form, I can only directly access the EmailContact control because it resides in the Controls collection of the form (a form is a control too). Anything deeper than that requires a bit more work and templates are no exception. To dig into a Web control’s internal children, you would normally need to use the FindControl method and access them by their ID. I’ll start there but remember that the child control I am looking for is the template’s container control. So in the code-behind class for a form, I can use code like this:
Dim container As TemplateItem = _
DirectCast(EmailContact1.FindControl( _
"headerTemplateItem"), TemplateItem)
This will give me access to the header template container so using the same technique, I can dive deeper and obtain its contents-the controls the page developer added:
Dim txt1 As TextBox = _
DirectCast(container.FindControl( _
"TextBox1"), TextBox)
Now I can use the visual power of the templates and their behavioral power as well by accessing their contents. However, there is one piece of behavior that you may also need. If a developer drops a link or a button into one of the templates, they need to be able to trap its Click event, right? I created the templates with no knowledge of what contents may go into them so I have to account for everything and that includes event activity.
You may be familiar with an event in the GridView called ItemCommand. This event receives click actions from links and buttons that a page developer may drop into a grid’s template columns. Now I’m going to show you exactly how this works.
Event Bubbling
When I created the EmailContact control I added several child controls into it, one of which required some amount of event handling. The button I added into my control to perform the actual e-mail sending raised its Click event as any button will. What’s important to realize here is that a control raises an event only to its parent, so in this case the “send” button raised its Click event to its parent, the EmailContact control. In the method I wrote that captured the button’s event, I made the decision to either handle the event or fire a new event up to my control’s parent, the form. This is standard operating procedure for child control event handling within composite Web controls. However, when I injected template functionality into the control, I did so completely aware that the page developer might put some links or buttons into one of my templates. In fact, I don’t know what the page developer may throw into my templates and I need to account for actions that may need to be taken on such template content.
This is where event bubbling comes into play. The concept of event bubbling means taking unhandled events and shooting them up the chain of parent controls until they reach the surface; the form. So now you’re probably under the impression that any event raised by any control inserted into a template will be handled by event bubbling-wrong. Event bubbling does not happen magically, it must be ordered by the controls when they fire their events. In the case of Microsoft’s ASP.NET controls, not all events order bubbling to take place. In fact, only three controls contain this functionality: Button, LinkButton, and ImageButton. Not only that, but in these controls it’s not the Click event that orders bubbling to take place, it’s another event called Command.
A Control Designer class provides design-time functionality for a Web control and is wired to the control by way of a class-level attribute decoration.
If you’ve ever wondered why buttons and links have this event, bubbling is the reason. A button’s (or link) Click event does not automatically bubble up, but its Command event does. Let me give you a peek into the code for the ASP.NET Button control. The button’s Click event is raised within a method called OnClick and its code looks something like this:
Protected Overridable Sub OnClick(ByVal e As
EventArgs)
RaiseEvent Click(Me, e)
End Sub
The Command event is raised within a method called OnCommand which contains the following code:
Protected Overridable Sub OnCommand(ByVal e As
CommandEventArgs)
RaiseEvent Command(Me, e)
RaiseBubbleEvent(Me, e)
End Sub
Both events are raised by the Button control back to back with their only difference being what you see in the code above. The Command event also makes a call to the RaiseBubbleEvent method and it is this method call that orders event bubbling to take place. This bubbling order will now continue up the chain until it is finally handled somewhere along the way. You handle events that are bubbled up a chain by overriding the OnBubbleEvent method.
So let’s reiterate where I’ve taken you so far. I’ve explained that buttons and links are already set up to bubble up their Command event. This gives me something to rely on in the EmailContact control. Should a page developer drop a link or a button into one of my templates, I can now override the OnBubbleEvent method in the EmailContact control in order to handle the bubbling coming from any controls dropped into one of my templates. Let’s see what that looks like.
Protected Overrides Function OnBubbleEvent( _
ByVal source As Object, ByVal e As EventArgs) _
As Boolean
If source.ID <> "btnSend" AndAlso _
TypeOf e Is CommandEventArgs Then
Dim cmd As CommandEventArgs = _
CType(e, CommandEventArgs)
RaiseEvent ItemCommand(Me, _
New CommandEventArgs(cmd.CommandName, _
cmd.CommandArgument))
Return True 'cancel further bubbling
Else
Return False
End If
End Function
This code is actually quite simple. The first thing I’m doing here is checking to make sure it was not the control’s “send” button that got me here. I’m also doing a check to make sure the event argument object sent in is of type CommandEventArgs. This is the event argument object that the CommandEventHandler delegate uses which defines the Command event of all buttons and links. More often than not, you’re probably going to be doing this check unless you start issuing RaiseBubbleEvent orders from your own events.
I’m casting the EventArgs object that came in to the type CommandEventArgs so I have strong-typing, then I’m raising an ItemCommand event that I declared in the EmailContact control.
Public Event ItemCommand As EventHandler(Of
CommandEventArgs)
Raising the ItemCommand event is the protocol defined by Microsoft for sending bubbled events up to the form. It’s important that control developers follow this protocol in order to provide consistency with the Microsoft controls-besides, it works!
The standard protocol for the ItemCommand event is to receive a CommandEventArgs object which contains a CommandName and CommandArgument property. These two properties come from the buttons and links and identify the button or link that the user clicked, giving a cleaner approach than using the sender argument of an event.
Finally, the method cancels any further event bubbling from taking place. By returning a True value, I’m telling the EmailContact control that the buck stops here. My control has now assumed all responsibility for the bubbled events and has taken appropriate action by raising its ItemCommand event.
What I’ve just explained is exactly what the ASP.NET grid controls do and why you must trap the ItemCommand event from the grid in order to receive clicks from buttons or links that template columns may contain. So now I can receive the EmailContact control’s ItemCommand event and identify a click from a contained link or button.
Protected Sub EmailContact1_ItemCommand( _
ByVal sender As Object, _
ByVal e As CommandEventArgs) _
Handles EmailContact1.ItemCommand
' use e.CommandName and/or e.CommandArgument
to determine what link or button was clicked
End Sub
Incidentally, the CommandName and CommandArgument properties of buttons and links are freeform text properties that you can use for anything that you want to identify the control. In the case of a repeatable control such as a grid, you could drag a couple of links into a template column and they will be repeated on every row. You can use the CommandName property to identity a clicked link then you can use the grid’s DataKeyField property to determine on which row.
So I hope I’ve taken some mystery out of how controls contained in grid templates send their events up to the form. The last thing I need to add to my control is some design-time functionality. In ASP.NET 1.1 (and 1.0), you right-clicked on a control with template functionality, such as the grid, and you’d see context menu commands that allow you to edit templates using the designer. Once in “template-editing” mode, you could drag controls into a template column from the toolbox. Unfortunately, you couldn’t easily build this design-time functionality into your own custom controls. With ASP.NET 2.0 that changes. You can add designer functionality for controls with templates, without which you would have to work in ASPX mode as the code examples I showed you earlier demonstrated.
Designer Editing
With the awesome designer that Visual Studio provides for ASP.NET development, it’s almost a shame when you have to transfer to ASPX view in order to accomplish something. This used to be the case with template editing unless you did a tremendous amount of work. This is no longer the case in ASP.NET 2.0 as Microsoft provided an infrastructure for adding designer editing to the control templates.
You do designer editing for control templates inside a control designer class. This partner class provides several overidable methods and properties to add design-time functionality to Web controls. Among these are Smart Tags, Auto-Formatting, and of course Template Editing. I cannot cover the intricacies and full power of control designer classes in this article but I will cover some of the basic essentials.
A control designer class inherits from the System.Web.UI.Design.ControlDesigner class. This base class provides many properties and methods to override in order to accomplish what you need. You’ll find the ControlDesigner class in the System.Design assembly which is not automatically referenced in a Web control project so you must go out and get it. The designer class is “wired” to a Web control using the Designer attribute on the class declaration.
Designer(GetType(DotNetDude.Web.UI.Design.
EmailContactDesigner))
In this example, the EmailContactDesigner class is my designer class which inherits from the framework’s ControlDesigner class.
In order to add template editing capability to a control you’ll override the Initialize method of the designer class and set a flag that informs the designer that we’re going to support template editing.
Public Overrides Sub Initialize(ByVal Component As
IComponent)
MyBase.Initialize(Component)
SetViewFlags(ViewFlags.TemplateEditing, True)
End Sub
Notice the call to the base method to make sure I didn’t cancel anything I wasn’t supposed to. Wouldn’t it be great if that was all there was to it? Well, unfortunately I’m not that lucky, however the code I’m going to write next allows easy categorization of templates so the page developer has a friendly interface with which to work.
The design Microsoft put in place lets you categorize templates into template groups, in the case that you have many templates in your control. The template itself is defined in an object called TemplateDefinition and it is the TemplateGroup object that contains one or more of these definition objects. The TemplateGroup objects are contained in an object of type TemplateGroupCollection, so it is here that you declare a variable of that type at the class level in your designer class.
Private o_TemplateGroups As
TemplateGroupCollection = Nothing
This collection gets built in a property called TemplateGroups which I now have to override. It is the contents of this property that will be exposed to the page developer for template editing.
Public Overrides ReadOnly Property
TemplateGroups() As TemplateGroupCollection
Get
If o_TemplateGroups Is Nothing Then
………
End If
Return o_TemplateGroups
End Get
End Property
In this property override, I’m going to build the TemplateGroupCollection that Visual Studio will use. The infrastructure will cache the TemplateGroupCollection object which is why it is declared at a class level. The “is nothing” check in the property prevents the unnecessary rebuilding of this object.
First I’ll initialize the o_TemplateGroups object by setting it to the TemplateGroups property of the base class. This not only instantiates it for me but following this technique, I establish a code architecture for a possible future inheritance chain in control designer classes.
o_TemplateGroups = MyBase.TemplateGroups
The next thing I need to do is identify the actual control you’re “designing.” In a control designer class that is wired to a Web control, the actual instance of the control being designed is obtainable in the Component variable that is defined in the base class. Many of the method and property overrides in a designer class make use of the actual control being designed, including the TemplateGroups property. So standard procedure is to declare an object variable and cast it to the Web control’s type.
Dim ctl As EmailContact = CType(Component,
EmailContact)
I’ll use this variable in a few minutes, but first I have to set up the two object variables I want to use for TemplateGroup and TemplateDefinition objects.
Dim o_TemplateGroup As TemplateGroup
Dim o_TemplateDefinition As TemplateDefinition
Now I can define groups and templates. Creating a new template group requires very complicated code so pay close attention here.
o_TemplateGroup = New TemplateGroup("Surrounding
Templates")
I know, I know, I’m a smart-aleck. The text “Surrounding Templates” will appear as the category heading for whatever template definitions I place in this group.
o_TemplateDefinition = New TemplateDefinition(Me,
"Header Template", ctl, "HeaderTemplate", False)
o_TemplateGroup. _
AddTemplateDefinition(o_TemplateDefinition)
Let’s go over the arguments in the TemplateDefinition constructor. The first is the instance of the designer incorporating template editing, usually just Me. The second argument is the name of template as it will be displayed in a context menu or a smart tag. Then you have the control you’re designing; remember you obtained this earlier by casting the Component object. The next argument is the name of the template property in the control. The Boolean argument that trails it off is set to False so this template will accept Server controls and HTML controls. Setting it to True will only allow the addition of server controls (regular Web controls) to the template.
As you can guess, you must repeat this for every template and every template group you want to define. The end result is my o_TemplateGroups object nicely filled with all my template definition information and that is what I return in this property. Listing 1 shows the full property code.
With the designer code done, let me show you the results. If I recompile my control and site, I can now right-click on it and I’ll see “Edit Template” added to the context menu. The listing in the submenu will display the template groups I defined in the designer class. And when I select the “Surrounding Templates” group, I’ll see something like Figure 3. You can see the controls that I manually added to the two templates and in fact I can now drag other controls directly from the toolbox. I can right-click again and select “End Template Editing” to return back to the standard control view and display the template contents in the control.
An “Edit Templates” link has also automatically been added to the control’s smart tag. If a smart tag did not exist previously, then one was automatically created for me when I added the template editing code to the designer class. Smart tags are another new 2.0 feature and I’ll write about them in a future article.
More Template Functionality
A control developer can insert templates anywhere in a custom control. I could have chosen to give this control some templates in between the form fields or just about anywhere I wanted. As you noticed, the template container control gets positioned throughout the composite control just like any other child control. You can also use control templates in repeater controls, or controls whose content is built dynamically within loop iterations. You can repeat templates in each iteration, just like you see in a grid. See the side bars for a good source of reference where you can learn more about this. Template functionality incorporated into controls that are built through loop iteration is also a good candidate for data binding. Once again, the grid is a good example of this.
Earlier in the article, I mentioned that I like to use a custom control as my template container even though something like a Panel would work just fine. The reasons for this are that I may want to put some properties in my container control. This can come in handy if I’m creating the template container control inside a loop in the hosting control for repeating purposes. I might set a property within the container control to an iteration number or something along those lines. Later, if I access the template container from the page using FindControl, I can access these properties. Even though I may not have this requirement immediately, it’s a good idea to set yourself up for the future.
The new data controls from Microsoft-GridView, DetailView, and FormView-all incorporate template functionality as you would expect them too; but they’re not the only ones now. ASP.NET’s Security control suite also has template functionality. These controls not only allow you to add other controls to their templates, but they also give you the ability to turn their pre-defined contents into templated contents. This let’s the page developer completely alter the way the control is going to render.
The SiteMapPath control (or bread-crumb control as it is also known) also has template functionality allowing you (but not limiting you) to change, for example, the separator that gets displayed between Web pages.
Necessary Workarounds
Unfortunately these controls are also examples of inconsistency even within Microsoft. The ItemCommand event I discussed earlier is not present in some of these controls so any links or buttons you add into their templates will not easily be trapped. I say “not easily” because it’s not impossible. If you encounter controls with template functionality but no event to expose for bubbling (ItemCommand is the usual standard), you can manually wire an event to the controls you insert into a template. I’ll demonstrate this using the EmailContact control, so for the time being, try to suspend disbelief and imagine the control written in a less-than-perfect state (no ItemCommand event).
Remember the link I dropped into the FooterTemplate? I’m going to put the EmailContact control into template-editing mode by right-clicking on it. I’ll select the Edit Templates menu and choose Surrounding Templates. Figure 3 shows the results. Now if I click the link I can view its properties in the property browser. In a perfect design, I would fill the CommandName and/or CommandArgument properties here so they are exposed through the control’s ItemCommand event; but I’m pretending that event does not exist. I can click the lightning bolt icon at the top of the property browser to display the link’s events. Now if I type something into the Command event (say ButtonCommand) and press Enter, the code-behind class will appear with a new event on it. While this approach will work, it’s not as clean as letting the parent control handle all events and event bubbling. It does break encapsulation by forcing you to handle events for a control not in the immediate child list of the form.
If you switch to ASPX view in Visual Studio, you would see the oncommand attribute on the LinkButton tag inside the FooterTemplate tags.
Conclusion
I’ve introduced you to one of the more powerful technologies in Web control development. How complex templates get all depends on how you use them in your controls but as you can see, they’re really quite simple to integrate just about anywhere. As I mentioned at the beginning of the article, you need to consider architecting for extensibility in most systems today and templates allow points of visual extensibility in your custom Web controls. With ASP.NET 2.0, editing templates in the designer is easier than ever making it even more attractive to add template functionality to your controls. While you do this, keep in mind the protocols put in place by Microsoft regarding event bubbling. Following them will give page developers a familiar interface to work with and provide complete encapsulation within your control. Have fun.