I thought I would compose a short how to on programmatic web controls. Often I have looked at the core set of controls within ASP.NET and wonder what design is required in order to get the design time mark up similar to that of the various controls. The types of markup I am referring to includes:
- Containers
- Lists
- Templates
- Nested Controls of a certain type
- Data bound controls
- Only one example here, as I have wanted to do for ages and could not quite put my finger on how it was achieved. Turns out, really simple lol
The following imports are used in these examples:
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
Simple Container Control
The first custom control structure I want to make is a custom panel. Not inherited from a panel, although the same would be achieved but rather the simplest custom container.
Desired Mark up
<aebs:CustomPanel ID="Panel1" runat="server">
Hell World
</aebs:CustomPanel>
This is as simple as it gets. This is a web control which persists any children controls or elements which you put in side.
[ParseChildren(false)]
[PersistChildren(true)]
public class CustomPanel : WebControl
{
public CustomPanel()
{
//
// TODO: Add constructor logic here
//
}
}
A Control with an object list
This control is a web control but it has a list of an object, so you can add numerous types of the object to its collection. The collection could be of a web control and if so it would be wise to inherit from a composite control as opposed to a web control.
Desired mark-up
<aebs:ListControl1 ID="ListControl1" runat="server">
<CustomObjects>
<aebs:CustomObject CustomProperty="Hello World" />
<aebs:CustomObject CustomProperty="Hello World 2" />
</CustomObjects>
</aebs:ListControl1>
So in design time you will see the intellisense prompt you for a tag called CustomObjects followed by nested prompts of a tag called Custom Object.
[ParseChildren(true)]
[PersistChildren(false)]
public class ListControl1 : WebControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<CustomObject> CustomObjects { get; set; }
public ListControl1()
{
//
// TODO: Add constructor logic here
//
}
}
The Custom object is nothing more than a class with a property. see here
public class CustomObject
{
public string CustomProperty { get; set; }
public CustomObject()
{
//
// TODO: Add constructor logic here
//
}
}
A Template Control
The mark up achieved with this type of control is like the type you see with for example the form view control which allows for:
- Item Template
- Insert Item Template
- Edit Item Template
All I am doing is showing the mark-up required for the design time mark-up, so when using you will need to use the ITemplate InstantiateIn method providing a web control or html control as the container.
Desired mark-up
<aebs:TemplateControl ID="TemplateControl1" runat="server">
<HeaderTemplate>
Hello Header World
</HeaderTemplate>
<ContentTemplate>
Hello Content World
</ContentTemplate>
<FooterTemplate>
Hello Footer World
</FooterTemplate>
</aebs:TemplateControl>
The code to achieve this is as follows.
[ParseChildren(true)]
[PersistChildren(false)]
public class TemplateControl : WebControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate HeaderTemplate { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ContentTemplate { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate FooterTemplate { get; set; }
public TemplateControl()
{
//
// TODO: Add constructor logic here
//
}
}
You are going to want to control how each template is rendered but for the purposes of this example I am only showing the bare bones of how to achieve the mark-up.
A container control with specific object types as options
A good example of this type of control is when you use the object or sql data source control. It allows you to specify parameters for the select, insert, update etc… The options though which are provided to you are restricted so you can only choose parameter objects. The key here is to specify a list property of the control with the type being a class other inherit from, not necessarily abstract.
Desired mark-up
<aebs:ConstrainedCollection ID="Constrained1" runat="server">
<AbstractProperties>
<aebs:ConcreteOne />
<aebs:ConcreteTwo />
</AbstractProperties>
</aebs:ConstrainedCollection>
The code to achieve this mark-up is as follows:
[ParseChildren(true)]
[PersistChildren(false)]
public class ConstrainedCollection : WebControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<AbstractClass1> AbstractProperties { get; set; }
public ConstrainedCollection()
{
//
// TODO: Add constructor logic here
//
}
}
So you can see that the only difference between this and the list control example above is that I use a type for the list which is inherited by two other types being ConcreteOne and also ConcreteTwo.
A DataPanel
I have wanted to do this for a while but could not quite put my finger on how it was achieved. Like I said up top, this turns out to be really simple. The same could be achieved with an ObjectDataSource and a FormView but i want a light weight container which I could throw an object at and use Eval inside it. I have many many uses for such a light weight singular object display. Plus I also wondered how the use of Eval was achieved in Custom Controls, turns out that it is through the use the System.Web.UI.IDataItemContainer Interface.
Desired Mark-up
<aebs:DataPanel ID="datapanel1" runat="server">
<%# Eval("ProjectName") %>
</aebs:DataPanel>
So I am not doing anything more than requesting a property of the object which I throw at the control. Throwing the object at the control I do inside the Page_Load event just for demo.
public void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DataPanel.Project p = new DataPanel.Project();
p.ProjectName = "Project 101";
datapanel1.BoundObject = p;
datapanel1.DataBind();
}
}
So the code to achieve this is just the simple container control above but this time I implement the interface. For the purposes of demonstration I have also banged the class inside this type as nested too.
[ParseChildren(false)]
[PersistChildren(true)]
public class DataPanel : WebControl, IDataItemContainer
{
public class Project
{
public string ProjectName { get; set; }
}
private object boundObject;
public DataPanel()
{
//
// TODO: Add constructor logic here
//
}
public object BoundObject
{
set
{
boundObject = value;
}
}
#region IDataItemContainer Members
public object DataItem
{
get { return boundObject; }
}
public int DataItemIndex
{
get { return 0; }
}
public int DisplayIndex
{
get { return 0; }
}
#endregion
}
Well I hope this is of some use to others, I will try and update this post soon with examples of custom DataSource controls and also custom DataBound Controls. When you start get into List Controls from a data source it gets real fun.
Cheers
Andrew