Web User Controls: Beyond the Basics

Make User Controls Interact with the Host Page





Web User Controls: Beyond the Basics

Make User Controls Interact with the Host Page


By Dino Esposito


Virtually all ASP.NET pages are made up of collections of server controls. In addition to in-box controls, such as TextBox and GridView, you can create and use your own controls. Classic extended server controls are classes derived from a base control class; Web user controls are sort of pagelets and result from the visual aggregation of a bunch of existing built-in and custom server controls. You create a Web user control in much the same way you create an ASP.NET page; you can employ it inside host pages as if it were a classic server control. Put another way, a Web user control is an embeddable page with a bolted-on object model for further programmability. A Web user control is saved to a file with the .ascx extension and, much like the ASP.NET page, can refer to a separate code-behind class or include inline server code.


Web user controls should be familiar to most ASP.NET developers they were introduced in the initial release of ASP.NET. However, using such controls properly and effectively with an ASP.NET page requires familiarity with a number of techniques and practices. I bet that all ASP.NET developers know how to embed a user control in a page. But what about synchronizing the activity of a user control with the remainder of the page? What about loading and, more importantly, commanding user controls dynamically?


In this article I ll take a relatively common scenario where user controls are employed and discuss how to implement a basic document-view model in which the user control plays the role of the view and any user control with given characteristics can successfully interact with the page. To start, let s say more about the context.


The Typical Scenario

You typically use a Web user control to refer a collection of user interface elements that logically form a single block. Imagine that you need to provide users with a page to review and edit some information say, details of a customer. You might come up with a page like that shown in Figure 1. As you can see, the page features a grid to list contacts and a generic panel where you display additional information. I believe that 90% of ASP.NET developers would seriously consider using a Web user control to implement the panel. Such a Web user control has a clear dependency on the remainder of the page. In fact, the user control is asked to display details about the selected customer. The ID of the customer is a piece of information that belongs to the ASPX page and must be passed, in some way, down to the user control. Similarly, the user control owns all details about the selected customer; for example, company name, address, and phone number. Should the user control include editing capabilities, you might need to refresh the host page to reflect changes. Look again at Figure 1 and imagine, for example, that the user control updates the contact name. At this point, you need to refresh the grid to have the new name displayed. In the end, the new information, or at least a notification of the change, must flow from the user control up to the host page.


Figure 1: The right-edged panel is a Web user control expected to contain details about the selected element.


The simplest way to link a Web user control from an ASP.NET page is via a static reference. You add an @Register directive on top of the page and then reference the user control through the specified prefix and tag name:


<% @Register tagPrefix="x" tagName="MyUserControl"

 Src="myusercontrol.ascx" %>


Linked in this way, the user control is statically associated with the page. It shows up in the Controls collection of the page and you can still remove it through the methods of the collection. However, you get a runtime exception if, for whatever reason, the ASCX file or one of its dependencies are not available at load time. In the remainder of the article, I ll demonstrate how to load user controls dynamically and how to use them from the page assuming a known interface.


Loading User Controls Dynamically

The Page class provides a method to load a user control programmatically. You read the URL of the user control from a known location for example, the Web.config file and invoke the LoadControl method of the page class. Here s an example:


Dim view As UserControl

Dim ascx As String = String.Empty

ascx = ConfigurationManager.AppSettings.Item("view")

If Not String.IsNullOrEmpty(ascx) Then

  view = Page.LoadControl(ascx)

End If


The name of the effective user control to load into the page is stored in the section of the configuration file:




By editing the Web.config file you actually modify the appearance and possibly the behavior of the page. When you statically link a user control to a page, and the page compiles, you re safe about your code. Any interaction between the page and the control is guaranteed to be correct and contracted. There might still be bugs around, but that s another story. When you load an ASCX from the configuration file, you don t know much about it. You can only load it as an instance of the UserControl class and, in doing so, you lose all the additional object model and functionality specific to the control.


