1

I would like to create an custom server control, VersionedContentControl, which will allow me to specify different variations of the final markup.

Example usage:

<custom:VersionedContentControl runat="server" VersionToUse="2">
    <ContentVersions>
        <Content Version="1">
            <asp:HyperLink runat="server" ID="HomeLink" NavigateUrl="~/Default.aspx">Home</asp:HyperLink>
        </Content>
        <Content Version="2">
            <asp:LinkButton runat="server" ID="HomeLink" OnClick="GoHome">Home</asp:LinkButton>
        </Content>
        <Content Version="3">
            <custom:HomeLink runat="server" ID="HomeLink" />
        </Content>
    </ContentVersions>
</custom:VersionedContentControl>

Using the markup above, I would expect the only LinkButton control to be utilized on the page.

Long Story

I am having great difficulty trying to define this custom control. I haven't even been able to find a good example on the MSDN of using nested controls like this. Instead, I have had to resort to following these blog posts as examples:

Unfortunately, everything I have tried has failed miserably. Here is what I have currently:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;

namespace CustomControls
{
    [ParseChildren(true)]
    [PersistChildren(false)]
    public class VersionedContentControl : Control, INamingContainer
    {
        public string VersionToUse { get; set; }

        [PersistenceMode(PersistenceMode.InnerProperty)]
        public IList<Content> ContentVersions { get; set; }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            var controlToUse = ContentVersions.Single(x => x.Version == VersionToUse);
            Controls.Clear();
            controlToUse.InstantiateIn(this);
        }
    }

    public class Content : ITemplate
    {
        public string Version { get; set; }

        public void InstantiateIn(Control container)
        {
            // I don't know what this method should do
        }
    }

    public class ContentVersionsList : List<Content> {}
}

Even though I haven't implemented InstantiateIn, all 3 versions of my content appear on the page; it shows 3 links.

Also, I can't actually use the control unless I specify different ID property values for each nested control; I can't use "HomeLink" for all of them. I would like to be able to re-use the ID so that I can access the control from the code behind.

I realize that, normally, it is forbidden to specify duplicate ID values for multiple controls on a page. However, in the MSDN documentation for System.Web.UI.MobileControls.DeviceSpecific, the examples use duplicate ID values for nested controls. Infact, the example is very close to what I want to do; it differs content based on a mobile device compatibility filter.

<mobile:Form id="Form1" runat="server">
    <mobile:DeviceSpecific Runat="server">
        <Choice Filter="isHTML32">
            <HeaderTemplate>
                <mobile:Label ID="Label1" Runat="server">
                    Header Template - HTML32</mobile:Label>
                <mobile:Command Runat="server">
                    Submit</mobile:Command>
            </HeaderTemplate>
            <FooterTemplate>
                <mobile:Label ID="Label2" Runat="server">
                    Footer Template</mobile:Label>
            </FooterTemplate>
        </Choice>
        <Choice>
            <HeaderTemplate>
                <mobile:Label ID="Label1" Runat="server">
                    Header Template - Default</mobile:Label>
                <mobile:Command ID="Command1" Runat="server">
                    Submit</mobile:Command>
            </HeaderTemplate>
            <FooterTemplate>
                <mobile:Label ID="Label2" Runat="server">
                    Footer Template</mobile:Label>
            </FooterTemplate>
        </Choice>
    </mobile:DeviceSpecific>
</mobile:Form>

It would be nice to look at the source of those controls to see how they accomplish this but, unfortunately, it is not open-source.

My Question

How can I create a custom server control which contains a list of nested controls and only renders one of the nested controls based on a property? Ideally re-using IDs among separate nested controls.

1
  • The functionality I desire is actually (mostly) provided by the build-in control: MultiView. I didn't even know this control existed! Unfortunately, it does not allow re-use of IDs like ITemplate instances do. I am going to keep trying. Commented Jul 31, 2013 at 17:54

2 Answers 2

1

tl;dr

I ended up using the MultiView control. It doesn't allow duplicate IDs but it satisfies all my other requirements and it allows me avoid having to maintain any custom code.

What I tried

I was finally able to get something working with this code:

using System.Collections.Generic;
using System.Linq;
using System.Web.UI;

namespace CustomControls
{
    [ParseChildren(true)]
    [PersistChildren(false)]
    public class VersionedContent : Control
    {
        public string VersionToUse { get; set; }

        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(ContentContainer))]
        [TemplateInstance(TemplateInstance.Multiple)]
        public List<Content> ContentVersions { get; set; }

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

        public ContentContainer ContentContainer
        {
            get
            {
                EnsureChildControls();
                return _contentContainer;
            }
        } private ContentContainer _contentContainer;

        protected override void CreateChildControls()
        {
            var controlToUse = ContentVersions.Single(x => x.Version == VersionToUse);
            Controls.Clear();
            _contentContainer = new ContentContainer();
            controlToUse.InstantiateIn(_contentContainer);
            Controls.Add(_contentContainer);
        }
    }

    public class Content : Control, ITemplate
    {
        public string Version { get; set; }

        public void InstantiateIn(Control container)
        {
            container.Controls.Add(this);
        }
    }

    public class ContentContainer : Control { }
}

This allows me to use the control like so:

<custom:VersionedContent ID="VersionedContentControl" runat="server" VersionToUse="1">
    <ContentVersions>
        <custom:Content Version="1">
            <custom:MyControlV1 runat="server" />
        </custom:Content>
        <custom:Content Version="2">
            <custom:MyControlV2 runat="server" CustomProperty="Foo" />
        </custom:Content>
    </ContentVersions>
</custom:VersionedContent>

Unfortunately, this solution had several drawbacks...

  • Even though I was using Template instances, I wasn't allowed to use duplicate IDs in separate Content sections
  • ViewState was not being handled properly
    • PostBack doesn't work properly
    • Validation doesn't work at all

After looking at the decompiled source code of the MultiView control (which is similar), I realized I would have had to make the code a lot more complicated to make it work as desired. The only thing I would gain over just using the MultiView control was possibly being able to use duplicate IDs, if I could even get that working. I decided it would be best to just settle for using the build-in MultiView control.

Sign up to request clarification or add additional context in comments.

Comments

0
protected override void RenderContents(HtmlTextWriter output)
        {

            output.Write("<div><div class=\"UserSectionHead\">");

            Label l = new Label() { Text = Label };
            TextBox t = new TextBox() { Text = Text };
            l.AssociatedControlID = t.ID;
            l.RenderControl(output);

            output.Write("</div><div class=\"UserSectionBody\"><div class=\"UserControlGroup\"><nobr>");

            t.RenderControl(output);

            output.Write("</nobr></div></div><div style=\"width:100%\" class=\"UserDottedLine\"></div>");

        }

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.