An Extensive Examination of User Controls

 

Scott Mitchell

March 2004

Applies to:
   Microsoft® ASP.NET

Summary: Provides a discussion of how user controls are implemented and rendered by ASP.NET, and then examines answers to the most asked user control questions. (28 printed pages)

Download the source code for this article.

Contents

Introduction
Understanding User Controls Basics
Deploying a User Control
How User Controls Are Processed
Responding to Events Raised by a User Control's Web Controls
Adding Properties and Methods to a User Control
Handling Postbacks
User Control Events
Creating and Raising a Custom Event
Dynamically Adding User Controls
Conclusion
Related Books

Introduction

When working on Web applications, developers often need to repeat certain HTML markup and source code in many Web pages. The worst thing you can do is repeat this HTML or code in each page where it's needed. Such repetition is detrimental for a number of reasons:

  • First, and most obviously, repeating code takes up time that could be better used elsewhere.
  • Second, every time you add, remove, or edit code or HTML markup, you introduce the chance of a bug or software defect. Yes, the probability may be low if you are cutting and pasting code from one page to another, but the threat still exists (you might, for example, paste the code in an incorrect place).
  • Third, making changes to the HTML or source code becomes a major headache when the HTML and code is spread around in many files. Finding all the changes that need to be made not only takes time, but is error-prone, as it's easy to overlook one or more places where the code had been duplicated.