The bottom line is that whenever you load an external component into an application, you must also provide a contract for the component to fulfill. In case it doesn t, the component can t be loaded into the application. To define the contract, you make assumptions about the expected behavior of the component. In this case, you have a customer-view component that needs to receive the ID of the selected customer (a very basic form of a document) and fire an event to the host in case of updates. Figure 2 shows the source code for the ICustomerView interface and a base user control class specific to the application.


Imports Microsoft.VisualBasic

Public Interface ICustomerView

 Sub SetCustomerID(ByVal customerID As String)

 Event CustomerChanged As EventHandler(Of CustomerEventArgs)

 Sub OnCustomerChanged()

End Interface

Public Class ViewUserControl : Inherits UserControl :

 Implements ICustomerView

 Private _customerID As String = String.Empty

 Public ReadOnly Property CustomerID() As String


         Return _customerID

     End Get

 End Property

 Public Event CustomerChanged As EventHandler _

   (Of CustomerEventArgs)

        Implements ICustomerView.CustomerChanged

 Public Sub OnCustomerChanged() _

        Implements ICustomerView.OnCustomerChanged

     Dim args As New CustomerEventArgs

     args.CustomerID = CustomerID

     RaiseEvent CustomerChanged(Me, args)

 End Sub

 Public Sub SetCustomerID(ByVal customerID As String) _

        Implements ICustomerView.SetCustomerID

     _customerID = customerID

     If _customerID Is Nothing Then




     End If

 End Sub

 Protected Overridable Sub UpdateUI()

 End Sub

 Protected Overridable Sub ClearUI()

 End Sub

End Class

Figure 2: Defining the contract for the user control.


The ICustomerView interface defines the CustomerChanged event that any compatible user control will fire to the page if the contents shown in the control get updated at some point. The SetCustomerID method initializes the user control by passing the ID of the selected customer. The OnCustomerChanged method is required for structural reasons that I ll discuss in a moment.


At this point you can choose to implement the interface directly in each user control or, more elegantly, create a base class that implements the interface: the ViewUserControl class. Actual Web user controls will then derive from this base class. The base class can abstract the contract of the interface to the extent that it is possible and desired. For example, I added two overridable methods to the ViewUserControl class: UpdateUI and ClearUI. They get called as soon as the document information is passed to the user control. The code snippet in Figure 3 shows the template of the code-behind class for a very simple but effective customer-view user control.


<%@ Control Language="VB" CodeFile="cust_simple.ascx.vb"

 Inherits="cust_simple" %>

Partial Class cust_simple : Inherits ViewUserControl

   Protected Overrides Sub UpdateUI()


       Label1.Text = CustomerID

   End Sub

   Protected Overrides Sub ClearUI()


       Label1.Text = String.Empty

   End Sub

End Class

Figure 3: Template for a simple but effective customer-view user control.


The user control features a label named Label1 that is updated with the ID of the selected customer. Let s step up and author a richer user control that fulfills the same contract.


Passing Data to the Control

Figure 4 illustrates the host page of a Web user control that implements the ICustomerView interface. The page contains a PlaceHolder control that sets the place for the user control.


Imports System.Web.Configuration

Partial Class Test : Inherits System.Web.UI.Page

 Private view As ViewUserControl

 Protected Sub Page_Load(ByVal sender As Object, _

  ByVal e As System.EventArgs) Handles Me.Load

     Dim ascx As String = String.Empty

     ascx = ConfigurationManager.AppSettings.Item("view")


     If Not String.IsNullOrEmpty(ascx) Then

        view = Page.LoadControl(ascx)

         AddHandler view.CustomerChanged, _

           AddressOf RefreshGrid



         Dim lit As New LiteralControl("No view available.")


     End If

 End Sub

 Protected Sub GridView1_PageIndexChanged(ByVal sender _

  As Object, ByVal e As System.EventArgs) _

  Handles GridView1.PageIndexChanged

     GridView1.SelectedIndex = -1


 End Sub

 Protected Sub GridView1_SelectedIndexChanged(ByVal _

  sender As Object, ByVal e As EventArgs) _

  Handles GridView1.SelectedIndexChanged

     Dim id As String = GridView1.SelectedDataKey.Value


 End Sub

