Introduction
This article aims at understanding templated web user controls from a beginner's perspective. We will try to understand
why we might need to have a templated user control and what the benefits of having a templated user control are.
Background
Whenever there is a scenario when we need to use a group of controls with some common functionality in multiple places,
we resort ourselves to User Controls. User Controls combine a set of controls and
functionality for reuse in multiple
places of a site.
Now let's we think about a scenario where we might need some data to be represented on the UI. We need to represent
this data on multiple pages and the layout/appearance of this data could be different on different locations.
Can we use a user control in such a scenario? The answer to this question is - Yes, we can. What we need to do
to facilitate such a reuse of data with the possibility/flexibility of
controlling the layout is to have ourselves a
templated user control.
Templated user controls encapsulate data inside and provide the facility to have a custom layout for the data for each
instance of the templated user control. Sometimes it may also provide the default representation with the
possibility to customize the representation. Let us try to work out an example to understand and implement the same.
Using the code
Let us think about a hypothetical scenario where we need to keep a Person's First Name, Last Name, and Age. We need to show
this information in many places of our website but the layout might be different for each
occurrence.
Implementing the Templated User Control
The first thing we need to do is create a User Control. Once we have the User Control with us we have to put a
placeholder control on this user control. This placeholder control, as the name suggests, will be a placeholder for the
controls providing the default representation and also for the custom layout that the developers might want to use.
Now before jumping on how to implement this user control lets do the required things
first. We will be needing an object for
representing the data. In our case we will have to create a Person
class to hold the person's first name, last name and age.
Let us go ahead and create this class.
public class PersonItem
{
private string firstName;
private string lastName;
private int age;
public PersonItem(string fName, string lName, int aGe)
{
this.firstName = fName;
this.lastName = lName;
this.age = aGe;
}
public string FirstName
{
get
{
return firstName;
}
set
{
firstName = value;
}
}
public string LastName
{
get
{
return lastName;
}
set
{
lastName = value;
}
}
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
}
Now we have the object ready with us, next thing we need to do is to define a class that will be used as the container for
all the controls that user might add in his custom layout. The main purpose of having this container class is to provide the
FindControl
functionality with the custom layout in place. so we can call this class a naming Container for this
control. This can be done by inheriting this class from Control
class and implementing INamingContainer
interface.
Also, since the controls inside this container will use the data of Person
class. this class will also be needing a reference to the person class. Let us create this class now.
public class PersonItemContainer : Control, INamingContainer
{
private PersonItem person = null;
public PersonItemContainer(PersonItem item)
{
this.person = item;
}
public PersonItem Person
{
get
{
return person;
}
set
{
person = value;
}
}
}
Now looking at this class it is pretty evident that it contains a reference to the Person
object but how will this
class serve as the naming container for our user control that part is not clear. To make this class serve as the NamingContainer
for our object we need to hook this class with the user control we have just created. To do that we need to create a class
member of
type ITemplate
and a public property for this inside our user control's code behind.
private ITemplate personTemplate;
public ITemplate PersonTemplate
{
get
{
return personTemplate;
}
set
{
personTemplate = value;
}
}
Still, How is it related to the NamingContainer class is not specified. to specify that we need to decorate this property with
an attribute as [TemplateContainer(typeof(PersonItemContainer))]
i.e.:
private ITemplate personTemplate;
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(PersonItemContainer))]
public ITemplate PersonTemplate
{
get
{
return personTemplate;
}
set
{
personTemplate = value;
}
}
The other attribute PersistenceMode
is to specify that this user control will have this property an the nested element in
the ASPX markup. having done this, now we need to write the code to provide the default representation of the control and hooking the controls data with the user supplied template/layout.
protected void Page_Init(object sender, EventArgs e)
{
this.phdPerson.Controls.Clear();
if (Person == null)
{
phdPerson.Controls.Add(new LiteralControl("Please attach the control with a person object."));
}
else
{
if (PersonTemplate == null)
{
phdPerson.Controls.Add(new LiteralControl("First Name: "));
phdPerson.Controls.Add(new LiteralControl(Person.FirstName));
phdPerson.Controls.Add(new LiteralControl("<br/>"));
phdPerson.Controls.Add(new LiteralControl("Last Name:"));
phdPerson.Controls.Add(new LiteralControl(Person.LastName));
phdPerson.Controls.Add(new LiteralControl("<br/>"));
phdPerson.Controls.Add(new LiteralControl("Age: "));
phdPerson.Controls.Add(new LiteralControl(Person.Age.ToString()));
}
else
{
PersonItemContainer container = new PersonItemContainer(this.Person);
this.PersonTemplate.InstantiateIn(container);
phdPerson.Controls.Add(container);
}
}
}
Let us now look at the complete code-behind of the user control to understand the complete logic:
public partial class TemplateUCtrl : System.Web.UI.UserControl
{
private ITemplate personTemplate;
private PersonItem personItem = null;
protected void Page_Init(object sender, EventArgs e)
{
this.phdPerson.Controls.Clear();
if (Person == null)
{
phdPerson.Controls.Add(new LiteralControl("Please attach the control with a person object."));
}
else
{
if (PersonTemplate == null)
{
phdPerson.Controls.Add(new LiteralControl("First Name: "));
phdPerson.Controls.Add(new LiteralControl(Person.FirstName));
phdPerson.Controls.Add(new LiteralControl("<br/>"));
phdPerson.Controls.Add(new LiteralControl("Last Name: "));
phdPerson.Controls.Add(new LiteralControl(Person.LastName));
phdPerson.Controls.Add(new LiteralControl("<br/>"));
phdPerson.Controls.Add(new LiteralControl("Age: "));
phdPerson.Controls.Add(new LiteralControl(Person.Age.ToString()));
}
else
{
PersonItemContainer container = new PersonItemContainer(this.Person);
this.PersonTemplate.InstantiateIn(container);
phdPerson.Controls.Add(container);
}
}
}
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(PersonItemContainer))]
public ITemplate PersonTemplate
{
get
{
return personTemplate;
}
set
{
personTemplate = value;
}
}
public PersonItem Person
{
get
{
return personItem;
}
set
{
personItem = value;
}
}
}
Using the Templated user control
Let us see how we can use this user control now. let us drag this user control on a page and provide the Person
details
on the code behind as
protected void Page_PreInit(object sender, EventArgs e)
{
PersonItem item = new PersonItem("one", "two", 23);
TemplateUCtrl1.Person = item;
}
This will show the default representation of the user control on the page.
What if the user fails to provide the Person details. To check that let us drag another user control on the page and lets not assign it with a Person
object.
So we have used the default user control. Now if we want to customize the layout of the user control then we can do that
by providing the PersonTemplate
inside the user control. Lets do that for the second user control(and also supply the person object on code behind.
The resulting markup and code will look as follows
<body>
<form id="form1" runat="server">
<div>
<uc1:TemplateUCtrl ID="TemplateUCtrl1" runat="server" />
<br />
<br />
<br />
<uc1:TemplateUCtrl ID="TemplateUCtrl2" runat="server">
<PersonTemplate>
Welcome Mr. <strong>'<%# Container.Person.FirstName %>'</strong>
<table>
<tr>
<td>
Person's first Name:
</td>
<td>
<asp:TextBox ID="TextBox1" runat="server"
Text="<%# Container.Person.FirstName %>"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Person's Last Name:
</td>
<td>
<asp:TextBox ID="TextBox2" runat="server"
Text="<%# Container.Person.LastName %>"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Person's Age:
</td>
<td>
<asp:TextBox ID="TextBox3" runat="server"
Text="<%# Container.Person.Age %>"></asp:TextBox>
</td>
</tr>
</table>
Give our best wishes to Mrs. <strong>'<%# Container.Person.LastName %>'</strong>
</PersonTemplate>
</uc1:TemplateUCtrl>
</div>
</form>
</body>
Code behind of the page:
public partial class _Default : System.Web.UI.Page
{
protected void Page_PreInit(object sender, EventArgs e)
{
PersonItem item = new PersonItem("one", "two", 23);
TemplateUCtrl1.Person = item;
TemplateUCtrl2.Person = item;
}
protected void Page_Load(object sender, EventArgs e)
{
Page.DataBind();
}
}
Now when we run this application:
Before summing up, let us look at the page and controls life cycle.
- The
Page
's Pre_Init
gets called and it assigns the user control's Person
object. - The
Control
's Pre_Init
gets called for the first user control we added and it renders the default representation. - The
Control
's Pre_Init
gets called for the second user control we added and it takes into account
that the user has supplied some custom template and render the page accordingly. Page
's Page_Load
gets called which will do a Page.Bind()
which will tell the templated user control to bind the data with the custom layout.
Points of Interest
Templated user controls provide a very easy way to have a control that can be reused along with some common
functionality. And it doed that with the possibility for the user to define his own layout for the control. We will perhaps look into templated server controls in a separate article.
History
- 07 June 2012: Fixed some Code formatting issues.
- 01 June 2012: First version.