In classic ASP, the predecessor to Microsoft® ASP.NET, developers reused HTML and source code by placing this often used markup and script in an include file, and then used server-side includes to automatically insert the contents of the include file into those Web pages that needed the common HTML or script. It was not uncommon for ASP developers to have several include files. Typically they had one for the "header" HTML—which contained the HTML markup to appear above the Web page's main content—one for the "footer" HTML—which contained the HTML markup to appear beneath the Web page's main content—and one that would include common source code functions. (For more on server-side includes in classic ASP, be sure to read: The Low-Down on #includes.)

Reuse of user interface elements—such as HTML markup, Web controls, or a mix of the two—is accomplished in ASP.NET with user controls. user controls are created not unlike ASP.NET Web pages. Like ASP.NET Web pages, user controls contain an HTML portion, which can include a mix of HTML markup and Web controls, and a source-code portion. Once a user control has been created, it can be used in any number of ASP.NET Web pages in the Web application. Due to the ease in creating user controls, and the benefits reuse provides, many developers turn to user controls for rendering the headers, footers, navigation links, and other common user interface elements of a page.

Note   User controls offer numerous advantages of server-side includes. Server-side includes are used to insert the exact content from another file. User controls, on the other hand, are implemented as classes, much like the ASP.NET Web controls. The page developer can programmatically interact with a user control, something that's not possible with server-side includes.

While simple user controls are very easy to create and deploy, developers usually run into problems when attempting some of the more advanced techniques with user controls. In this article, we'll look at how ASP.NET implements and renders user controls, and we will examine how to accomplish the following advanced user control techniques:

  • Responding to events raised by the Web controls in a user control.
  • Creating properties and methods in the user control that can be accessed by the ASP.NET Web page the user control resides within.
  • Giving user controls events that can be raised by user action and then handled by the page that contains the user control.
  • Programmatically loading a user control into an ASP.NET page.

Understanding User Controls Basics

user controls are created as .ascx files, and include an HTML portion and a source-code portion. Using Microsoft® Visual Studio® .NET, the source-code portion of a user control is contained within a separate file as a code-behind class, just like the source-code portion for an ASP.NET Web page exists in a separate code-behind class. (If you are not using Visual Studio .NET, a user control's source-code portion can appear in a server-side <script> block.)

To better understand how user controls are implemented and rendered, let's create a simple user control example. Imagine you work for a company that sells food items, and that you have been tasked with creating a Web site for customers to learn more about the products available. The product information is stored in a database with two germane tables: Categories, which contains a record for each of the product categories; and Products, which contains a record for each product for sale. Each product falls under precisely one category; this mapping is maintained with a foreign key CategoryID in the Products table. (The actual data we will be using is from the Northwinds database.)

In designing the Web site you decide that there might be several pages from which you'd like to show the products belonging to the Beverages category. Therefore, it would make sense to build a user control that utilizes a DataGrid to display the Beverage products. Using Visual Studio .NET, a user control can be created by adding a new item to an ASP.NET Web Application of type Web user control. Doing so will create two files: a .ascx file that contains the HTML markup for the user control, and a code-behind class file that contains the user control's source code.

Take a moment to create a new user control named DisplayBeverages.ascx. At this point, you can create the user control much like you would a standard ASP.NET Web page. For this user control, drag and drop a DataGrid into the .ascx file's Designer. In the user control's code-behind class, add the following code to bind the Beverages products to the DataGrid:

private void Page_Load(object sender, System.EventArgs e)
{
   if (!Page.IsPostBack)
      BindData();
}

private void BindData()
{
   OleDbConnection myConnection = new 
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
Server.MapPath("Northwind.mdb"));

   const string productSQL = "SELECT ProductName, UnitPrice FROM 
Products WHERE CategoryID = 1";

   OleDbCommand myCommand = new OleDbCommand(productSQL, myConnection);

   myConnection.Open();
   dgProducts.DataSource = myCommand.ExecuteReader();
   dgProducts.DataBind();
   myConnection.Close();         
}

The Page_Load event handler, which will run each time the user control is loaded, calls the BindData() method only on the first page load, and not on subsequent postbacks. BindData() creates a connection to the Northwinds Access database, issues a SQL statement that gets all of the products whose CategoryID equals 1 (the CategoryID for Beverages), and then binds the results to the DataGrid added to the user control (dgProducts).

Note   A complete working example of this user control—and of all the user controls we'll be examining throughout this article—is available in the article's code download.

Deploying a User Control

Once you have created a user control, the next step is deploying the user control in an ASP.NET Web page. This involves two steps: adding a @Register directive to the ASP.NET Web page and then using the declarative Web control syntax to insert the user control within the HTML content of the Web page.

Note Unfortunately, Visual Studio .NET does not provide any assistance with this process. User controls cannot be added to the Toolbox, and therefore cannot be dragged and dropped onto a Web page. You have to manually add the @Register directive and manually insert the syntax for the user control. Too, in the Designer Visual Studio .NET shows just a grayed out box with the user control's name rather than the actual rendered content of the user control.

Update   An alert student in one of my ASP.NET classes, along with a number of readers of this article, have noted that there is a simpler way to add a user control to an ASP.NET Web page using Visual Studio .NET. After creating the user control, drag it from the Solution Explorer onto the Design view of the ASP.NET page. This will automatically add the <%@ Register %> directive and the user control's declarative syntax in the HTML portion of the ASP.NET Web page.

However, realize that to programmatically reference the user control in the page's code-behind class, you'll still have to manually add a member variable to the code-behind class with the same name as the user control. (See the "Adding Properties and Methods to a User Control" section for more information.)

The @Register directive needs to appear at the top of the ASP.NET Web page's HTML portion and has the following syntax:

<%@ Register TagPrefix="prefix" TagName="name" 
   Src="path to .ascx file" %>

The TagPrefix and TagName values specify how the user control will be referenced declaratively in the Web page's HTML portion. The Src must contain the path to the user control's .ascx file. If the user control resides in the same directory as the ASP.NET Web page, Src can contain just the filename of the .ascx file. The Src value can also contain ~, which resolves down to the Web application's base directory. This tilde syntax is useful because it allows you to place all user controls in a /Controls directory. Then, ASP.NET Web pages, regardless of where in the directory structure they reside, can specify the Src as ~/Controls/UserControlFileName.ascx.

The following shows the @Register directive used in the ASP.NET Web page Beverages.aspx. From the Src attribute you can ascertain that the DisplayBeverages.ascx user control was placed in the root directory of the Web application.

<%@ Register TagPrefix="skm" TagName="ShowBeverages" 
Src="~/DisplayBeverages.ascx" %>

Next, to add the user control to the Web page, simply add the following declarative syntax in the Web page's HTML portion:

<skm:ShowBeverages runat="server"></skm:ShowBeverages>

Notice that the prefix (skm) and tag name (ShowBeverages) match the TagPrefix and TagName attribute values in the @Register directive. Also note the runat="server"—this attribute is required just as it is with other Web controls.

With this addition, we're ready to build our Web application and test out our user control by visiting Beverages.aspx. Figure 1 shows a screenshot of Beverages.aspx when viewed through a browser with the output of the user control circled.

ms972975.usercontrols_fig01(en-us,MSDN.10).gif

Figure 1. Beverages user control

The benefit of user controls is that they provide a means of user interface reuse. That is, if other Web pages need to list the beverage products available, we would merely need to add a @Register directive to those pages, and place the appropriate declarative syntax in the page's HTML portion. Had we opted not to use a user control, and instead just created the code to display the beverages in Beverages.aspx, to replicate that functionality in other pages we would have to cut and paste both the DataGrid and associated source code from Beverages.aspx to the other pages that need to provide a listing of beverage products.

How User Controls Are Processed

The HTML portion of ASP.NET Web pages can contain a mix of Web controls, user controls, and HTML syntax. When an ASP.NET Web page is visited for the first time, or for the first time after a change has been made to the HTML portion of the Web page, a class is automatically created from the HTML portion. This class, which is derived from the Web page's code-behind class, constructs the Web page's control hierarchy based on the declarative syntax in the HTML portion.

For example, imagine that a Web page had the following markup in its HTML portion:

<html>
<body>
  <form runat="server">
    Name: <asp:TextBox runat="server" id="name"></asp:TextBox>
    <br />
    <asp:Button runat="server" Text="Submit"></asp:Button>
  </form>
</body>
</html>

This would generate the following control hierarchy:

ms972975.usercontrols_fig02(en-us,MSDN.10).gif

Figure 2. Controls hierarchy on page

Notice that the HTML markup is converted into LiteralControl instances, the Web Form into an HtmlForm object, and the TextBox and Button Web controls into their respective classes.

Once this control hierarchy is created, the ASP.NET Web page can be rendered by recursively invoking the RenderControl(HtmlTextWriter) method of each of the controls in the hierarchy. When a control's RenderControl(HtmlTextWriter) method is invoked, the control generates its HTML markup and appends it to the HtmlTextWriter object. Realize that each time an ASP.NET Web page is requested, the page's corresponding class is invoked causing the control hierarchy to be created and rendered.

User controls work in a similar fashion to ASP.NET Web pages. When a user control is visited for the first time, or for the first time after the HTML portion has been changed, the user control is compiled into a class that is derived from the user control's code-behind class. Like the ASP.NET Web page's autogenerated class, the user control's autogenerated class constructs a control hierarchy based on the HTML portion of the user control. In essence, a user control has its own control hierarchy.

Imagine, then, that we had a user control with the following HTML markup:

<asp:Label runat="server" id="lblCategoryName"></asp:Label>
<br />
<asp:Label runat="server" id="Description"></asp:Label>

The user control's control hierarchy, then, would look like the following:

ms972975.usercontrols_fig03(en-us,MSDN.10).gif

Figure 3. Control Hierarchy for user control

Recall that an ASP.NET Web page's HTML portion can contain not only Web controls and HTML syntax, but user controls as well. user controls are entered into the control hierarchy as an instance of the user control's autogenerated class. So, imagine we augmented our previous ASP.NET Web page example so that after the Button Web control was an instance of the user control whose declarative syntax we just discussed. This would cause the Web page's control hierarchy to look like the following:

Click here for larger image.

Figure 4. Updated control hierarchy for page

After the user control class is added to the control hierarchy, the class's InitializeAsUserControl(Page) method is invoked. The InitializeAsUserControl(Page) method is defined in the System.Web.UI.UserControl class, which the autogenerated user control class is derived from indirectly. (Recall that the autogenerated user control class is derived from the user control's code-behind class; this code-behind class is derived from System.Web.UI.UserControl, similar to how an ASP.NET Web page's code-behind class is derived from System.Web.UI.Page.) The InitializeAsUserControl(Page) method generates the user control's class hierarchy and assigns the user control's Page property to the passed in Page instance, thereby allowing the user control to programmatically reference the Page to which it belongs. After InitializeAsUserControl(Page) runs, the Web page's control hierarchy looks like:

Click here for larger image.

Figure 5. Updated page hierarchy after initializing the user control

Notice that once the user control is affixed to the control hierarchy and builds up its own control hierarchy, the page's control hierarchy appears just as it would if a user control hadn't been used, but instead had its HTML portion entered directly into the page's HTML portion. With this complete hierarchy, the Web page can be rendered by recursively calling the RenderControl(HtmlTextWriter) method of each control in the hierarchy.

Now that we've seen how a user control is handled underneath the covers, let's take a look at some of the more advanced concepts of user controls, starting with how to respond to events raised by Web controls in a user control.

Responding to Events Raised by a User Control's Web Controls

As we've seen, a user control can contain a mix of HTML syntax and Web controls within its HTML portion. For example, earlier we created the DisplayBeverages.ascx user control, which displayed the beverage products in a DataGrid. Oftentimes developers creating user controls need to be able to programmatically respond to server-side events raised by the Web controls within the user control. Imagine that we configured this DataGrid so that the columns could be sorted. This would cause the DataGrid's columns' headers to be rendered as LinkButtons. When one of these links were clicked, the Web page would be posted back and the DataGrid's SortCommand would be raised. The question, then, is how does one respond to this Web control event when it occurs within a user control?

Fortunately, the answer is straightforward—you use the same techniques in the user control that you would if the Web control raising the event were in an ASP.NET Web page. That is, in the user control you would create an event handler for the Web control event and provide the needed code within this event handler. To demonstrate this, let's create a new user control, DisplayBeveragesSorting.ascx, which enhances the DisplayBeverages.ascx user control we examined earlier by making the DataGrid sortable.

The first step is to enhance the DataGrid so that it supports sorting. This involves adding AllowSorting="True" to the DataGrid's declaration and configuring the SortExpression properties of the DataGrid's BoundColumns. Next, create an event handler for the DataGrid's SortCommand event within the user control code-behind class. (A discussion on creating sortable DataGrid's is beyond the scope of this article. For more information on adding sorting functionality to a DataGrid, read: An Extensive Examination of the DataGrid Web Control.)

