Using ASP.Net User Controls
by Jon Wojtowicz

User Controls
Many articles have been written about creating and using user controls in ASP.Net. Most do a good job of describing how to create and statically add controls and some even cover dynamically adding the control. There seems to be a lack of information on some of the difficulties you can experience when using user controls. I'll provide basic coverage of creating and adding the control to a page statically, exposing properties and raising events. I'll also be looking at the issues of managing dynamic controls.


The Basic Control
I'm going to start with a menu type control that provides several hyperlinks to various pages in a web site. The basic control architecture is to allow data binding the list of menu items from the control page. I will add a DataList of LinkButtons for our menu. I will also keep the data binding simple by assuming we are binding to a DataSet or DataTable. The result of our controls can be seen in Figure 1.
Figure 1.
Since a user control is placed inside a form where I cannot directly control the layout, I should enforce the layout using tables or other containers and not rely on flow layout.
One of the first issues you can encounter is dependent upon how you handle the creation of the LinkButtons during data binding. If I create an event handler for the ItemDataBound event of the DataList I can certainly find the LinkButton control and set its text and CommanArgument properties. I can also attach an event handler for the Command event during this process. If I test this arrangement we notice that the Command event handler never gets entered. I have to change the event wire up to get the event to behave properly. I need to specify the name of the event handler on the ascx page inside the LinkButton element. After I make this slight correction, the event handler works as expected. If I declare all the information using data binding expressions with the declarative OnCommand event handler I do not have this issue. Listing 1 demonstrates the ItemDataBound event handler code and Listing 2 demonstrates the alternative DataList with the data binding expressions.
Listing 1.
 
private void MenuList_ItemDataBound(object sender, DataListItemEventArgs e)
{
	if( e.Item.ItemType == ListItemType.Item
		|| e.Item.ItemType == ListItemType.AlternatingItem)
	{
		DataRowView drv = (DataRowView)e.Item.DataItem;
		LinkButton lnk = (LinkButton)e.Item.FindControl("lnkNav");
		lnk.Text = drv[displayField].ToString();
		lnk.CommandArgument = drv[dataKeyField].ToString();
	}
}				
			
Listing 2.
 
<asp:DataList id="MenuList" runat="server" RepeatDirection="Horizontal" CellSpacing="10">
	<ItemTemplate>
		<asp:LinkButton ID="lnkNav" Runat="server" OnCommand="lnk_Command" 
		CommandArgument='<%# DataBinder.Eval(Container.DataItem, DataKeyField) %>'>
		<%# DataBinder.Eval(Container.DataItem, DisplayField) %></asp:LinkButton>
	</ItemTemplate>
</asp:DataList>				
			
Adding Events
Now that I have the event handlers in my menu control I need to propagate these to the parent. I must first decide how I want to send the value back to the parent. The two options I consider are setting a read only field on the menu control, or creating our own event arguments. I have decided on the latter method for this example.
I create our event arguments, called ItemSelectedEventArgs, by inheriting from EventArgs and adding a Key property to pass the key value back to the parent. The code for our event arguments can be seen in Listing 3.
Listing 3.
 
public class ItemSelectedEventArgs : EventArgs
{
	private string key;

	public string Key
	{ 
		get{ return key; }
	}

	public ItemSelectedEventArgs(string key)
	{
		this.key = key;
	}
}				
			
To complete our event, I need to create a delegate. A delegate is a type-safe method reference. This allows the parent to provide the method to call when the event is raised. I will call our delegate ItemSelectedDelegate and our event will be called ItemSelected. In following the standard approach for events, I create a method called OnItemCreated which determines if there are any subscribers to the event and calls the delegate if there are. I call this method from the event handler for our LinkButtons. Listing 4 shows the related code sections for the delegate and event.
Listing 4.
 
public delegate void ItemSelectedDelegate(object sender, ItemSelectedEventArgs e);
...
public class MenuControl : System.Web.UI.UserControl
{
	protected System.Web.UI.WebControls.DataList MenuList;
	protected string displayField;
	protected string dataKeyField;