End Class

Figure 4: A host page that loads Web user controls dynamically.


In the Page_Load event, determine the URL of the user control and load it in the assigned place. Next, the user control must be initialized with the ID of the selected customer. The SelectedIndexChanged event on the grid signals when a new contact is selected on the grid:


' Get the ID of the selected customer

Dim id As String = GridView1.SelectedDataKey.Value



By contract, the user control has a SetCustomerID method that the page uses to pass information. Needless to say, you should programmatically ensure that the user control implements the required interface and degrade gracefully otherwise.


What the user control does with the ID of the customer (and in general with any document it receives) depends on the implementation of the particular control. Let s consider a more realistic user control that uses a DetailsView control to present and edit information.


You can bind data to a DetailsView in either of two ways: using a data source object or specifying a data source control. It is not widely known that a DetailsView control doesn t work in edit or insert mode if bound to a data source object. The control throws an exception as the user clicks to switch to edit mode. The description is not particularly clear, but essentially it is because of the lack of a data source ID. In the end you have two binding options for a read-only DetailsView; you have only one for a read/write DetailsView. Let s go for a SqlDataSource object then.


The data source control uses a classic SELECT statement to grab data with just one parameter the customer ID. The following code shows how to programmatically configure the data source:


Protected Overrides Sub UpdateUI()



 Dim p As New Parameter("customerid", _

  TypeCode.String, CustomerID)


 DetailsView1.DataSourceID = "SqlDataSource1"


End Sub


Figure 5 shows a page and Web user control in action.


Figure 5: An ASP.NET page and a user control synchronized.


Notifying Changes

The DetailsView control also allows users to edit the contents displayed. When this happens, the subsequent user interface is obviously up to date, but only inside the DetailsView control. What about the page? Suppose you edit the name of the selected contact. Without ad hoc coding, after the page refresh only the DetailsView shows the correct name; the grid from where you make your selection will show the old name. This is a clear synchronization issue; how can the page know about changes in the customer view? The trick is making the control fire an event when the DetailsView is updated. You handle the ItemUpdated event of the DetailsView, swallow the event, and rethrow it under a new name for example, CustomerChanged. In our implementation, the CustomerChanged event is defined in the ViewUserControl base class. From a derived class, you can t fire an event defined on a base class.


To work around this issue you define a fire method on the base class; for instance, OnCustomerChanged. From the user control simply call this method and have the event fired (again, see Figure 2). The code here shows how the event is bubbled up to the page:


Sub DetailsView1_ItemUpdated(ByVal sender As Object, _

 ByVal e As DetailsViewUpdatedEventArgs) Handles _



End Sub


By handling the CustomerChanged event on the page, you are notified of updates in the customer-view panel. The simplest way to keep grid and details views in sync is rebinding data:




To finish, you might want to add a bit of script code to provide a message box to the user to confirm the success of the update operation. You do this by registering a start-up script:


Sub RegisterNotifyScript()

 Dim js As String = "alert('Contact updated successfully');"

 Page.ClientScript.RegisterStartupScript(Me.GetType(), _

  "RegisterNotifyScript", js, True)

End Sub


Registered with the page in the handler of the CustomerChanged event, the script runs as the page is reloaded after the update. From a user s perspective, the effect is like that of a confirmation message box in a Windows application.



User controls are simple user interface elements designed to make ASP.NET programming comfortable and effective. Putting user controls to work is relatively easy. But using them effectively in a context that combines extensibility, reusability, and programming power is not so trivial. This article described consolidated, widely accepted solutions to a few common issues.


The source code accompanying this article is available for download.


Dino Esposito is a Solid Quality Learning mentor and the author of Introducing ASP.NET 2.0 AJAX Extensions and Programming Microsoft ASP.NET 2.0-Core Reference from Microsoft Press. Based in Italy, Dino is a frequent speaker at industry events worldwide. Join the blog at http://weblogs.asp.net/despos.




Hide comments


  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.