Adding sorting functionality changes the code for the user control's code-behind class a bit. The major change is that the BindData() method now takes a string input that specifies the column name by which the results should be sorted. Also, the code-behind class now contains an event handler for the DataGrid's SortCommand event. This event handler merely rebinds the data to the DataGrid using the specified SortExpression.

private void Page_Load(object sender, System.EventArgs e)
{
   if (!Page.IsPostBack)
      BindData("UnitPrice DESC");
}

private void BindData(string orderBy)
{
   OleDbConnection myConnection = new 
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
Server.MapPath("Northwind.mdb"));

   string productSQL = 
    "SELECT ProductName, UnitPrice FROM Products " + 
      "WHERE CategoryID = 1 ORDER BY " + orderBy;

   OleDbCommand myCommand = new OleDbCommand(productSQL, myConnection);

   myConnection.Open();
   OleDbDataReader reader = myCommand.ExecuteReader();
   dgProducts.DataSource = reader;
   dgProducts.DataBind();
   reader.Close();
   myConnection.Close();         
}

private void dgProducts_SortCommand(object source, 
  System.Web.UI.WebControls.DataGridSortCommandEventArgs e)
{
   BindData(e.SortExpression);
}

The following screenshots show the SortableBeverages.aspx Web page, which includes the user control DisplayBeveragesSorting.ascx. Note that the DataGrid's column headers are rendered as links. Clicking a link causes the Web Form to postback, raising the DataGrid's SortCommand event handler. The event handler in the user control catches this event and processes it by rebinding the database data to the DataGrid in the properly sorted order. The first screenshot shows the page when first visited; the second screenshot shows the page after the Product Name link has been clicked.

