Templated Server Control Example
This example shows a control named VacationHome that demonstrates how to implement a templated server control. The VacationHome control defines two exposed properties, Title and Caption. The page designer sets the values of these properties at design time and the control uses those property values at run time to set properties for its child controls. By editing the <Template> element in the control, the page developer specifies the controls and markup that define the control's user interface. The control also enables page developers to use the <%# Container %> syntax, so that Title and Caption values can be referred to in the template markup at design time and displayed in the rendered output. A page designer could create an ASP.NET Web page that looks like this:
<aspSample:VacationHome ID="VacationHome1"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting from $200"
Runat="server" Width="230px" Height="129px">
<Template>
<table bgcolor="aqua" align="center" id="Table1"
runat="server" style="width: 286px; height: 260px">
<tr>
<td style="width: 404px" align="center">
<asp:Label ID="Label1" Runat="server"
Text="<%#Container.Title%>"
Font-Names="Arial, Helvetica"></asp:Label>
</td>
</tr>
<tr>
<td style="width: 404px">
<asp:Image ID="Image1" Runat="server"
ImageUrl="~/images/hawaii.jpg" />
</td>
</tr>
<tr>
<td style="width: 404px; height: 26px;" align="center">
<asp:Label ID="Label2" Runat="server"
Text="<%#Container.Caption%>"
Font-Names="Arial, Helvetica">
</asp:Label>
</td>
</tr>
</table>
</Template>
</aspSample:VacationHome>
Code Listing for the VacationHome Control
Option Strict On
Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.Design
Namespace Samples.AspNet.VB.Controls
< _
AspNetHostingPermission(SecurityAction.Demand, _
Level:=AspNetHostingPermissionLevel.Minimal), _
AspNetHostingPermission(SecurityAction.InheritanceDemand, _
Level:=AspNetHostingPermissionLevel.Minimal), _
Designer(GetType(VacationHomeDesigner)), _
DefaultProperty("Title"), _
ToolboxData( _
"<{0}:VacationHome runat=""server""> </{0}:VacationHome>") _
> _
Public Class VacationHome
Inherits CompositeControl
Private _template As ITemplate
Private _owner As TemplateOwner
< _
Bindable(True), _
Category("Data"), _
DefaultValue(""), _
Description("Caption") _
> _
Public Overridable Property Caption() As String
Get
Dim s As String = CStr(ViewState("Caption"))
If s Is Nothing Then s = String.Empty
Return s
End Get
Set(ByVal value As String)
ViewState("Caption") = value
End Set
End Property
< _
Browsable(False), _
DesignerSerializationVisibility( _
DesignerSerializationVisibility.Hidden) _
> _
Public ReadOnly Property Owner() As TemplateOwner
Get
Return _owner
End Get
End Property
< _
Browsable(False), _
PersistenceMode(PersistenceMode.InnerProperty), _
DefaultValue(GetType(ITemplate), ""), _
Description("Control template"), _
TemplateContainer(GetType(VacationHome)) _
> _
Public Overridable Property Template() As ITemplate
Get
Return _template
End Get
Set(ByVal value As ITemplate)
_template = value
End Set
End Property
< _
Bindable(True), _
Category("Data"), _
DefaultValue(""), _
Description("Title"), _
Localizable(True) _
> _
Public Property Title() As String
Get
Dim s As String = CStr(ViewState("Title"))
If s Is Nothing Then s = String.Empty
Return s
End Get
Set(ByVal value As String)
ViewState("Title") = value
End Set
End Property
Protected Overrides Sub CreateChildControls()
Controls.Clear()
_owner = New TemplateOwner()
Dim temp As ITemplate = _template
If temp Is Nothing Then
temp = New DefaultTemplate
End If
temp.InstantiateIn(_owner)
Me.Controls.Add(_owner)
End Sub
Public Overrides Sub DataBind()
CreateChildControls()
ChildControlsCreated = True
MyBase.DataBind()
End Sub
End Class
<ToolboxItem(False)> _
Public Class TemplateOwner
Inherits WebControl
End Class
#Region "DefaultTemplate"
NotInheritable Class DefaultTemplate
Implements ITemplate
Sub InstantiateIn(ByVal owner As Control) _
Implements ITemplate.InstantiateIn
Dim title As New Label
AddHandler title.DataBinding, AddressOf title_DataBinding
Dim linebreak As New LiteralControl("<br/>")
Dim caption As New Label
AddHandler caption.DataBinding, _
AddressOf caption_DataBinding
owner.Controls.Add(title)
owner.Controls.Add(linebreak)
owner.Controls.Add(caption)
End Sub
Sub caption_DataBinding(ByVal sender As Object, _
ByVal e As EventArgs)
Dim source As Label = CType(sender, Label)
Dim container As VacationHome = _
CType(source.NamingContainer, VacationHome)
source.Text = container.Caption
End Sub
Sub title_DataBinding(ByVal sender As Object, _
ByVal e As EventArgs)
Dim source As Label = CType(sender, Label)
Dim container As VacationHome = _
CType(source.NamingContainer, VacationHome)
source.Text = container.Caption
End Sub
End Class
#End Region
Public Class VacationHomeDesigner
Inherits ControlDesigner
Public Overrides Sub Initialize(ByVal Component As IComponent)
MyBase.Initialize(Component)
SetViewFlags(ViewFlags.TemplateEditing, True)
End Sub
Public Overloads Overrides Function GetDesignTimeHtml() As String
Return "<span>This is design-time HTML</span>"
End Function
Public Overrides ReadOnly Property TemplateGroups() As TemplateGroupCollection
Get
Dim collection As New TemplateGroupCollection
Dim group As TemplateGroup
Dim template As TemplateDefinition
Dim control As VacationHome
control = CType(Component, VacationHome)
group = New TemplateGroup("Item")
template = New TemplateDefinition(Me, "Template", control, "Template", True)
group.AddTemplateDefinition(template)
collection.Add(group)
Return collection
End Get
End Property
End Class
End Namespace
// VacationHome.cs
using System;
using System.ComponentModel;
using System.Drawing;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
namespace Samples.AspNet.CS.Controls
{
[
AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level=AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Minimal),
Designer(typeof(VacationHomeDesigner)),
DefaultProperty("Title"),
ToolboxData(
"<{0}:VacationHome runat=\"server\"> </{0}:VacationHome>"),
]
public class VacationHome : CompositeControl
{
private ITemplate templateValue;
private TemplateOwner ownerValue;
[
Bindable(true),
Category("Data"),
DefaultValue(""),
Description("Caption")
]
public virtual string Caption
{
get
{
string s = (string)ViewState["Caption"];
return (s == null) ? String.Empty : s;
}
set
{
ViewState["Caption"] = value;
}
}
[
Browsable(false),
DesignerSerializationVisibility(
DesignerSerializationVisibility.Hidden)
]
public TemplateOwner Owner
{
get
{
return ownerValue;
}
}
[
Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty),
DefaultValue(typeof(ITemplate), ""),
Description("Control template"),
TemplateContainer(typeof(VacationHome))
]
public virtual ITemplate Template
{
get
{
return templateValue;
}
set
{
templateValue = value;
}
}
[
Bindable(true),
Category("Data"),
DefaultValue(""),
Description("Title"),
Localizable(true)
]
public virtual string Title
{
get
{
string s = (string)ViewState["Title"];
return (s == null) ? String.Empty : s;
}
set
{
ViewState["Title"] = value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
ownerValue = new TemplateOwner();
ITemplate temp = templateValue;
if (temp == null)
{
temp = new DefaultTemplate();
}
temp.InstantiateIn(ownerValue);
this.Controls.Add(ownerValue);
}
public override void DataBind()
{
CreateChildControls();
ChildControlsCreated = true;
base.DataBind();
}
}
[
ToolboxItem(false)
]
public class TemplateOwner : WebControl
{
}
#region DefaultTemplate
sealed class DefaultTemplate : ITemplate
{
void ITemplate.InstantiateIn(Control owner)
{
Label title = new Label();
title.DataBinding += new EventHandler(title_DataBinding);
LiteralControl linebreak = new LiteralControl("<br/>");
Label caption = new Label();
caption.DataBinding
+= new EventHandler(caption_DataBinding);
owner.Controls.Add(title);
owner.Controls.Add(linebreak);
owner.Controls.Add(caption);
}
void caption_DataBinding(object sender, EventArgs e)
{
Label source = (Label)sender;
VacationHome container =
(VacationHome)(source.NamingContainer);
source.Text = container.Caption;
}
void title_DataBinding(object sender, EventArgs e)
{
Label source = (Label)sender;
VacationHome container =
(VacationHome)(source.NamingContainer);
source.Text = container.Title;
}
}
#endregion
public class VacationHomeDesigner : ControlDesigner
{
public override void Initialize(IComponent Component)
{
base.Initialize(Component);
SetViewFlags(ViewFlags.TemplateEditing, true);
}
public override string GetDesignTimeHtml()
{
return "<span>This is design-time HTML</span>";
}
public override TemplateGroupCollection TemplateGroups
{
get {
TemplateGroupCollection collection = new TemplateGroupCollection();
TemplateGroup group;
TemplateDefinition template;
VacationHome control;
control = (VacationHome)Component;
group = new TemplateGroup("Item");
template = new TemplateDefinition(this, "Template", control, "Template", true);
group.AddTemplateDefinition(template);
collection.Add(group);
return collection;
}
}
}
}
Code Discussion
A templated control extends the CompositeControl by adding a property of type ITemplate and defining the naming container for the control. By defining a naming container, you allow the page developer to use the <#%Container%> syntax in the template definition. The template control also defines a property of a type that derives from Control to host the controls defined in the template. Specific attributes and member overrides are implemented to coordinate the template property, host control, and naming container behavior.
The following list summarizes the main implementation requirements for a templated control as demonstrated by VacationHome. Details about each requirement are provided in the discussion following the list. The VacationHome control demonstrates:
Deriving from the CompositeControl base class. A templated control is a special kind of composite control. You can also derive from WebControl, but CompositeControl adds the implementation for INamingContainer, which enables use of the <#%Container%> syntax.
Implementing a property of type ITemplate and applying relevant metadata attributes to it to define its persistence and its naming container.
Exposing a property of type Control or a class derived from Control that serves to host the controls defined in the template element. This control is referred to as the template container.
Overriding the CreateChildControls method to instantiate the template controls in the Controls collection of the template container.
Optionally, defining a default template, which the control uses when the page developer does not specify a template.
Optionally, defining a designer class for the control. The designer class allows the page developer to edit the templates in a visual designer.
The attributes applied to the ITemplate property are BrowsableAttribute, PersistenceModeAttribute, and TemplateContainerAttribute. The TemplateContainerAttribute specifies the type of the control that the page parser should use when resolving the Container variable in an expression such as <#%Container.Title%> in a template. The type specified must implement INamingContainer and define the data properties (in this case Caption and Title) for the control. This type can be the type of the template owner or of a control further up the control tree. In the VacationHome control, the control whose type is passed into the TemplateContainerAttribute constructor is not the template owner, but the VacationHome control itself. The BrowsableAttribute is set to false, because templates are typically not edited in a visual designer's property editing window. The PersistenceModeAttribute is set to InnerProperty, because the template specification is written as an inner element of the control.
The templated control must define a property of type Control that becomes the container for the controls created by the template. In the example, the VacationHome control defines the Owner property, which is of type TemplateOwner, which in turn derives from WebControl. The TemplateOwner class is marked with ToolboxItem(false) to indicate that the TemplateOwner class does not need toolbox support in a visual designer. For more information, see ToolboxItemAttribute. Controls in the template are instantiated and added to the Controls property of the Owner control. If your control exposes multiple ITemplate properties, you might define a separate template container property for each template. The Owner property is exposed as a public property. This allows the page designer to use the FindControl method to reference specific controls in the template at run time.
The VacationHome control overrides the base CreateChildControls method. The CreateChildControls method instantiates the controls specified in the Template property and adds them to the Owner object's Controls collection. The Owner object is then added to the Controls collection of the VacationHome instance, and the control can then be rendered.
If the page developer has not defined a template, then the VacationHome creates an instance of DefaultTemplate, which derives from ITemplate. The InstantiateIn method creates two Label controls to display the Title and Caption properties. And event-handler method is created for the DataBinding event of each control. The DataBinding event-handler sets the Text property to the appropriate property (Title or Caption) of VacationHome.
The VacationHomeDesigner class that implements a designer for the VacationHome class derives from ControlDesigner. During initialization, the SetViewFlags method, called with TemplateEditing, enables template editing at design time. The GetDesignTimeHtml method is overridden to render the control when it is not in template editing mode. The code in the overridden TemplateGroups property defines one template group that contains one template. Each TemplateGroup object adds a template editing choice in the visual designer's template-editing user interface. (In Visual Studio 2005, template-editing choices are displayed in a smart tag associated with the control.) In the VacationHome control, the only editing choice is "Item". Each TemplateDefinition object creates a template for editing in the designer. The templatePropertyName parameter of the TemplateDefinition constructor specifies the name of the template property in the control. The DesignerAttribute is applied to the VacationHome class to specify the designer class.
Test Page for the VacationHome Control
The following example shows an .aspx page that uses the VacationHome control. The first instance of the control in the page specifies a template for the ITemplate property of the control. The second instance does not specify the ITemplate property, which causes the VacationHome control to use its default template at run time.
<%@ Page Language="VB"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
If Not IsPostBack Then
VacationHome1.DataBind()
VacationHome2.DataBind()
End If
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>
VacationHome Control Test Page
</title>
</head>
<body>
<form id="form1" runat="server">
<aspSample:VacationHome ID="VacationHome1"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" Width="230px" Height="129px">
<Template>
<table id="TABLE1" runat="server"
style="width: 286px; height: 260px;
background-color:Aqua; text-align:center">
<tr>
<td style="width: 404px" align="center">
<asp:Label ID="Label1" Runat="server"
Text="<%#Container.Title%>"
Font-Names="Arial, Helvetica"></asp:Label>
</td>
</tr>
<tr>
<td style="width: 404px">
<asp:Image ID="Image1" Runat="server"
ImageUrl="~/images/hawaii.jpg"
AlternateText="Hawaii home" />
</td>
</tr>
<tr>
<td style="width: 404px; height: 26px;" align="center">
<asp:Label ID="Label2" Runat="server"
Text="<%#Container.Caption%>"
Font-Names="Arial, Helvetica">
</asp:Label>
</td>
</tr>
</table>
</Template>
</aspSample:VacationHome>
<br /> <br />
<br />
The VacationHome control rendered with its default template:
<br /> <br />
<aspSample:VacationHome ID="VacationHome2"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" BorderStyle="Solid" BackColor="#66ffff"
Height="30px" Width="238px" Font-Names="Arial, Helvetica" />
</form>
</body>
</html>
<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
VacationHome1.DataBind();
VacationHome2.DataBind();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>
VacationHome Control Test Page
</title>
</head>
<body>
<form id="form1" runat="server">
<aspSample:VacationHome ID="VacationHome1"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" Width="230px" Height="129px">
<Template>
<table id="TABLE1" runat="server"
style="width: 286px; height: 260px;
background-color:Aqua; text-align:center">
<tr>
<td style="width: 404px" align="center">
<asp:Label ID="Label1" Runat="server"
Text="<%#Container.Title%>"
Font-Names="Arial, Helvetica"></asp:Label>
</td>
</tr>
<tr>
<td style="width: 404px">
<asp:Image ID="Image1" Runat="server"
ImageUrl="~/images/hawaii.jpg"
AlternateText="Hawaii home" />
</td>
</tr>
<tr>
<td style="width: 404px; height: 26px;" align="center">
<asp:Label ID="Label2" Runat="server"
Text="<%#Container.Caption%>"
Font-Names="Arial, Helvetica">
</asp:Label>
</td>
</tr>
</table>
</Template>
</aspSample:VacationHome>
<br /> <br />
<br />
The VacationHome control rendered with its default template:
<br /> <br />
<aspSample:VacationHome ID="VacationHome2"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" BorderStyle="Solid" BackColor="#66ffff"
Height="30px" Width="238px" Font-Names="Arial, Helvetica" />
</form>
</body>
</html>
Building and Using the Example
For information about building the control and using it in a page, see Building the Custom Server Control Examples. You must add a reference to the System.Design assembly for compilation.
See Also
Concepts
Typed Styles for Child Controls Example