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
.
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; }
}
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);
}
}
}
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.
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>
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?<%@ 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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With