ms972975.usercontrols_fig06(en-us,MSDN.10).gif

Figure 6. SortableBeverages user control

ms972975.usercontrols_fig07(en-us,MSDN.10).gif

Figure 7. SortableBeverages user control after sorting

What is important to take away from this example is that if you need to do some processing when a Web control event fires, and that Web control is within a user control, place the event handler for that event within the user control's code-behind class.

Adding Properties and Methods to a User Control

Our running user control example—displaying the beverage products in a DataGrid—could be made much more useful by allowing the Web page that the user control resides upon to specify what category of products the DataGrid should display. This way, the same user control could be used to display information about Beverages, Condiments, Dairy products, and so on. To accomplish this, we need to add a property to the user control's code-behind class through which the CategoryID of products to display can be specified.

Properties in a user control code-behind class can be created as public member variables or by using the property syntax. That is, we could add a CategoryID property to our user control by adding to the user control code-behind class either:

public class DisplayProductsByCategory : System.Web.UI.UserControl
{
   public int CategoryID;  // a public member variable

   private void Page_Load(object sender, System.EventArgs e)
   {
      ...
   }
}

Or:

public class DisplayProductsByCategory : System.Web.UI.UserControl
{
   // ******** property syntax ************
   private int categoryID;
   public int CategoryID
   {
      get
      {
         return categoryID;
      }
      set
      {
         categoryID = value;
      }
   }
   // *************************************

   private void Page_Load(object sender, System.EventArgs e)
   {
      ...
   }
}

The benefit of the property syntax is that you can perform checks to find out if the property values are within legal bounds or not. Furthermore, as we'll see in an example shortly, you might need to save property values to the ViewState, in which case you'll want to use the property syntax.

To display the products that belong to a certain category, we need to update our BindData() method so that the SQL statement uses a parameter to specify the CategoryID as opposed to having a hard-coded value. The following code shows the BindData() method for our updated user control, DisplayProductsByCategory.ascx.

private void BindData(string orderBy)
{
   OleDbConnection myConnection = new 
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
Server.MapPath("Northwind.mdb"));

   string productSQL = "SELECT ProductName, UnitPrice FROM " +
     "Products WHERE CategoryID = @CatID ORDER BY " + orderBy;

   OleDbCommand myCommand = new OleDbCommand(productSQL, myConnection);
   myCommand.Parameters.Add(new OleDbParameter("@CatID", CategoryID));

   myConnection.Open();         
   dgProducts.DataSource = myCommand.ExecuteReader();
   dgProducts.DataBind();
   myConnection.Close();         
}

Notice that the @CatID parameter is assigned the value of the CategoryID property. This ensures that the DataGrid will only display those products that belong to the category specified using the CategoryID property.

