The previous
post dealt with getting the user controls to be dynamic with both addition and removal methods for the controls. In this post we will look at doing code reduction and making the code reusable with a minimum amount of effort. We created only one type of user control in the previous set of examples, but it is easy to image a situation where you would be creating more than one type type of control and in this scenario it would be handy not to have to write a massive amount of code for each control but use generic methods.
So that is what we are going to do in this post, some good old fashioned code refactoring. First we will look at the interface that is being used by user control and how it can be made generic. The original code for the interface is as follows
public interface IPersonControl
{
Person Data { get; set; }
void DataBind();
}
We can change the interface to a generic type like so
public interface IUserControl<T>
{
T Data { get; set; }
void DataBind();
}
We will now change our existing PersonControl to use this new interface. The existing code is like this
public partial class PersonControl : UserControl, IPersonControl
And to implement the new interface we change it to the following
public partial class PersonControl : UserControl, IUserControl<Person>
It is a simple as that. Any new control we want to use should use the new interface and whichever entity it is being used to represent.
We will now take a look at the method that enumerates the repeater items for the items values. Before we had this code
List<Person> CurrentPersonList
{
get
{
var items = new List<Person>();
foreach(RepeaterItem item in rptPeople.Items)
{
var control = (PersonControl) item.FindControl("phPerson").Controls[0];
items.Add(control.Data);
}
return items;
}
}
If we take a look at the code, we can make it generic by supplying a reference the type of entity in the list we want to return and also the name of the placeholder control and the repeater we want to use as parameters to a method. So by applying that logic we end up with this code.
private static List<T> GetCurrentList<T>(Repeater repeater, string controlName)
{
var items = new List<T>();
foreach (RepeaterItem item in repeater.Items)
{
var control = (IUserControl<T>) item.FindControl(controlName).Controls[0];
items.Add(control.Data);
}
return items;
}
As you can see now we have a nice reusable method. But we can further change this code and use some LINQ to replace the foreach statement. It will now look like this
private List<T> GetCurrentList<T>(Repeater repeater, string controlName)
{
return (from RepeaterItem item in repeater.Items
select (IUserControl<T>) item.FindControl(controlName).Controls[0]
into control select control.Data).ToList();
}
To use the method we would replace the List<Person> CurrentPersonList as follows
private List<Person> CurrentPersonList
{
get { return GetCurrentList<Person>(rptPeople, "phPerson"); }
}
Now we will look at changing the methods that add items to the repeater. Again we will take a look at the original code that was used in the last post.
protected void AddPerson(object sender, EventArgs e)
{
var currentData = CurrentPersonList;
currentData.Add(new Person());
rptPeople.DataSource = currentData;
rptPeople.DataBind();
}
If we look at what we could do here, we could change the code so that we supply the entity type that we want to use along with the current list of items, and the repeater that want to bind to. There is one slight additional change we need to do and that is when you use a generic method to create a new instance of a type, you must specify in the method declaration, the
new constraint.
private static void AddItem<T>(ICollection<T> currentData, Repeater repeater) where T : new()
{
var data = new T();
currentData.Add(data);
repeater.DataSource = currentData;
repeater.DataBind();
}
We can now replace the contents of the AddPerson method the the following
protected void AddPerson(object sender, EventArgs e)
{
AddItem(CurrentPersonList,rptPeople);
}
We look at the ItemCommand method which can generic the same way we have refactored the AddItem methods. The original code looks like this
if (e.CommandName != "Remove") return;
var index = e.Item.ItemIndex;
var currentData = CurrentPersonList;
currentData.RemoveAt(index);
rptPeople.DataSource = currentData;
rptPeople.DataBind();
Again we will look at the code and see what is reusable. If we supply the RepeaterItemEventArgs, a generic list of items and the repeater to bind to. So with that we end up like this
private static void RemoveItem<T>(Repeater repeater, IList<T> data, RepeaterCommandEventArgs e)
{
if (e.CommandName != "Remove") return;
var index = e.Item.ItemIndex;
data.RemoveAt(index);
repeater.DataSource = data;
repeater.DataBind();
}
To use the method in the RemovePerson event, we change the contents of the repeater ItemCommand method to
private void RptPeopleItemCommand(object source, RepeaterCommandEventArgs e)
{
RemoveItem(rptPeople, CurrentPersonList, e);
}
Now the final piece of the puzzle is to change the ItemCreated event and the ItemLoad events. We can merge the ItemLoad into the ItemCreated event using a
lambda expression. So the two methods before we start look like this.
private void RptPeopleItemCreated(object sender, RepeaterItemEventArgs e)
{
var data = (Person) e.Item.DataItem;
if(!Equals(data,default(Person)))
{
var placeholder = (PlaceHolder) e.Item.FindControl("phPerson");
var control = (PersonControl) LoadControl("~/UserControls/PersonControl.ascx");
control.Data = data;
placeholder.Controls.Add(control);
}
else
{
e.Item.Load += PersonItemLoad;
}
}
private void PersonItemLoad(object sender, EventArgs e)
{
var literal = (RepeaterItem)sender;
var placeHolder = literal.FindControl("phPerson");
var control = (PersonControl)LoadControl("~/UserControls/PersonControl.ascx");
placeHolder.Controls.Add(control);
}
If you use the lambda operator => we can merge the the ItemLoad and ItemCreated events into one method.
private void RptPeopleItemCreated(object sender, RepeaterItemEventArgs e)
{
var data = (Person) e.Item.DataItem;
if(!Equals(data,default(Person)))
{
var placeholder = (PlaceHolder) e.Item.FindControl("phPerson");
var control = (PersonControl) LoadControl("~/UserControls/PersonControl.ascx");
control.Data = data;
placeholder.Controls.Add(control);
}
else
{
e.Item.Load += (send,ea) =>
{
var literal = (RepeaterItem)sender;
var placeHolder = literal.FindControl("phPerson");
var control = (PersonControl)LoadControl("~/UserControls/PersonControl.ascx");
placeHolder.Controls.Add(control);
};
}
}
Now that we have it merged we can refactor this method into a generic method. We can replace the entity parts and if we pass in the name of the placeholder control and the path of the control to load as well as the RepeaterItemEventArgs we can re-use the method.
private void AddControl<T>(string controlPath, string controlName, RepeaterItemEventArgs e)
{
var data = (T)e.Item.DataItem;
if (!Equals(data, default(T)))
{
var placeHolder = e.Item.FindControl(controlName);
var control = (IUserControl<T>)LoadControl(controlPath);
control.Data = data;
placeHolder.Controls.Add((Control)control);
}
else
{
e.Item.Load += (sender, ea) =>
{
var literal = (RepeaterItem)sender;
var placeHolder = literal.FindControl(controlName);
var control = (IUserControl<T>)LoadControl(controlPath);
placeHolder.Controls.Add((Control)control);
};
}
}
And there we have it, the code distilled down so that we can reuse certain methods if we have more than one repeater with different type of controls. So that is the end of the series of posts on dynamic controls. You can get the code bits from the
post in the series