ASP.NET Templated UserControls avoid Code Duplication

I wasn't completely aware that you can use Templates in an ASCX UserControl until another developer that I work with was trying to do it. Previous to that, I would have created a Custom Control. However, it can be done quite easily.

What that means is that one can have a standard Templated UserControl with the exact kind of styling desired in say, the Header and Footer Templates, and you can place whatever controls you want in the content template - buttons, GridViews, you name it.

This gives you the freedom to place several of these on a page and automatically get the consistent look and feel you want without having to duplicate code. There are a couple of gotchas, but for most developers they would be minor:

1) Design-Time support is out of the question. But, you can always lay out your "stuff" on the page, and once you like it, you just surround it with the UserControl and the appropriate templates.

2) In order to get a reference to the Controls you may have placed on one of your Templated UserControls, you have to use an approach like this:    

GridView gv = (GridView)this.TemplatedControl1.FindControl("GridView1");

-- not a big deal, really.

Here's a small screen capture that illustrates it in use (three of them on a page):



Now here is how we put together the control. The markup is very simple:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TemplatedControl.ascx.cs" Inherits="TemplatedUserControl.TemplatedControl" %>
<div class="contentBox">
<
asp:PlaceHolder ID="Title" runat="server"></asp:PlaceHolder>
<
asp:PlaceHolder ID="Description" runat="server"></asp:PlaceHolder>
<
asp:PlaceHolder ID="Footer" runat="server"></asp:PlaceHolder>
</
div>

That's it! Notice we have a standard PlaceHolder that represents each of the Templates we'll create.

Now let's look at the CodeBehind. First, the entire class, then a short discussion:

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

namespace TemplatedUserControl
{
    [ParseChildren(true)]
    public partial class TemplatedControl : System.Web.UI.UserControl
    {       
        private ITemplate _title;
        private ITemplate _description;
        private ITemplate _footer;        

        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplateControl))]
        public ITemplate TitleTemplate
        {
            get { return _title; }
            set { _title = value; }
        }

        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplateControl))]
        public ITemplate DescriptionTemplate
        {
            get { return _description; }
            set { _description = value; }
        }

        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplateControl))]
        public ITemplate FooterTemplate
        {
            get { return _footer; }
            set { _footer = value; }
        }
   
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);             
            if (_title != null)
            {
                _title.InstantiateIn(Title);
            }
            if (_description != null)
            {
                _description.InstantiateIn(Description);
            }
            if (_footer != null)
            {
                _footer.InstantiateIn(Footer);
            }
        }
    }
}

So basically, we just need an ITemplate property for each Template we want to have in our Control.  Each needs to have the following attributes:

[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplateControl))]

Then, we only need to override the OnInit event. We check to see if each of the templates is not null, and call it's InstantiateIn method, passing in the PlaceHolder id where it belongs. The only other thing we must do is decorate our Control class with the [ParseChildren(true)] attribute.  If a class is marked ParseChildren(ChildrenAsProperties=true) or just ParseChildren(true), child tags are mapped to properties of the class. When ChildrenAsProperties is true, a DefaultProperty may also be applied.

Now, when we want to use one of these, we can have markup like so:

<uc1:TemplatedControl ID="TemplatedControl2" runat="server" >     
    <TitleTemplate> 
   <div class="header">
        <div class="cornerTl"></div>
        <div class="cornerTr"></div>       
        <div class="title"><asp:Label ID="lblTitle" runat="server">Title of this other thingy.</asp:Label></div>
        <div class="clear"></div>
    </div>
 </TitleTemplate>
    <DescriptionTemplate>
<div class="content"><BR />
        This is some more description, and this can have controls in it too. For example:
  <asp:Button ID="Button2"
       runat="server" Text="Hit me"  OnClick="Button2_Click"/><br />
       <div align="center">
   <iframe id="ifr1" src=""  height="0px" class="content" runat="server" frameborder="0" width="400px;" style="visibility:hidden" >
   <div class="content">&nbsp;   </div>
   </iframe>
   </div>
   <br /> Click the button and we'll display something  in an IFRAME.
</div>
    </DescriptionTemplate>    
    <FooterTemplate>
      <div class="footer">
        <div class="cornerBl"></div>
        <div class="cornerBr"></div>
    </div> 
    </FooterTemplate>
    </uc1:TemplatedControl>
You can download the sample project that has the TemplatedControl with Header, Description (content) and Footer Templates, and the nice title bar with rounded corners look.
By Peter Bromberg   Popularity  (5064 Views)