At this point we have created a user control with a CategoryID property whose value dictates what subset of products are displayed in the DataGrid. All that remains is to set this property programmatically in an ASP.NET Web page. To demonstrate this, let's create a new Web page, Condiments.aspx, that will use the DisplayProductsByCategory.ascx user control to display just the condiment products (those whose CategoryID equals 2.)

When adding the user control in the HTML portion of Condiments.aspx, it is important that we provide an ID attribute for the user control. We'll use this ID to reference the user control in the code-behind class. That is, use the following declarative syntax when adding the user control in the HTML portion:

<skm:ShowProducts runat="server" id="beverages"></skm:ShowProducts>

In the ASP.NET Web page's code-behind class we must reference the user control programmatically and set its CategoryID property to the value 2. This can be accomplished in either one of two ways:

  • Use the FindControl(ID) method to locate the user control in the Web page's control hierarchy.
  • Create a protected member variable in the Web page's code-behind class having the same type as the user control and the same name as the user control's ID.

Let's examine each of these two approaches. The first approach can be accomplished using the following code:

private void Page_Load(object sender, System.EventArgs e)
{
   if (!Page.IsPostBack)
   {
      DisplayProductsByCategory myUC = 
        (DisplayProductsByCategory) FindControl("beverages");
      myUC.CategoryID = 2;
   }
}

The FindControl(ID) method takes in a single string input and searches the page's control hierarchy for a control with an ID property value equal to the input parameter. If such a control is found, it is returned; otherwise null (or Nothing, in Microsoft® Visual Basic® .NET) is returned. FindControl(ID) returns an object of type Control, so we need to cast the return value from Control to the user control type, DisplayProductsByCategory.

The second method is to add a protected member variable with the appropriate type and name to the Web page's code-behind class. Once this has been done, the user control can be referenced directly by this member variable, as shown in the following example:

public class Condiments : System.Web.UI.Page
{
   protected DisplayProductsByCategory beverages;

   private void Page_Load(object sender, System.EventArgs e)
   {
      if (!Page.IsPostBack)
         beverages.CategoryID = 2;
   }
}

Notice that the protected member variable is of the correct type (DisplayProductsByCategory) and has the same name as the ID property of the user control in the Web page's HTML portion. Once this variable has been declared, it can be referenced directly, as shown in the Page_Load event handler. (Realize that if the name of the member variable and the user control's ID property value do not match up, you'll get a NullReferenceException exception.)

Note When creating user controls with Visual Studio .NET, the class name of the user control is the name of the user control's code-behind class, DisplayProductsByCategory in this instance. If you are not using a code-behind class for the user control, and are instead having the user control's source code in a server-side <script> block, you can specify the user control's class name in the @Control directive's ClassName attribute. (For more information on the @Control directive refer to the technical documentation.)

The following screenshot shows the Condiments.aspx page when visited through a browser. Note that the products displayed belong to the condiments product line, and that the reason this subset of products is displayed is because the ASP.NET Web page sets the user control's CategoryID property to 2, which is the CategoryID for condiments.

ms972975.usercontrols_fig08(en-us,MSDN.10).gif

Figure 8. Condiments page, showing DisplayProductsByCategory control

Handling Postbacks

The DisplayProductsByCategory.ascx user control uses a sortable DataGrid, just like the DisplayBeveragesSorting.ascx user control we examined in the previous section. When the user clicks one of the DataGrid's sortable column headers, the Web page is posted back and the user control's event handler fires, recalling BindData(string), passing in the SortExpression of the clicked column.

Ideally, the page would display the correct subset of condiments appropriately sorted. However, clicking a DataGrid header link causes no records to be displayed (see Figure 9).

ms972975.usercontrols_fig09(en-us,MSDN.10).gif

Figure 9. Behavior clicking the header

The reason this happens is because the ASP.NET Web page is posted back, but in the ASP.NET Web page's Page_Load event handler, the user control's CategoryID property is only assigned when Page.IsPostBack is false. Therefore, the user control's CategoryID property has the default int value of 0. Since there are no products with a CategoryID of 0, no products are listed in the DataGrid.

A simple workaround is to have the user control's CategoryID property set on every page load, regardless of whether or not it's a postback. A better technique is to make the user control responsible for persisting its property values across postbacks. This can be accomplished by altering the user control's CategoryID's property syntax to read and write its values to the ViewState, as opposed to a private member variable. The needed change follows:

public int CategoryID
{
   get
   {
      object o = ViewState["CategoryID"];
      if (o == null)
         return 0;   // return a default value
      else
         return (int) o;
   }
   set
   {
      ViewState["CategoryID"] = value;
   }
}

