Reputation: 45303
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.
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.
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.
Upvotes: 1
Views: 2102
Reputation: 6066
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>");
}
Upvotes: 0
Reputation: 45303
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.
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...
Template
instances, I wasn't allowed to use duplicate IDs in separate Content
sectionsAfter 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.
Upvotes: 1