Web User Controls: Beyond the Basics

Make User Controls Interact with the Host Page

Dino Esposito

October 30, 2009

11 Min Read
ITPro Today logo

CoreCoder

LANGUAGES: VB.NET

ASP.NETVERSIONS: 2.0

 

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 ofserver controls. In addition to in-box controls, such as TextBox and GridView,you can create and use your own controls. Classic extended server controls areclasses derived from a base control class; Web user controls are sort ofpagelets and result from the visual aggregation of a bunch of existing built-inand custom server controls. You create a Web user control in much the same wayyou create an ASP.NET page; you can employ it inside host pages as if it were aclassic server control. Put another way, a Web user control is an embeddablepage with a bolted-on object model for further programmability. A Web usercontrol is saved to a file with the .ascx extension and, much like the ASP.NETpage, can refer to a separate code-behind class or include inline server code.

 

Web user controls should be familiar to most ASP.NETdevelopers they were introduced in the initial release of ASP.NET. However,using such controls properly and effectively with an ASP.NET page requiresfamiliarity with a number of techniques and practices. I bet that all ASP.NETdevelopers know how to embed a user control in a page. But what aboutsynchronizing 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 scenariowhere user controls are employed and discuss how to implement a basicdocument-view model in which the user control plays the role of the view andany user control with given characteristics can successfully interact with thepage. To start, let s say more about the context.

 

The Typical Scenario

You typically use a Web user control to refer a collectionof user interface elements that logically form a single block. Imagine that youneed 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 Figure1. As you can see, the page features a grid to list contacts and a genericpanel where you display additional information. I believe that 90% of ASP.NETdevelopers would seriously consider using a Web user control to implement thepanel. Such a Web user control has a clear dependency on the remainder of thepage. In fact, the user control is asked to display details about the selectedcustomer. The ID of the customer is a piece of information that belongs to theASPX 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 includeediting capabilities, you might need to refresh the host page to reflectchanges. Look again at Figure 1 and imagine, for example, that the user controlupdates the contact name. At this point, you need to refresh the grid to havethe new name displayed. In the end, the new information, or at least anotification of the change, must flow from the user control up to the hostpage.

 


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

 

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

 

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

 Src="myusercontrol.ascx" %>

 

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

 

Loading User Controls Dynamically

The Page class provides a method to load a user controlprogrammatically. You read the URL of the user control from a known location forexample, the Web.config file and invoke the LoadControl method of the pageclass. 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 thepage is stored in the section of the configuration file:

 

   

 

By editing the Web.config file you actually modify the appearanceand possibly the behavior of the page. When you statically link a user controlto a page, and the page compiles, you re safe about your code. Any interactionbetween 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 anASCX from the configuration file, you don t know much about it. You can onlyload it as an instance of the UserControl class and, in doing so, you lose allthe additional object model and functionality specific to the control.

 

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

 

Imports Microsoft.VisualBasic

Public Interface ICustomerView

 Sub SetCustomerID(ByValcustomerID As String)

 Event CustomerChanged AsEventHandler(Of CustomerEventArgs)

 Sub OnCustomerChanged()

End Interface

Public Class ViewUserControl : Inherits UserControl :

 Implements ICustomerView

 Private _customerID AsString = String.Empty

 Public ReadOnly Property CustomerID()As String

     Get

         Return_customerID

     End Get

 End Property

 Public EventCustomerChanged As EventHandler _

   (Of CustomerEventArgs)

        ImplementsICustomerView.CustomerChanged

 Public SubOnCustomerChanged() _

        Implements ICustomerView.OnCustomerChanged

     Dim args As NewCustomerEventArgs

     args.CustomerID =CustomerID

     RaiseEventCustomerChanged(Me, args)

 End Sub

 Public SubSetCustomerID(ByVal customerID As String) _

        Implements ICustomerView.SetCustomerID

     _customerID =customerID

     If _customerID IsNothing Then

         ClearUI()

     Else

         UpdateUI()

     End If

 End Sub

 Protected Overridable SubUpdateUI()

 End Sub

 Protected Overridable SubClearUI()

 End Sub

End Class

Figure 2: Definingthe contract for the user control.

 

The ICustomerView interface defines the CustomerChangedevent that any compatible user control will fire to the page if the contentsshown in the control get updated at some point. The SetCustomerID method initializesthe user control by passing the ID of the selected customer. TheOnCustomerChanged method is required for structural reasons that I ll discussin a moment.

 

