23

This question has been asked before

but it doesn't hurt to ask it again:

How do i add templating to a UserControl in ASP.net?

What hasn't worked so far

  1. Start with a new UserControl5, which i'll call Contoso:

    public partial class Contoso: System.Web.UI.UserControl
    {
    }
    

    This will allow us to use a new control:1

    <Contoso>
        Stuff in here
    <Contoso>
    
  2. Create a public ContentTemplate property of type ITemplate:

    public partial class Contoso: System.Web.UI.UserControl
    {
       public ITemplate ContentTemplate { get; set; }
    }
    

    and add an indeterminate number of attributes to the ContentTemplate property:2

    //[ParseChildren(true)]
    [ParseChildren(true, "ContentTemplate")]
    //[ParseChildren(false)]
    public partial class Contoso: System.Web.UI.UserControl
    {
       [TemplateContainer(typeof(ContentContainer))]
       [TemplateInstance(TemplateInstance.Single)]
       [PersistenceMode(PersistenceMode.InnerProperty)]   
       //[PersistenceMode(PersistenceMode.InnerDefaultProperty)] 
       [Browsable(true)]
       //[Browsable(false)]
       [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
       //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
       public ITemplate ContentTemplate { get; set; }
    }
    

    this will allow us to add <ContentTemplate> to the control in our aspx file:1

    <Contoso>
       <ContentTemplate>
           Stuff in here
       </ContentTemplate>
    </Contoso>
    
  3. Next we need to actually use the ContentTemplate stuff, by adding it somewhere. We do this by adding it to one of our UserControl's internal div elements.

    Starting from our .aspx file which was originally empty:

    <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Contoso.aspx.cs" Inherits="Contoso" %>
    

    we add a parent div that will hold our ContentTemplate stuff:

    <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Contoso.aspx.cs" Inherits="Contoso" %>
    <div id="ContentDiv" runat="server"></div>
    

    Then we stuff the ContentTemplate stuff into that parent div during the control's Init:

    public partial class Contoso: System.Web.UI.UserControl
    {
       protected override void OnInit(EventArgs e)
       {
          base.OnInit(e);
    
          //If there's content, then put it into our ContentDiv div
          if (this.ContentTemplate != null)
             this.ContentTemplate.InstantiateIn(ContentDiv);
       }
    
       [PersistenceModeAttribute(PersistenceMode.InnerProperty)]    
       [TemplateInstanceAttribute(TemplateInstance.Single)]
       [Browsable(true)]
       [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
       public ITemplate ContentTemplate { get; set; }
    }
    
  4. Edit: Indicate that your class implements INamingContainer:

    public partial class Contoso: System.Web.UI.UserControl: INamingContainer
    {
       protected override void OnInit(EventArgs e)
       {
          base.OnInit(e);
    
          //If there's content, then put it into our ContentDiv div
          if (this.ContentTemplate != null)
             this.ContentTemplate.InstantiateIn(ContentDiv);
       }
    
       [PersistenceModeAttribute(PersistenceMode.InnerProperty)]    
       [TemplateInstanceAttribute(TemplateInstance.Single)]
       [Browsable(true)]
       [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
       public ITemplate ContentTemplate { get; set; }
    }
    

    The INamingContainer interface does not have any members, and is only used to mark your UserControl class as something.

  5. And we're done3. We can now use this control in our aspx page. But first we need to "register" it at the top of our aspx page:

    <%@ Register src="Contoso.ascx" TagName="Contoso" tagprefix="uc" %>
    

    Where:

    • Contoso.ascx is the name of the ascx file
    • Contoso is the name of the element we will use to reference this user control
    • uc is a bit of text we will have to put in front of uc:Contoso (i use uc as short for user-control)
  6. Add the control to our page:

    <uc:Contoso ID="Crackers" runat="server">
        <ContentTemplate>
            Stuff goes here
        </ContentTemplate>
    </qwerty:Contoso>
    

And we're done!4

Edit: Forgot to add the reason the above doesn't work. Visual Studio shows the error:

Error Creating Control - Crackers

Type 'System.Web.UI.UserControl' does not have a public property named 'ContentTemplate'

enter image description here

Which makes sense, since UserControl does not have a public property named ContentTemplate - so i can hardly blame it.

Series

This question is one in the ongoing Stackoverflow series, "Templating user controls":

Bonus Reading

Footnotes

  • 1 You can't ever use that syntax. That's just an easy to read and understand form.
  • 2 Nobody knows what attributes to add, or why. Add more or less attribute to taste.
  • 3 Not done. Done with the UserControl, but not our work.
  • 4 Not done; it doesn't work.
  • 5 in the web-site (not a web application, not in a separate assembly)
Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Your footnotes would be easier to read if you insert them inline in your text, it's annoying to scroll down just to read the note – Jupaol Jul 28 '12 at 06:41
  • 2
    @Jupaol The footnotes don't add anything relevant to the question, and are only there to ward off nitpickers who want to nit-pick unimportant details. It would be a shame to litter the question in-line with such unimportant nonsense. – Ian Boyd Sep 07 '12 at 20:01

1 Answers1

15

Well I believe you almost got it.

BTW. The UserControl is not rendered using Visual Studio Designer, however when you run the application the control works. This is different if you use Server Controls instead, in that case, the control is displayed correctly in the Visual Studio designer

The following code works great to build templated user controls and templated server controls however, if you would like to add binding capabilities, the process is slightly different, take a look

Download Source Code

This is the code to create a templated UserControl.

Simple Output

enter image description here

Template Container

public class MyTemplateContainer : Control, INamingContainer { }

ASPX Code behind

protected void Page_Load(object sender, EventArgs e)
{
    // just to demonstrate using the contorl
    this.WebUserControl1.Controls.Add(new LiteralControl("<br />new control"));
}

ASPX

<%@ Register src="WebUserControl.ascx" tagname="WebUserControl" tagprefix="uc1" %>

    <uc1:WebUserControl ID="WebUserControl1" runat="server">
        <ContentTemplate>
            My Template<br />
            <asp:Label Text='Hello People' runat="server" ID="lblMessage" />
        </ContentTemplate>
    </uc1:WebUserControl>

ASCX Code behind

public partial class WebUserControl : System.Web.UI.UserControl
{
    [TemplateContainer(typeof(MyTemplateContainer))]
    [TemplateInstance(TemplateInstance.Single)]
    [PersistenceMode(PersistenceMode.InnerProperty)]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public ITemplate ContentTemplate { get; set; }

    protected void Page_Init(object sender, EventArgs e)
    {
        this.myPlaceHolderTag.Controls.Clear();

        if (this.ContentTemplate != null)
        {
            var container = new MyTemplateContainer();

            this.ContentTemplate.InstantiateIn(container);
            this.myPlaceHolderTag.Controls.Add(container);
        }
        else
        {
            this.myPlaceHolderTag.Controls.Add(new LiteralControl("No template defined"));
        }
    }
}

ASCX

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %>

<asp:PlaceHolder runat="server" ID="myPlaceHolderTag" />

Code to add a templated Server Control

Output

enter image description here

ASPX

<%@ Register Namespace="MyControls" TagPrefix="my" %>

<my:MyServerControl runat="server" ID="myServerControl">
    <ContentTemplate>
        My Server templated control<br />
        <asp:Label Text="My Label" runat="server" />
    </ContentTemplate>
</my:MyServerControl>

Template Container

namespace MyControls
{
    [ToolboxItem(false)]
    public class MyTemplateContainer : Control, INamingContainer { } 
}

Templated Server Control

namespace MyControls
{
    [ToolboxData("<{0}:MyServerControl runat=server >")]
    [ToolboxItem(true)]
    [ParseChildren(true)]
    // you can inherit from another control if you like, for example from the CompositeControl
    public class MyServerControl : Control, INamingContainer
    {
        [TemplateInstance(TemplateInstance.Multiple)]
        [TemplateContainer(typeof(MyTemplateContainer))]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [DefaultValue(null)]
        public ITemplate ContentTemplate { get; set; }

        protected override void CreateChildControls()
        {
            var p = new Panel { ID = "myPanel", BackColor = Color.Silver, Width = new Unit("100%") };

            if (this.ContentTemplate == null)
            {
                p.Controls.Add(new LiteralControl("No content has been specified"));
            }
            else
            {
                var c = new MyTemplateContainer();

                this.ContentTemplate.InstantiateIn(c);
                p.Controls.Add(c);
            }

            this.Controls.Clear();
            this.Controls.Add(p);
        }

        public override void DataBind()
        {
            this.CreateChildControls();
            this.ChildControlsCreated = true;
            base.DataBind();
        }

        public override ControlCollection Controls
        {
            get
            {
                this.EnsureChildControls();
                return base.Controls;
            }
        }
    }
}

References:

Jupaol
  • 21,107
  • 8
  • 68
  • 100
  • @IanBoyd - When I tested this it worked fine for me. Have you had the chance to test Jupaol's answer? – Peter Aug 02 '12 at 15:45
  • @peter i've actually not had a chance to test it; as i'm on vacation this week (and away from the offending Visual Studio 2010 ASP.net web-site that contains the buggy control). i will certainly be testing it soon; as no longer being able to view any pages at design-time is a *real* hinderance. – Ian Boyd Aug 03 '12 at 17:20
  • @peter Unfortunately i cannot say that this solves the problem of Visual Studio not being able to render a page that contains `ContentTemplate` until i solve the separate problem of Visual Studio not being able to render a page that contains `ContentTemplate`. The difference is that one is **my** own `UserControl`, the other is **Microsoft's** `UpdatePanel`. And while the solution here (stop using `UserControls`) might work, i can't update Microsoft Ajax Toolkit to tell **them** to stop using `UserControls`. So i'm stuck with pages i cannot edit. – Ian Boyd Sep 07 '12 at 15:49
  • See question [UserControl does not have public property named ContentTemplate](http://stackoverflow.com/questions/11656637/usercontrol-does-not-have-public-property-named-contenttemplate) – Ian Boyd Sep 07 '12 at 15:50
  • How do you access `IsPostBack` now that your control is a `Control` rather than a `UserControl`? – Ian Boyd Sep 07 '12 at 16:01
  • @IanBoyd - Page.IsPostBack should work, as for the UpdatePanel issue, I'm fairly confused. The word UpdatePanel does not appear anywhere in the original question or answer. Also the UpdatePanel is not a user control. – Peter Sep 07 '12 at 18:18
  • @Peter You're right. This question isn't related to an `UpdatePanel`. For that, see the linked question. The only relation is that Visual Studio gives the same error (`Type %s does not have a pubic property 'ContentTemplate'`) in both cases. (And the error with the `UpdatePanel` is *because* of my templated user control). (In reality i'm unable to use `Control`, but Visual Studio gives a *new* error: http://stackoverflow.com/questions/12323303/how-to-inherit-from-control-rather-than-usercontrol#comment16538811_12323303) – Ian Boyd Sep 07 '12 at 19:48
  • When you inherit your *"control"* from `Control`, what class are you inheriting it from? When i try to change my control to inherit from `Control` rather than `UserControl` Visual Studio gives an error, saying that i have to inherit from `UserControl` or `PageControl`. What `Control` are you inheriting from? i'm trying to use `System.Web.UI.Control` - which is not allowed. (i.e. what namespace is the `Control` that you're using in?) – Ian Boyd Sep 07 '12 at 20:25
  • i just realized it...your answer is acknowledging that `UserControl` are broken in the designer - that's it they're broken. i read it as *user controls are broken, unless you do this*. Six hours cursing and swearing on this. Monday i'll try *server* controls, and see if server-controls work in ASP.net *web-sites* (as opposed to web-applications), can be registered globally in `web.config`, and any number of other issues that i know will come up. – Ian Boyd Sep 07 '12 at 21:12
  • @Jupaol small question, how can I allow edit in visual studio designer like in panel? because I can't add content from the designer – Ahmed Magdy May 18 '13 at 13:06
  • 1
    It's a year later, and i wanted to see if the problem was ever solved. i was reading my original question, which contains so much WebForms nonsense: adding never-ending amounts of attributes, having to inherit from certain classes, having to call methods. It makes me wonder what i was thinking when i decided to use WebForms; i should have used MVC. – Ian Boyd Aug 19 '13 at 13:38
  • @Jupaol - thanks, that solves my problem immediately. I had seen some of the crud out there on this, but your one simple example works just fine for me. I'm not sure what all the other comments are about, but this works - brilliant, thanks. – philw Sep 18 '14 at 10:56