	public event ItemSelectedDelegate ItemSelected;
...
	protected void lnk_Command(object sender, CommandEventArgs e)
	{
		ItemSelectedEventArgs selArgs = new ItemSelectedEventArgs(e.CommandArgument.ToString());
		OnItemSelected(selArgs);
	}

	protected void OnItemSelected(ItemSelectedEventArgs e)
	{
		if( ItemSelected != null )
			ItemSelected(this, e);
	}
...				
			
Now that I have the event, I can simply add an event handler form our main page. The method for retrieving user data from a control follows the same pattern, expose the data field as a property and allow the client to retrieve it. Figure 2 shows the result of the event on our form.
Figure 2.
Adding Controls Dynamically
But what if I don't know which controls I will need at design time? Do I code in every possible control and write code to show the control I want and hide the rest? Then what happens if I have to add yet another control? As you can see this situation could very easily get out of control. Fortunately ASP.Net allows us to dynamically load controls at run time by using the Page.LoadControl method.
The LoadControl method takes the path to our user control and returns a type of control. For this to be useful I need to cast the Control to our specific type. I can also extend this by using polymorphism through a base class or interface on our control. This would allow the most flexibility in extending our solution.
For our example I will keep the code simple. I'll be creating a series of controls that displays the item selected from the menu and allows the user to input some text. The first thing I need to do is create a control container for the dynamic control. I also need to determine which control to create based on the user selection in the menu. This is a simple switch statement but provides us with a factory for creating our user controls. I'll be using a div as a control container for our dynamic controls. Figure 3 displays the results of a selected item with the dynamic control. Each time I select a new menu item, I will dynamically create and add the control to our container.
Figure 3.
Now I want to perform an action when the user clicks a button on our main form. I simply add a button to the form and add an event handler and I should be all set. In this case I'll simply change the text of the button so the event can be seen. I first click on one of the menu items to dynamically load the control. This can be seen in Figure 4. I then click the button and my control is gone. This can be seen in Figure 5. This control disappearing act was caused by the control not being reloaded on the page. During the post back, the page has no idea about this new control. To overcome this I have to somehow maintain the state of the page. To do this, I'll add the key value that I get from the menu control into the view state when I create the control. I'll also have to add some code on the page load event to recreate the control on every request. The page load and the control creation code can be seen in Listing 5. Now the control will be added at every request.
Figure 4.
Figure 5.
Listing 4.
 
private void Page_Load(object sender, System.EventArgs e)
{
	if( ! IsPostBack )
	{
		MainMenu.DataSource = GetDataTable();
		MainMenu.DataKeyField = "ItemId";
		MainMenu.DisplayField = "Title";
		MainMenu.DataBind();
	}
	else
	{
		object key = ViewState["ControlKey"];
		if( key != null )
		{
			int keyValue = (int)key;
			AddControl(keyValue);
		}
	}
}
		
...

private void MainMenu_ItemSelected(object sender, ItemSelectedEventArgs e)
{
	this.lblSelectedItem.Text = "You have selected item " + e.Key;
	int keyValue = int.Parse(e.Key);
	AddControl(keyValue);
	ViewState["ControlKey"] = keyValue;
}

...

private void AddControl(int keyValue)
{
	CtrlContainer.Controls.Clear();
	Control ctrl = null;
	switch(keyValue)
	{
		case 1:
			ctrl = Page.LoadControl("BookControl.ascx");
			break;
		case 2:
			ctrl = Page.LoadControl("AuthorControl.ascx");
			break;
		case 3:
			ctrl = Page.LoadControl("ProductControl.ascx");
			break;
		case 4:
			ctrl = Page.LoadControl("CustomerControl.ascx");
			break;
	}
	CtrlContainer.Controls.Add(ctrl);
}		
			
Expanding the functionality of the control, I want to capture a button click each one of our controls. I'll add a button and event handler to each of the user controls. With this addition, the initial form after the click on the control can be seen in Figure 6. This performs as expected in our new form.
Figure 6.
The next step is to actually capture some input from the user. I'll add a drop down list to the form and have the selected text become the label of the button. The results of this scenario can be seen in Figure 8.
Figure 7.
Summary
While the examples I have presented are simple, they allow you to see how this could be extended into more complex controls. Hopefully you'll be able to avoid some of the common issues people have with using user controls.