Moving from the previous ASP development paradigms to the ASP.NET approach of building Web applications requires a shift in thought about how Web pages are constructed. Developers have tried to encapsulate functionality using server-side includes or, in some cases, custom Active X controls that are reused throughout an application. Now, developers can build sections of a Web page into smaller functional pieces of code that can respond to and raise events, called User Controls.
To explain the use of events to encapsulate behavior in a User Control, I’ll demonstrate how to build a Web-based, data-driven, business application that includes several pages that allow users to return a list of items, select an individual item, and edit the details of that item. This is a fairly simple and common scenario in most business applications. Whatever approach you choose to implement, this functionality for one page should be reusable across the rest of the application.
How User Controls work
A User Control is a section of a page that has its own HTML elements and the code that interacts with those elements. You can assemble pages quickly by placing common User Controls on a page and then wiring them together with code. For my example, it’s logical to consider breaking the page into three distinct pieces:
- · EmployeeList.ascx—A control to hold the ASP.NET DataGrid, which lists the items
- · EmployeeDetails.ascx—A control that holds a form used for editing the details of a particular item
- · Employees.aspx—The .aspx page that manages the state of all its constituent controls
Architecting the page
I’ll use the sample Northwind database schema to demonstrate this technique. In this simple application, I’ll allow the user to return a list of employees from the Employees table. By providing the user with a page that contains a DataGrid populated with employees, the user can then select a particular employee, and the page will then present a form with the details of the selected employee. This form will have two buttons: one for submitting the changes to the database and another for canceling the update.
The Page_Load event of the Employees.aspx page, which I won’t be explicitly showing you here for the sake of brevity, is responsible for setting the initial state of the user controls. Because I want to choose an employee before displaying the employee’s details, the EmployeeList.ascx control is initially visible, while the EmployeeDetails.ascx control is not. The latter control will not be visible until an employee is selected from the ASP.NET DataGrid control in EmployeeList.ascx. To display the initial view, the Page_Load event handler sets the appropriate properties of the EmployeeList.ascx control and then calls EmployeeList.GetEmployees. The GetEmployees method returns a DataSet filled with employees from the database and binds this DataSet to a DataGrid named EmployeeGrid.
Ideally, the desired effect would be for the user to select an employee from the list and have the details for the selected employee show on the same form. However, the form for editing the employee details is in a separate user control, so how is this accomplished? This is where the .NET event functionality comes in handy.
Events: The glue that binds User Controls together
When a user makes a selection in the EmployeeList control, I need a way for the page to accept the selected EmployeeID and then use that information to display the employee details in the EmployeeDetails control. I’ll accomplish this by using events: messages sent from an object and received by another object.
In this case, the EmployeeList control raises an event that exposes the selected EmployeeID. The Employees page handles the event and uses the information returned to load and populate the EmployeeDetails control. The .NET Framework provides two approaches for handling events from a User Control. Deciding which approach to use depends upon whether the raised event needs to pass any event-related data.
Events without data
If there is no need to return data from the raised event, the User Control merely needs to define an event using the generic event declaration as follows:
public event EventHandler EmployeeSelected;
This should be followed by some code to raise the event, as shown below:
protected virtual void OnEmployeeSelected(EventArgs e)
{
if(EmployeeSelected != null)
EmployeeSelected(this, e);
}
When the event needs to be raised from within the control, you’d call the OnEmployeeSelected method. In this example, the event will likely be called from the OnItemCommand event handler of the EmployeeGrid control, as shown in Listing A.
Events with data
I need to pass data with the event, so my program should define its own custom class derived from the EventArgs class. Once this class is defined, I’ll declare a delegate for the event. First, I need to define an EmployeeCommandEventArgs custom class that will contain the EmployeeID selected by the user of the EmployeeList control. Check Listing B for my implementation.
After declaring a custom EventArgs class, I need to create a custom delegate that knows how to pass the new EmployeeCommandEventArgs class when EmployeeList raises the EmployeeCommandEvent. The delegate signature looks like this:
public delegate void EmployeeCommandEventHandler
(object sender,
EmployeeCommandEventArgs e);
Since the EmployeeList control now has its own event argument class, I’ll need to change the event declaration as well to reflect that I’m using the custom EventArgs class. Listing C shows the updated event declaration.
In the OnItemCommand event handler of the EmployeeGrid DataGrid control, I’ll create the new event argument class and populate it with the required data. Assuming the DataKeyField property of the EmployeeGrid is set to the EmployeeID, populating the event data is rather straightforward, as you can see in Listing D.
Handling the event
The final step is to make the Employees.aspx page able to handle the event raised by the EmployeeList control. The Employees.aspx page needs to register the event and provide an event handler to respond to EmployeeList’s events. You can accomplish this by adding the following line of code to the InitializeComponent method:
EmployeeList1.EmployeeSelected += new
EmployeeCommandEventHandler(this.Employee_Selected);
Now the Employee_Selected event handler can get the EmployeeID from the EmployeeCommandEventArgs object. It passes this value to the GetEmployeeInfo method of the EmployeeDetails.ascx User Control and set its visible property to True to display the details, like so:
protected void Employee_Selected(
object sender, EmployeeCommandEventArgs e)
{
int EmployeeID = e.EmployeeID;
EmployeeDetails1.GetEmployeeInfo(EmployeeID);
EmployeeDetails.Visible = true;
}
The GetEmployeeInfo method accepts this parameter, retrieves the information for the selected employee, and populates the constituent controls with the appropriate data.
Of course, to make this simple application fully functional, I would need to wire the Cancel button and the Submit button events and raise them to the Employees.aspx page. Since the code to update the selected employee’s information is contained within the EmployeeDetails.ascx control, it is likely that neither of these buttons needs to contain data when raising events. In this case, you can use the generic EventArgs class and the EventHandler delegate as shown above. The Cancel button and Submit button event handlers simply need to hide the EmployeeDetails.ascx control and then refresh the data for the Employeelist.ascx control. Of course, the Submit button will also post changes back to the employee database.
A reusable pattern
The technique demonstrated here represents a reusable pattern that you can follow for creating any kind of data-centric business application. Creating User Controls that encapsulate list views of entities and creating other User Controls that encapsulate editing commands for a single entity allow you to quickly develop new data-centric applications. When you create new pages, you can simply drop the User Controls on the page and wire them together using the events raised by the controls. This technique takes advantage of one of the most important features of the .NET Framework—the ability to create objects and completely encapsulate their behavior.