If you have built compiled, custom controls in the past, this approach should be intimately familiar. The ViewState is a dictionary that is automatically serialized into a hidden <input> field in the rendered Web page's <form> during rendering. Upon postback this hidden <input> field is deserialized back into the ViewState object. Hence, the ViewState object can be used to persist values across postbacks. With the above change, the user control's CategoryID property value is persisted in the ViewState so it is not lost across postbacks. With this enhancement to the user control, DisplayProductsByCategory.ascx can now correctly handle DataGrid sorting.

User Control Events

Web controls that appear in an ASP.NET Web page—like the TextBox, DropDownList, DataGrid, and others—can raise server-side events, which can be detected and handled programmatically by using event handlers defined in the Web page's code-behind class. Like Web controls, user controls can also provide events that, when fired, can be handled by the Web page that contains the user control. Before a user control event can be caught by the page that contains the user control, we first must create an event in the user control and then fire it at an appropriate time.

To illustrate these concepts—creating an event in a user control, raising the event, and handling the raised event in the Web page that contains the user control—let's look at another example of listing products by categories. This time, rather than having a CategoryID in the user control, let's add a drop-down list of all of the possible categories. The user, then, can select what category of products she wants to see. Upon making a selection, the Web Form will be posted back, and the DataGrid will be bound again, displaying those products matching the selected category.

We can accomplish all of this in a user control. Start by creating a new user control called DisplayProductSelection.ascx with DropDownList and DataGrid Web controls. In the user control's code-behind class we need to populate the DropDownList with the categories only on the first load, and not on subsequent postbacks. The BindData() method, then, displays those products whose category matches the selected category in the drop-down list, and is called only on the first page load and when the DropDownList Web control's SelectedIndexChanged event fires. The code for the user control's code-behind class is shown here:

private void Page_Load(object sender, System.EventArgs e)
{
   if (!Page.IsPostBack)
   {
      BindCategoriesDDL();
      BindData("UnitPrice DESC");
   }
}

private void BindCategoriesDDL()
{
  // binds the records from the Categories table to the 
  // categories DropDownList (code removed for brevity)
}

private void BindData(string orderBy)
{
   OleDbConnection myConnection = new 
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
Server.MapPath("Northwind.mdb"));

   string productSQL = "SELECT ProductName, UnitPrice FROM " +
     "Products WHERE CategoryID = @CatID ORDER BY " + orderBy;

   OleDbCommand myCommand = new OleDbCommand(productSQL, myConnection);
   myCommand.Parameters.Add(new OleDbParameter("@CatID", 
     categories.SelectedValue));

   myConnection.Open();
   dgProducts.DataSource = myCommand.ExecuteReader();
   dgProducts.DataBind();
   myConnection.Close();         
}


private void categories_SelectedIndexChanged(object sender, 
  System.EventArgs e)
{
   BindData("UnitPrice DESC");
}

With this code, the DisplayProductSelection.ascx user control allows the user to select a category from a drop-down list, and then displays the products matching that category in the DataGrid. The screenshot in Figure 10 below shows this user control being used in an ASP.NET Web page.

ms972975.usercontrols_fig10(en-us,MSDN.10).gif

Figure 10. DisplayProductSelection control

At this point, the user control completely handles the events of its containing Web controls. That is, the user control's code-behind class contains the event handler for the DropDownList's SelectedIndexChanged event. As we discussed earlier, this is the ideal approach—a user control should handle the events that are raised by its Web controls. However, what if the ASP.NET Web page is interested in knowing when the user has chosen to view a different category of products?

To support this we can add a custom event to our user control's code-behind class, and raise this custom event in the DropDownList's SelectedIndexChanged event handler. The ASP.NET Web page that contains the user control, then, can create an event handler for the user control's custom event. Let's first look at adding and raising a custom event to a user control and then examine how to create an event handler for a user control event in an ASP.NET Web page.

Creating and Raising a Custom Event

An event is a means by which a class notifies a set of subscribers that some interesting thing has occurred. A subscriber is a class that's interested in being alerted when the event transpires. In our example, the user control class (the event source) will be notifying the ASP.NET Web page (the subscriber) that the user has selected a different product category from the drop-down list (the event).

When creating an event in a class, you must specify the delegate for the event. The delegate indicates what the event handler's signature must look like—that is, what return value and input parameters the event handler must accept. For events in the .NET Framework, all event delegates must not return anything, and must accept two input parameters: the first of type Object and the second one of, or derived from, System.EventArgs.

There are a number of built-in event delegates in the .NET Framework. The simplest is the EventHandler delegate, which indicates that the event handler for the event will have the signature:

void eventHandlerName(object sender, EventArgs e)

Another common event delegate in Web applications is the CommandEventHandler, which indicates that the event handler for the event will have the signature:

void eventHandlerName(object sender, CommandEventArgs e)

