Trigger Page Methods from a User Control
By Tim Stall
Introduction
User Controls have a lot of benefits – they let you abstract a group of commonly used controls to a single, reusable control. Sometimes there will be a business need to pass data between the User Control and its containing ASP.NET Web page. For example, you might have an address control, and upon loading the page it sets the address control's street, city, and state properties. However, while it's simple enough for an Aspx page to trigger a User Control's methods, it is not as simple for the User Control to conversely trigger its containing page's methods.
For example, imagine you wanted to create a generic record navigator that could be used to step through the records of any collection of data. This user interface for this record navigator could be implemented as a User Control. To use this generic navigator you'd create an ASP.NET Web page, somehow display data - be it in a DataGrid, DataList, Repeater, some other built-in Web control, or a custom Web control - and finally add the navigator User Control to the page. The challenge now is to enable the navigator to interact with the data. That is, when the end user indicates that he wants to view a different page of data, the navigator User Control must be able to update the Web control being used to display the data. In this case the end user is updating the Web control displaying the data not through a WebControl that is part of the page itself, but rather through a User Control. Clearly the User Control needs to be able to invoke a method in its containing ASP.NET Web page. This method will then be able to correctly navigate through the data.
In this article we will see how a User Control can trigger the methods of its containing page. This article assumes the reader is already familiar with creating and deploying User Controls. If you need to brush up on User Controls, consider reading the User Control technical documentation, or Building ASP.NET User Controls.
Design Requirements
Any potential solution has the following design requirements:
- The User Control cannot have any page-specific features hard-coded into it. One aspect of a User Control is that it can be reused by multiple pages. Hard-coding page-specific functionality violates this, and therefore is bad practice.
- The User Control must trigger the page's method. This is what the user is intuitively looking for: they want to click something on the User Control and see the page update, they don't want to have to make an extra click back on the main page in order to pull the data from the User Control.
- Whatever techniques we use must be applicable to any aggregated-type control, such as custom controls or custom rendered controls. Although we will implement this solution with User Controls due to their simplicity, in an enterprise setting you may need to be prepared to migrate your User Control to a Custom Control in order to increase its reusability (because Custom Controls are compiled to their own DLL, there are more reusable than User Controls). Our solution cannot interfere with this potential migration.
- Our solution must adhere to standard User Control best practices; mainly that it not be adversely affected by unrelated postbacks, and that it expose all external data as public properties.
Inadequate Solutions
Sometimes it helps to understand a good solution by first understanding inadequate solutions. Two such inadequate solutions are:
- Keeping the record navigator as part of the main page and simply not using a user control. The problem with this is that it completely forfeits all the benefits from User Controls, such as code and UI reusability.
- Hard-coding the data structure into the User Control, such as making the User Control have a
Data
property that is a reference to the DataGrid, DataList, Repeater, or whatever other control might be used to display the data in the ASP.NET Web page. Although simple enough to program, this violates the design requirements of not hard-coding data, and having the User Control trigger the page (not vice-versa).
Technical Solution
What we ultimately need is for the User Control to be able to call a method, i.e. we'd like to pass it a method reference and let it call that method on its own terms. This is exactly what a Delegate lets us do. According to MSDN, a Delegate is "is a data structure that refers to a static method or to a class instance and an instance method of that class." In other words you can assign a method-reference to a Delegate and pass that similar to how you'd pass other types.
We will solve the problem with an example solution for the record navigator problem we mentioned in the introduction.
In this example, there is a WebForm that contains a User Control. The User Control contains two properties, one for
a Delegate and one for the business data – in this case an index as an integer.
The WebForm has a PopulateData()
method to populate the data. This method takes in a page index, sends
that as a parameter to a GetData()
method, and then populates the Page controls appropriately. The
WebForm creates a Delegate that refers to the PopulateData()
method, and passes that Delegate to the
User Control's Delegate-type property.
Whenever the User Control's previous or next buttons are clicked, it then calls the Delegate that it was given,
passing in the data values selected from the User Control (in this case just index
). Finally the
Delegate in turn triggers the parent page's PopulateData()
method.
Technical Implementation
Now that we have a high-level understanding of what we want to do, let's code it. First we want to create the User Control. Create a User Control named
RecordIndex.ascx
and add the
following two properties to it:
|
The first property, CurrentIndex
, stores the business data - the purpose for having the User Control in
the first place. The second property UpdateIndex
stores the Delegate.
Next add the GUI controls to the User Control: two buttons (BtnPrev
, BtnNext
) and a label
(LblIndex
) as shown below:

Double-click each button to bring up its click event, and add the following code:
|
Note that both buttons reference a SetCurrentIndex()
method; let's add that next:
|
This method sets the label to the CurrentIndex
, and then invokes the Delegate. This is the User
Control's method that does the work to trigger the parent page's PopulateData()
method.
This finishes the User Control; let's start on the WebForm now.
Create a WebForm named Main.aspx
. Add a DataGrid and the RecordIndex User Control to it (keep the
default names of DataGrid1
and RecordIndex1
).
Next, we need to add a reference to the User Control in the code-behind class. We also need to create a Delegate.
These two tasks are accomplished by adding the following declarations to the WebForm's code-behind class:
|
The RecordIndex1
member variable lets us work with the RecordIndex User Control in the WebForm's code-behind
class. The DelPopulateObject
is the Delegate that will reference the PopulateData()
method.
We're now ready to add the PopulateData()
and GetData()
methods to the WebForm's code-behind class:
|
The PopulateData()
method takes in an index, passes it along to the GetData()
method, and
then sends the resulting DataTable
to a DataGrid. For simplicity the GetData()
method in
this example just uses the input parameter to create a string array – however it can be replaced with whatever data
access code you use.
With all the supporting pieces in place, now add the WebForm's Page_Load
method. This method first sets
the default business values for the User Control, and then creates an instance of the Delegate and also assigns it to
the User Control.
|
Lastly, add a dummy button to the WebForm just to ensure that it unrelated postbacks don't interfere with the RecordIndex User Control. After adding the user interface pieces (the DataGrid, the User Control, and a dummy Button), your WebForm should look something like the screenshot below. Note the RecordIndex User Control at the bottom, the DataGrid in the middle, and the "Test Postback" button.

Program Flow
On the initial WebForm load, the relevant control flow starts at the WebForm's
Page_Load
and sets the
User Control's properties. It only needs to set the business data (like the index) the first time because that data
is serialized and persists in the page's ViewState. It sets the Delegate property upon every postback because the Delegate
is not serialized and saved to the ViewState by default.
After the WebForm Page_Load
, the User Control's Page_Load
is called. This sets the default
business values (stored in the User Control's properties) and then calls the SetCurrentIndex()
method.
This method updates the User Control's GUI to reflect the values, and then gets the Delegate reference from the property
and invokes the method, triggering the data to be updated on the parent page.
When the user clicks the RecordIndex previous or next buttons, they update the internal business data appropriately,
and then call the SetCurrentIndex()
method, which updates the parent page as just described previously.
The control flow is nearly identical for postbacks – except that the default business data is not reset.
Summary
User Controls offer many benefits to Web applications. Part of taking advantage of these benefits is passing data both ways between a WebForm and a User Control. While passing data to the User Control is trivial, passing it back from the User Control to the page is not. However we can still solve this by having the page instantiate a Delegate and pass that to the User Control, giving the User Control the ability to trigger a parent page's method on demand.
Happy Programming!
Attachments: