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"
    "https://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="https://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"
    "https://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="https://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

Composite Web Control Example

Typed Styles for Child Controls Example

Other Resources

Developing Custom ASP.NET Server Controls