The CommandEventArgs class is derived from the EventArgs class, and contains two additional properties: CommandArgument and CommandName. The CommandEventHandler delegate is used for a number of ASP.NET Web control events, such as the Button's Command event, and the DataGrid, DataList, and Repeater's ItemCommand events.

In addition to using one of the event delegates included with the .NET Framework, you can also create your own event delegates, as well as your own classes extending EventArgs. For more information on events and event delegates be sure to read Events and Delegates, Event Delegates in Simple English, Defining an Event.

For our demonstration, we will be creating an event in the user control called ProductSelectionChanged, which will have an event delegate of type CommandEventHandler. To create an event in the user control class, use the following syntax:

// C#
public event CommandEventHandler ProductSelectionChanged;

' VB.NET
Public Event ProductSelectionChanged(ByVal sender as Object, 
  ByVal e as CommandEventArgs)

This event declaration should appear within the user control's code-behind class.

All that remains left to do at the user control level is to raise the event when the user chooses a different category through the categories drop-down list.

private void categories_SelectedIndexChanged(object sender, 
  System.EventArgs e)
{
   BindData("UnitPrice DESC");

   // Fire the ProductSelectionChanged event...
   if (ProductSelectionChanged != null)
   {
      CommandEventArgs e = new 
        CommandEventArgs(categories.SelectedValue, null);
      ProductSelectionChanged(this, e);
   }
}

Notice that to raise the event we simply create a new CommandEventArgs instance and invoke the event with syntax similar to calling a method. The reason I chose to have the ProductSelectionChanged event use the CommandEventHandler event delegate instead of the EventHandler event delegate was because I wanted to pass along the CategoryID the user selected from the drop-down list. I simply used the CommandEventArgs class's CommandName property as a conduit for transmitting the selected CategoryID. (A better approach would have involved creating a specific delegate, but doing so here, I felt, would have further complicated the example.)

Note Realize that the Visual Basic .NET syntax for raising an event is slightly different from C#. In Visual Basic .NET use the RaiseEvent statement like so:

RaiseEvent ProductSelectionChanged(Me, e)

Handling the Event

At this point we have given our user control code-behind class a custom event, and have this event raised when the user selects a new category from the drop-down list. The final piece of the puzzle is to create an event handler in an ASP.NET Web page that utilizes this user control and to wire up the ProductSelectionChanged event to the event handler.

The first step, naturally, is to create a new ASP.NET Web page. For this example, I created a page called AnyProduct.aspx, and added the DisplayProductSelection.ascx user control. Since we will need to reference the user control in the code-behind class, be sure to give it an ID attribute and create a protected member variable in the code-behind class of the proper type and with the same name as the user control's ID in the HTML portion. (In AnyProduct.aspx, the ID value and member variable name is products.)

Note If you are using Visual Basic .NET, be sure to declare the protected member variable using the WithEvents keyword. The WithEvents keyword indicates that the member variable's class might raise an event that we want to handle. The syntax, then, would be:

Protected WithEvents products As DisplayProductSelection.

With the user control added to the HTML portion and a corresponding protected member variable added, the next step is to create the event handler. Since the ProductSelectionChanged event is defined to use the CommandEventHandler event delegate, the event handler will have to accept two input parameters: an object and a CommandEventArgs instance. The following shows what a suitable event handler might look like in C#:

private void products_ProductSelectionChanged(object sender, 
CommandEventArgs e)
{
   Response.Write("The user selected CategoryID " + e.CommandName);
}

Notice that the event handler name—products_ProductSelectionChanged—could be any legal method name. Also, note that in the event handler we can get the selected CategoryID by using the CommandName property of the CommandEventArgs parameter.

The final step is to wire up the ProductSelectionChanged event to the products_ProductSelectionChanged event handler. In C#, the event handlers are typically wired during the Page class's Init event. If you are using Visual Studio .NET, the code-behind class contains an InitializeComponent() method that is invoked in the OnInit() method. (These two methods are tucked away in the Web Form Designer Generated Code region.) To wire up the event, add the following line of code in the InitializeComponent() method:

products.ProductSelectionChanged += new 
  CommandEventHandler(this.products_ProductSelectionChanged);

Here products is the member variable representing the user control in the code-behind class. This syntax adds an event delegate to the event. The parameter passed into the CommandEventHandler delegate is the method name that should be invoked when the ProductSelectionChanged event fires.

Note   In Visual Basic .NET, you can wire up an event to an event handler using the Handles keyword like so:

Private Sub products_ProductSelectionChanged(ByVal sender As Object, _
  ByVal e As CommandEventArgs) _
  Handles products.ProductSelectionChanged. 

