How do I create an ASP.NET control that simulates switch/case?
-
12-09-2019 - |
Question
I've got a repeater looping through a list of objects that are of different types. I'd like to render the objects differently depending on their type. For this I need some kind of control (since I want to avoid using the code-behind) that has a behavior similar to a switch/case statement. Basically it could look like this:
<xxx:TestType Object='<%# Container.DataItem %>'>
<Case Type="Namespace.ClassX">
<asp:Label ... />
</Case>
<Case Type="Namespace.ClassY">
<asp:TextBox ... />
</Case>
<Default>
<p>Other</p>
</Default>
</xxx:TestType>
I've made web controls before, but this is a rather complex one...
- How do I make it support multiple
<Case>
elements? - Is it possible to implement the
<Case Type="...">
elements, or am I limited to attribute-less elements?
I'm guessing I have to make a type for the <Case>
elements and somehow specifying it for the main web control?
I'd be grateful for any pointers, links to tutorials, etc!
Alternative
Alternatively, suggest a nicer way of rendering different HTML/ASP.NET controls based on the type of the currently bound object. The first method that popped into my head was this, which I consider (very) ugly:
<asp:PlaceHolder Visible='<%# CheckType(Container, typeof(ClassX)) %>' runat="server">
...
</asp:PlaceHolder>
<asp:PlaceHolder Visible='<%# CheckType(Container, typeof(ClassY)) %>' runat="server">
...
</asp:PlaceHolder>
Solution
The MultiView control is the closest thing out-of-the-box in ASP.NET to a C# switch statement.
OTHER TIPS
Look at implementing ITemplate interface
public class Case
{
[PersistenceMode(PersistenceMode.Attribute)]
public string Type { get; set; }
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate Template { get; set; }
}
public class DeclarativeCase
: CompositeControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<Case> Cases { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Default { get; set; }
}
<xxx:DeclarativeCase runat="server" ID="test">
<Cases>
<xxx:Case Type="Namespace.TypeName">
<Template>
<asp:Label ID="Label1" runat="server"></asp:Label>
</Template>
</xxx:Case>
</Cases>
<Default>
<asp:Label ID="Label2" runat="server"></asp:Label>
</Default>
</xxx:DeclarativeCase>
Why don't you ditch the server control and use the C# switch statement. All you need is to wrap it in the code tags.
Another approach is a iteration (for loop). Create an interface with a type property and a render method. Then iterate over the interfaces until you've found the correct type.
I had a similar requirement a while ago and I did something similar to:
[ParseChildren(true, "Templates")]
public class TypedRepeater : CompositeDataBoundControl
{
[
PersistenceMode(PersistenceMode.InnerProperty),
Browsable(false),
MergableProperty(false)
]
public TypedTemplateCollection Templates
{
get;
}
protected TypedTemplate GetTemplate(object dataItem) { if (dataItem == null) { return null; } foreach (TypedTemplate template in Templates) { Type itemType = dataItem.GetType(); if (dataItem.IsAssignableFrom(template.Type)) { return template; } } return null; }
protected TypedTemplateRepeaterItem CreateItem(int index, object dataItem, bool dataBinding)
{
TypedTemplateRepeaterItem repeaterItem = new TypedTemplateRepeaterItem();
if ((!dataBinding) && (ViewState[string.Format("TemplateIxc_{0}", index)] is int))
{
int _template = (int)ViewState[string.Format("TemplateIxc_{0}", index)];
if ((_template >= 0) && (_template < Templates.Count) && (Templates[_template].ItemTemplate != null))
{
Templates[_template].ItemTemplate.InstantiateIn(repeaterItem);
}
else
{
DefaultTemplate.InstantiateIn(repeaterItem);
}
}
else if (dataBinding)
{
TypedTemplate template = GetTemplate(dataItem);
ITemplate itemTemplate = DefaultTemplate;
if (template != null)
{
itemTemplate = template.ItemTemplate;
ViewState[string.Format("TemplateIxc_{0}", index)] = Templates.IndexOf(template);
}
else
{
ViewState[string.Format("TemplateIxc_{0}", index)] = -1;
}
repeaterItem.DataItem = dataItem;
repeaterItem.DataItemIndex =
repeaterItem.DisplayIndex = index;
itemTemplate.InstantiateIn(repeaterItem);
repeaterItem.DataBind();
repeaterItem.DataItem = null;
}
return repeaterItem;
}
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int count = 0;
if (dataSource != null)
{
foreach (object dataItem in dataSource)
{
TypedTemplateRepeaterItem repeaterItem = CreateItem(count, dataItem, dataBinding);
Controls.Add(repeaterItem);
count++;
}
}
return count;
}
}
where TypedTemplateCollection is a StateManagedCollection of a TypedTemplate class:
[ParseChildren(true, "ItemTemplate")]
public class TypedTemplate
{
public Type Type
{
get { return Type.GetType(TypeName); }
}
[
PersistenceMode(PersistenceMode.Attribute),
Browsable(true),
DefaultValue("")
]
public string TypeName
{
get;
set;
}
[
PersistenceMode(PersistenceMode.InnerProperty),
Browsable(true),
DefaultValue(typeof(ITemplate), null),
TemplateContainer(typeof(TypedTemplateRepeaterItem))
]
public ITemplate ItemTemplate
{
get;
set;
}
}
and TypedTemplateRepeaterItem is:
public class TypedTemplateRepeaterItem : WebControl, INamingContainer, IDataItemContainer
{
#region IDataItemContainer Members
public object DataItem
{
get;
set;
}
public int DataItemIndex
{
get;
set;
}
public int DisplayIndex
{
get;
set;
}
#endregion
}
The MSDN site has a simple clear example of templating and it certainly seems like the way to go in your case.