At this point you can choose to implement the interfacedirectly in each user control or, more elegantly, create a base class thatimplements the interface: the ViewUserControl class. Actual Web user controlswill then derive from this base class. The base class can abstract the contractof the interface to the extent that it is possible and desired. For example, Iadded two overridable methods to the ViewUserControl class: UpdateUI andClearUI. They get called as soon as the document information is passed to theuser control. The code snippet in Figure 3 shows the template of thecode-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 SubUpdateUI()

       MyBase.UpdateUI()

       Label1.Text =CustomerID

   End Sub

   Protected Overrides SubClearUI()

       MyBase.ClearUI()

       Label1.Text =String.Empty

   End Sub

End Class

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

 

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

 

Passing Data to the Control

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

 

Imports System.Web.Configuration

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

 Private view AsViewUserControl

 Protected SubPage_Load(ByVal sender As Object, _

  ByVal e AsSystem.EventArgs) Handles Me.Load

     Dim ascx As String =String.Empty

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

     PlaceHolder1.Controls.Clear()

     If NotString.IsNullOrEmpty(ascx) Then

        view =Page.LoadControl(ascx)

         AddHandlerview.CustomerChanged, _

           AddressOf RefreshGrid

         PlaceHolder1.Controls.Add(view)

     Else

         Dim lit As NewLiteralControl("No view available.")

         PlaceHolder1.Controls.Add(lit)

     End If

 End Sub

 Protected SubGridView1_PageIndexChanged(ByVal sender _

  As Object, ByVal e AsSystem.EventArgs) _

  HandlesGridView1.PageIndexChanged

     GridView1.SelectedIndex = -1

     view.SetCustomerID(Nothing)

 End Sub

 Protected SubGridView1_SelectedIndexChanged(ByVal _

  sender As Object, ByVale As EventArgs) _

  HandlesGridView1.SelectedIndexChanged

     Dim id As String =GridView1.SelectedDataKey.Value

     view.SetCustomerID(id)

 End Sub

End Class

Figure 4: A hostpage that loads Web user controls dynamically.

 

In the Page_Load event, determine the URL of the usercontrol and load it in the assigned place. Next, the user control must beinitialized with the ID of the selected customer. The SelectedIndexChangedevent 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

view.SetCustomerID(id)

 

By contract, the user control has a SetCustomerID methodthat the page uses to pass information. Needless to say, you shouldprogrammatically ensure that the user control implements the required interfaceand 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 implementationof the particular control. Let s consider a more realistic user control thatuses a DetailsView control to present and edit information.

 

You can bind data to a DetailsView in either of two ways: usinga data source object or specifying a data source control. It is not widely knownthat a DetailsView control doesn t work in edit or insert mode if bound to adata source object. The control throws an exception as the user clicks toswitch to edit mode. The description is not particularly clear, but essentiallyit is because of the lack of a data source ID. In the end you have two bindingoptions for a read-only DetailsView; you have only one for a read/writeDetailsView. Let s go for a SqlDataSource object then.

 

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

 

Protected Overrides Sub UpdateUI()

 MyBase.UpdateUI()

 SqlDataSource1.SelectParameters.Clear()

 Dim p As NewParameter("customerid", _

  TypeCode.String,CustomerID)

 SqlDataSource1.SelectParameters.Add(p)

 DetailsView1.DataSourceID= "SqlDataSource1"

 DetailsView1.DataBind()

End Sub

 

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

 


Figure 5: An ASP.NET page and a usercontrol synchronized.

 

Notifying Changes

The DetailsView control also allows users to edit thecontents displayed. When this happens, the subsequent user interface isobviously up to date, but only inside the DetailsView control. What about thepage? 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 gridfrom where you make your selection will show the old name. This is a clearsynchronization 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, andrethrow it under a new name for example, CustomerChanged. In ourimplementation, the CustomerChanged event is defined in the ViewUserControlbase class. From a derived class, you can t fire an event defined on a baseclass.

 

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

 

Sub DetailsView1_ItemUpdated(ByVal sender As Object, _

 ByVal e AsDetailsViewUpdatedEventArgs) Handles _

 DetailsView1.ItemUpdated

 MyBase.OnCustomerChanged()

End Sub

 

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

 

GridView1.DataBind()

 

To finish, you might want to add a bit of script code toprovide a message box to the user to confirm the success of the updateoperation. 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 theCustomerChanged event, the script runs as the page is reloaded after theupdate. From a user s perspective, the effect is like that of a confirmationmessage box in a Windows application.

 

Conclusion

User controls are simple user interface elements designedto make ASP.NET programming comfortable and effective. Putting user controls towork is relatively easy. But using them effectively in a context that combinesextensibility, reusability, and programming power is not so trivial. Thisarticle described consolidated, widely accepted solutions to a few commonissues.

 

The source code accompanyingthis article is available for download.

 

Dino Esposito is aSolid Quality Learning mentor and the author of IntroducingASP.NET 2.0 AJAX Extensions and ProgrammingMicrosoft 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.

 

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like