Figure 11 shows a screenshot of AnyProduct.aspx after the user has selected a new category from the drop-down list in the user control. When the user chooses a new drop-down list item the Web Form posts back, the DropDownList raises its SelectedIndexChanged event, causing the user control's event handler to be invoked. This event handler rebinds the data to the DataGrid and then raises its ProductSelectionChanged event. This causes the products_ProductSelectionChanged event handler in the ASP.NET Web page AnyProduct.aspx to execute, and a Response.Write() statement emits the value of the CategoryID selected by the user.

ms972975.usercontrols_fig11(en-us,MSDN.10).gif

Figure 11. AnyProduct page showing the DisplayProductSelection control

Dynamically Adding User Controls

My career in Web development started with building an intranet application using classic ASP for a consulting company back in 1998. One of the requirements of this Web application was that the capabilities provided differed on a user-by-user basis. For example, a consultant could only examine information about the projects he was currently working on. A manager, on the other hand, was able to edit information on any of the projects assigned to her team. Due to this requirement, when a user visited a Web page I needed to determine their role and then display the correct user interface elements.

This model of generating user interface elements based on the user's role or permission set is still commonly used in today's Web applications. Given that, you might need to dynamically add a user control to a Web page based on a user's role. For example, if the user visiting an ASP.NET Web page is a manager, then the ListManagerProjects.ascx user control would need to be loaded; if, however, a consultant was visiting the same page, this user control would not be loaded.

One approach to dynamically showing and hiding user controls is to simply add all of the user controls that might need to be shown, and then set the Visible property of all of the user controls based on the user's role. Another approach is to dynamically load the user control into the page at runtime.

The System.Web.UI.Control class—from which the ASP.NET Page class and all Web controls derive from, either directly or indirectly—contains a Controls property, which is a collection of objects that are derived from Control as well. Since the Page class contains a collection of Controls, and each control in the collection can contain a collection of Controls, and so on, a control hierarchy is formed. (Refer back to Figure 2 to see a graphical representation of a control hierarchy.)

Web controls or user controls can be programmatically added to another control's Controls collection, thereby dynamically inserting the Web control or user control into the Page's control hierarchy at runtime. Dynamically adding Web controls is easy—simply use the Controls property's Add() method to insert the Web control:

// Create a Button
Button b = new Button();
b.Text = "Click Me!";

// Add the Button to the Page's Controls collection
Page.Controls.Add(b);

There are some subtle issues involving the Page's ViewState that can arise when adding Web controls to a Web page dynamically. A thorough discussion of this is beyond the scope of this article. The short of it is, though, that the controls must be reloaded on each and every postback, and the safest place to dynamically add the Web controls is during the Init event. For more information on dynamically adding Web controls see HOW TO: Dynamically Create Controls in ASP.NET with Visual Basic .NET and Adding Controls to a Web Forms Page Programmatically.

Dynamically loading a user control differs slightly from dynamically loading a Web control. Before a user control can be loaded into a Controls collection, it must first be loaded using the LoadControl(string) method. LoadControl(string) takes in as a string input the virtual path to the user control to load (you can use the ~ syntax discussed earlier). It returns a Control instance of the user control. This Control instance can then be added to the Page's Controls collection:

// Load the User Control
Control uc = LoadControl("~/MyUserControl.ascx");

// Add the User Control to the Controls collection
Page.Controls.Add(uc);

As with dynamically loading Web controls, when dynamically loading a user control it must be done on every visit to the page (including postbacks). Too, the ideal place for this loading is in the Page's Init event.

Note Realize that all controls have a Controls collection, not just the Page class. Therefore, if you want precise placement of the user control on the Web page, drop a PlaceHolder Web control where you want the user control to be placed, and then when programmatically loading the user control, add it to the PlaceHolder's Controls collection.

Included in this article's download is an ASP.NET Web page called DynamicUserControls.ascx that illustrates how to dynamically load a user control based on a querystring value. This demonstration shows how to have the user controls recreated on each page load during the Init event.

Conclusion

In this article we examined user controls in detail. We quickly looked at simply creating and deployin0067 a user control before turning to how user controls are injected into the Page's control hierarchy, and how their output is rendered. Following this, we looked at a number of more advanced user control topics, including: having a user control respond to an event of one of its Web controls; adding properties to a user control; giving a user control an event and creating an event handler in the ASP.NET Web page that contains the user control; and how to dynamically load a user control.

ASP.NET: Tips, Tutorials and Code

ASP.NET Data Web Controls Kick Start

Developing Microsoft ASP.NET Server Controls and Components

 

About the Author

Scott Mitchell, author of five books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies for the past five years. Scott works as an independent consultant, trainer, and writer. He can be reached at mitchell@4guysfromrolla.com or through his blog, which can be found at http://www.scottonwriting.net/sowBlog/.

© Microsoft Corporation. All rights reserved.