To read the article online, visit http://www.4GuysFromRolla.com/articles/031704-1.aspx

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

Private _intCurrentIndex As Integer
Private _delUpdateIndex As System.Delegate

Public Property CurrentIndex() As Integer
    Get
        Return _intCurrentIndex
    End Get
    Set(ByVal Value As Integer)
        _intCurrentIndex = Value
    End Set
End Property

Public WriteOnly Property UpdateIndex() As System.Delegate
    Set(ByVal Value As System.Delegate)
        _delUpdateIndex = Value
    End Set
End Property

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:

Screenshot of User Control UI

Double-click each button to bring up its click event, and add the following code:

#Region "Buttons"

Private Sub BtnPrev_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles BtnPrev.Click
	_intCurrentIndex = (CInt(Me.LblIndex.Text) - 1)
	SetCurrentIndex()
End Sub

Private Sub BtnNext_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles BtnNext.Click
	_intCurrentIndex = (CInt(Me.LblIndex.Text) + 1)
	SetCurrentIndex()
End Sub

#End Region

Note that both buttons reference a SetCurrentIndex() method; let's add that next:

Private Sub SetCurrentIndex()

	'Set User Control's properties:
	Me.LblIndex.Text = _intCurrentIndex.ToString()

	'call method to re-populate parent page data, 
	'	given current index:
	Dim aObj(0) As Object
	aObj(0) = _intCurrentIndex
	_delUpdateIndex.DynamicInvoke(aObj)

End Sub

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:

Protected RecordIndex1 As RecordIndex

Delegate Sub DelPopulateObject(ByVal myInt As Integer)

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:

Private Sub PopulateData(ByVal i32Value As Int32)
    'Get the data, and send that to the Page:
    Me.DataGrid1.DataSource = GetData(i32Value)
    Me.DataGrid1.DataBind()
End Sub

Private Function GetData(ByVal i32Index As Int32) As String()
    'For simplicity, just return a string array of simple data.
    '   This could be a DataBase call instead.

    'Make index be absolute value to handle negative numbers:
    i32Index = Math.Abs(i32Index)

    Dim astrValues(i32Index) As String

    'Populate the array:
    Dim i As Int32
    For i = 0 To i32Index
        astrValues(i) = i.ToString()
    Next

    Return astrValues
End Function

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.

Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    'Put user code to initialize the page here

    If Not Page.IsPostBack Then
        'default param: index = 5
        Me.RecordIndex1.CurrentIndex = 5
    End If

    'create DELEGATE for getData method, and send it to the
    '   RecordNavigator user control
    '   This will allow the user control to call a method 
    '   from its containing page.
    'NOTE: Must put this outside of the Page.IsPostBack because
    '   property value doesn't persist and hence must be reassigned
    '   on each postback.
    Dim delPopulate As New DelPopulateObject(AddressOf Me.PopulateData)
    Me.RecordIndex1.UpdateIndex = delPopulate
End Sub

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.

Screenshot of WebForm UI.

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!

  • By Tim Stall


    Attachments:


  • Download the complete source code (in ZIP format)

  • Article Information
    Article Title: ASP.NET.Trigger Page Methods from a User Control
    Article Author: Tim Stall
    Published Date: March 17, 2004
    Article URL: http://www.4GuysFromRolla.com/articles/031704-1.aspx


    Copyright 2017 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers