9

I'm just getting started with Custom User Controls in C# and I'm wondering if there are any examples out there of how to write one which accepts nested tags?

For example, when you create an asp:repeater you can add a nested tag for itemtemplate.

4 Answers 4

17

I wrote a blog post about this some time ago. In brief, if you had a control with the following markup:

<Abc:CustomControlUno runat="server" ID="Control1">
    <Children>
        <Abc:Control1Child IntegerProperty="1" />
    </Children>
</Abc:CustomControlUno>

You'd need the code in the control to be along the lines of:

[ParseChildren(true)]
[PersistChildren(true)]
[ToolboxData("<{0}:CustomControlUno runat=server></{0}:CustomControlUno>")]
public class CustomControlUno : WebControl, INamingContainer
{
    private Control1ChildrenCollection _children;

    [PersistenceMode(PersistenceMode.InnerProperty)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public Control1ChildrenCollection Children
    {
        get
        {
            if (_children == null)
            {
                _children = new Control1ChildrenCollection();
            }
            return _children;
        }
    }
}

public class Control1ChildrenCollection : List<Control1Child>
{
}

public class Control1Child
{
    public int IntegerProperty { get; set; }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Don't think it's poor form at all if it answers the question. It's when people write blog posts ONLY post. That's when it's insane making. Have a click.
Wow, this has been standing here for over three years now, and all this time I've been slapped across the face with overly complicated and elaborate 'examples' that didn't really work at all. Thank you!
The problem with linked blog posts as an answer is when the site becomes dead. Which is the case as I'm typing. Thus I can't see the answer.
@tjmoore I agree -- try looking it up on The Wayback Machine
6

I followed Rob's blog post, and made a slightly different control. The control is a conditional one, really just like an if-clause:

<wc:PriceInfo runat="server" ID="PriceInfo">
    <IfDiscount>
        You don't have a discount.
    </IfDiscount>
    <IfNotDiscount>
        Lucky you, <b>you have a discount!</b>
    </IfNotDiscount>
</wc:PriceInfo>

In the code I then set the HasDiscount property of the control to a boolean, which decides which clause is rendered.

The big difference from Rob's solution, is that the clauses within the control really can hold arbitrary HTML/ASPX code.

And here is the code for the control:

using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebUtilities
{
    [ToolboxData("<{0}:PriceInfo runat=server></{0}:PriceInfo>")]
    public class PriceInfo : WebControl, INamingContainer
    {
        private readonly Control ifDiscountControl = new Control();
        private readonly Control ifNotDiscountControl = new Control();

        public bool HasDiscount { get; set; }

        [PersistenceMode(PersistenceMode.InnerProperty)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public Control IfDiscount
        {
            get { return ifDiscountControl; }
        }

        [PersistenceMode(PersistenceMode.InnerProperty)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public Control IfNotDiscount
        {
            get { return ifNotDiscountControl; }
        }

        public override void RenderControl(HtmlTextWriter writer)
        {
            if (HasDiscount)
                ifDiscountControl.RenderControl(writer);
            else
                ifNotDiscountControl.RenderControl(writer);
        }
    }
}

Comments

2

I ended up with something very similar to the answer by Rob (in wayback archive) @gudmundur-h, but I used ITemplate to get rid of that annoying "You can't place content between X tags" in the usage. I'm not entirely sure what is actually required or not, so it's all here just in case.

The partial/user control markup: mycontrol.ascx

Note the important bits: plcChild1 and plcChild2.

<!-- markup, controls, etc -->
<div class="shell">
    <!-- etc -->

    <!-- optional content with default, will map to `ChildContentOne` -->
    <asp:PlaceHolder ID="plcChild1" runat="server">
        Some default content in the first child.
        Will show this unless overwritten.
        Include HTML, controls, whatever.
    </asp:PlaceHolder>

    <!-- etc -->

    <!-- optional content, no default, will map to `ChildContentTwo` -->
    <asp:PlaceHolder ID="plcChild2" runat="server"></asp:PlaceHolder>

</div>

The partial/user control codebehind: mycontrol.ascx.cs

[ParseChildren(true), PersistChildren(true)]
[ToolboxData(false /* don't care about drag-n-drop */)]
public partial class MyControlWithNestedContent: System.Web.UI.UserControl, INamingContainer {
    // expose properties as attributes, etc

    /// <summary>
    /// "attach" template to child controls
    /// </summary>
    /// <param name="template">the exposed markup "property"</param>
    /// <param name="control">the actual rendered control</param>
    protected virtual void attachContent(ITemplate template, Control control) {
        if(null != template) template.InstantiateIn(control);
    }

    [PersistenceMode(PersistenceMode.InnerProperty),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public virtual ITemplate ChildContentOne { get; set; }

    [PersistenceMode(PersistenceMode.InnerProperty), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public virtual ITemplate ChildContentTwo { get; set; }

    protected override void CreateChildControls() {
        // clear stuff, other setup, etc
        // needed?
        base.CreateChildControls();

        this.EnsureChildControls(); // cuz...we want them?

        // using the templates, set up the appropriate child controls
        attachContent(this.ChildContentOne, this.plcChild1);
        attachContent(this.ChildContentTwo, this.plcChild2);
    }
}

Important bits (?):

  • ParseChildren -- so stuff shows up?
  • PersistChildren -- so dynamically created stuff doesn't get reset?
  • PersistenceMode(PersistenceMode.InnerProperty) -- so controls are parsed correctly
  • DesignerSerializationVisibility(DesignerSerializationVisibility.Content) -- ditto?

The control usage

<%@ Register Src="~/App_Controls/MyStuff/mycontrol.ascx" TagPrefix="me" TagName="MyNestedControl" %>

<me:MyNestedControl SomeProperty="foo" SomethingElse="bar" runat="server" ID="meWhatever">
    <%-- omit `ChildContentOne` to use default --%>
    <ChildContentTwo>Stuff at the bottom! (not empty anymore)</ChildContentTwo>
</me:MyNestedControl>

Comments

1

My guess is you're looking for something like this? http://msdn.microsoft.com/en-us/library/aa478964.aspx

Your tags were removed or are invisible, so can't really help you there.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.