Chapter 7. Creating Custom Controls with User Controls

<feature><title>In this Chapter</title> <objective>

Creating User Controls

</objective>
<objective>

AJAX and User Controls

</objective>
<objective>

Dynamically Loading User Controls

</objective>
<objective>

Summary

</objective>
</feature>

A Web User control enables you to build a new control from existing controls. By taking advantage of User controls, you can easily extend the ASP.NET Framework with your own custom controls.

Imagine, for example, that you need to display the same address form in multiple pages in a web application. The address form consists of several TextBox and Validation controls for entering address information. If you want to avoid declaring all the TextBox and Validation controls in multiple pages, you can wrap these controls inside a Web User control.

Anytime that you discover that you need to display the same user interface elements in multiple pages, you should consider wrapping the elements inside a User control. By taking advantage of User controls, you make your website easier to maintain and extend.

In this chapter, you learn how to build custom controls with User controls. It starts with the basics. You learn how to create a simple User control and expose properties and events from the User control.

You then examine how you can use AJAX with a User control. You learn how to modify the content displayed by a User control without posting the page that contains the User control back to the web server.

Finally, you learn how you can load User controls dynamically. You learn how to load a User control at runtime and inject the User control into a page. In the final section of this chapter, dynamically loaded User controls are used to build a multi-page wizard.

Creating User Controls

Let’s start by building a simple User control that randomly displays one image from a folder of images (see Figure 7.1). The code for the User control is contained in Listing 7.1.

Displaying an image with the RandomImage User control.

Figure 7.1. Displaying an image with the RandomImage User control.

Example 7.1. RandomImage.ascx

<%@ Control Language="VB" ClassName="RandomImage" %>
<%@ Import Namespace="System.IO" %>

<script runat="server">

    Private Sub Page_Load()
        Dim imageToDisplay As String = GetRandomImage()
        imgRandom.ImageUrl = Path.Combine("~/Images", imageToDisplay)
        lblRandom.Text = imageToDisplay
    End Sub

    Private Function GetRandomImage() As String
        Dim rnd As New Random()
        Dim images() As String = Directory.GetFiles(MapPath("~/Images"), "*.jpg")
        Dim imageToDisplay As String = images(rnd.Next(images.Length))
        Return Path.GetFileName(imageToDisplay)
    End Function
</script>

<asp:Image
    id="imgRandom"
    Width="300px"
    Runat="server" />
<br />
<asp:Label
    id="lblRandom"
    Runat="server" />

Visual Web Developer Note

You create a new User control in Visual Web Developer by selecting Website, Add New Item, and selecting the Web User control item.

The file in Listing 7.1 closely resembles a standard ASP.NET page. Like a standard ASP.NET page, the User control contains a Page_Load() event handler. Also, the User control contains standard controls such as the ASP.NET Image and Label controls.

User controls are closely related to ASP.NET pages. Both the UserControl class and the Page class derive from the base TemplateControl class. Because they derive from the same base class, they share many of the same methods, properties, and events.

The important difference between an ASP.NET page and a User control is that a User control is something you can declare in an ASP.NET page. When you build a User control, you are building a custom control.

Notice that the file in Listing 7.1 ends with the .ascx extension. You cannot request this file directly from a web browser. To use the RandomImage User control, you must declare the control in an ASP.NET page.

The page in Listing 7.2 contains the RandomImage User control. When you open the page, a random image is displayed.

Example 7.2. ShowRandomImage.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="RandomImage"
    Src="~/UserControls/RandomImage.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show RandomImage</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <user:RandomImage
        ID="RandomImage1"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Before you can use a web User control in a page, you must register it. The page in Listing 7.2 includes a <%@ Register %> directive that contains the following three attributes:

  • TagPrefixIndicates the namespace that you want to associate with the User control for the current page. You can use any string that you want.

  • TagNameIndicates the name that you want to associate with the User control for the current page. You can use any string that you want.

  • SrcIndicates the virtual path to the User control (the path to the .ascx file)

The RandomImage User control is declared in the body of the page. It looks like this:

<user:RandomImage ID="RandomImage1" Runat="Server" />

Notice that the declaration of the User control uses the TagPrefix and TagName specified in the <%@ Register %> directive. Furthermore, notice that you provide a User control with both an ID and a Runat attribute, just as you would for any standard ASP.NET control.

Visual Web Developer Note

You can add a User control to a page in Visual Web Developer simply by dragging the User control from the Solution Explorer window onto the Design surface. The <%@ Register %> directive is automatically added to the source of the page.

Registering User Controls in the Web Configuration File

As an alternative to registering a User control in each page in which you need to use it by using the <%@ Register %> directive, you can register a User control once for an entire application. You can register a User control in an application’s web configuration file.

For example, the web configuration file in Listing 7.3 registers the RandomImage control for the application.

Example 7.3. Web.Config

<?xml version="1.0"?>
<configuration>
<system.web>
  <pages>
    <controls>
      <add
        tagPrefix="user"
        tagName="RandomImage"
        src="~/UserControls/RandomImage.ascx"/>
    </controls>
  </pages>
</system.web>
</configuration>

After you register a User control in the web configuration file, you can simply declare the User control in any page. For example, the page in Listing 7.4 contains an instance of the RandomImage User control, but it does not include the <%@ Register %> directive.

Example 7.4. ShowAppRegister.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Application Register</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <user:RandomImage
        ID="RandomImage1"
        Runat="Server" />

    </div>
    </form>
</body>
</html>

You need to be aware of one important limitation when registering a User control in the web configuration file. A User control cannot be located in the same folder as a page that uses it. For that reason, you should create all your User controls in a subfolder (I typically create a UserControls subfolder for each of my applications).

Exposing Properties from a User Control

The RandomImage User control always displays an image from the Images folder. It would be nice if you could specify the name of the folder that contains the images so that you could use different folder paths in different applications. You can do this by exposing a property from the RandomImage User control.

The modified RandomImage control in Listing 7.5, named PropertyRandomImage, exposes a property named ImageFolderPath.

Example 7.5. PropertyRandomImage.ascx

<%@ Control Language="VB" ClassName="PropertyRandomImage" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">

    Private _imageFolderPath As String = "~/Images"

    Public Property ImageFolderPath() As String
        Get
            Return _imageFolderPath
        End Get
        Set(ByVal Value As String)
            _imageFolderPath = value
        End Set
    End Property

    Private Sub Page_Load()
        Dim imageToDisplay As String = GetRandomImage()
        imgRandom.ImageUrl = Path.Combine(_imageFolderPath, imageToDisplay)
        lblRandom.Text = imageToDisplay
    End Sub

    Private Function GetRandomImage() As String
        Dim rnd As New Random()
        Dim images() As String = Directory.GetFiles(MapPath("~/Images"), "*.jpg")
        Dim imageToDisplay As String = images(rnd.Next(images.Length))
        Return Path.GetFileName(imageToDisplay)
    End Function
</script>

<asp:Image
    id="imgRandom"
    Width="300px"
    Runat="server" />
<br />
<asp:Label
    id="lblRandom"
    Runat="server" />

After you expose a property in a User control, you can set the property either declaratively or programmatically. The page in Listing 7.6 sets the ImageFolderPath property declaratively.

Example 7.6. ShowDeclarative.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="PropertyRandomImage"
 Src="~/PropertyRandomImage.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Declarative</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <user:PropertyRandomImage
        ID="PropertyRandomImage1"
        ImageFolderPath="~/Images2"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Notice that the PropertyRandomImage User control in Listing 7.6 includes an ImageFolderPath property. When you request the page, the random images are retrieved from the Images2 folder.

Visual Web Developer Note

Any properties that you add to a User control appear in both Intellisense and the Property window.

The page in Listing 7.7 demonstrates how you can set the ImageFolderPath programmatically.

Example 7.7. ShowProgrammatic.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="PropertyRandomImage"
    Src="~/PropertyRandomImage.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Protected  Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
        PropertyRandomImage1.ImageFolderPath = "~/Images2"
    End Sub

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Programmatic</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <user:PropertyRandomImage
        ID="PropertyRandomImage1"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 7.7 includes a Page_Load() event handler. This handler programmatically sets the ImageFolderPath to the value Images2.

Exposing Events from a User Control

You can expose custom events from a User control. After you expose the event, you can handle the event in the page that contains the User control.

Exposing events is useful when you need to pass information up to the containing page. Imagine, for example, that you want to create a custom tab strip with a User control. When a user clicks a tab, you want to change the content displayed in the page (see Figure 7.2).

Displaying a tab strip with a User control.

Figure 7.2. Displaying a tab strip with a User control.

The User control in Listing 7.8 contains the code for a simple tab strip.

Example 7.8. TabStrip.ascx

<%@ Control Language="VB" ClassName="TabStrip" %>
<%@ Import Namespace="System.Collections.Generic" %>
<script runat="server">

    Public Event TabClick As EventHandler

    ''' <summary>
    ''' The index of the selected tab
    ''' </summary>
    Public ReadOnly Property SelectedIndex() As Integer
        Get
            Return dlstTabStrip.SelectedIndex
        End Get
    End Property

    ''' <summary>
    ''' Create the tabs
    ''' </summary>
    Private Sub Page_Load()
        If Not Page.IsPostBack Then
            ' Create the tabs
            Dim tabs As New List(Of String)()
            tabs.Add("Products")
            tabs.Add("Services")
            tabs.Add("About")

            ' Bind tabs to the DataList
            dlstTabStrip.DataSource = tabs
            dlstTabStrip.DataBind()

            ' Select first tab
            dlstTabStrip.SelectedIndex = 0
        End If
    End Sub

    ''' <summary>
    ''' This method executes when a user clicks a tab
    ''' </summary>
    Protected Sub dlstTabStrip_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
        RaiseEvent TabClick(Me, EventArgs.Empty)
    End Sub

</script>

<asp:DataList
    id="dlstTabStrip"
    RepeatDirection="Horizontal"
    OnSelectedIndexChanged="dlstTabStrip_SelectedIndexChanged"
    CssClass="tabs"
    ItemStyle-CssClass="tab"
    SelectedItemStyle-CssClass="selectedTab"
    Runat="server">
    <ItemTemplate>
    <asp:LinkButton
        id="lnkTab"
        Text='<%# Container.DataItem %>'
        CommandName="Select"
        Runat="server" />
     </ItemTemplate>
 </asp:DataList>

The tab strip is created with the help of a DataList control. The DataList control displays links for each of the items created in the Page_Load() event handler.

Notice that the TabStrip control exposes an event named TabClick. This event is raised in the dlstTabStrip_SelectedIndexChanged() event handler when a user clicks a tab.

The page in Listing 7.9 uses the TabStrip control to display different content depending on the tab selected.

Example 7.9. ShowTabStrip.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="TabStrip" Src="~/TabStrip.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Protected Sub TabStrip1_TabClick(ByVal sender As Object, ByVal e As EventArgs)
        MultiView1.ActiveViewIndex = TabStrip1.SelectedIndex
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        html
        {
            background-color:silver;
            font:14px Georgia,Serif;
        }
        .tabs a
        {
            color:blue;
            text-decoration:none;
            font:14px Arial,Sans-Serif;
        }
        .tab
        {
            background-color:#eeeeee;
            padding:5px;
            border:Solid 1px black;
            border-bottom:none;
        }
        .selectedTab
        {
            background-color:white;
            padding:5px;
            border:Solid 1px black;
            border-bottom:none;
        }
        .views
        {
            background-color:white;
            width:400px;
            border:Solid 1px black;
            padding:10px;
        }
    </style>
    <title>Show TabStrip</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <user:TabStrip
        ID="TabStrip1"
        OnTabClick="TabStrip1_TabClick"
        Runat="Server" />

    <div class="views">
    <asp:MultiView
        id="MultiView1"
        ActiveViewIndex="0"
        Runat="server">
        <asp:View ID="Products" runat="server">
            <h1>Products</h1>
            We sell a variety of useful products...
        </asp:View>
        <asp:View ID="Services" runat="server">
            <h1>Services</h1>
            We offer a number of services...
        </asp:View>
        <asp:View ID="About" runat="server">
            <h1>About</h1>
            We were the first company to offer products and services...
        </asp:View>
    </asp:MultiView>
    </div>

    </div>
    </form>
</body>
</html>

The page in Listing 7.9 includes an event handler for the TabStrip control’s TabClick event. When you click a tab, the index of the selected tab is retrieved from the tab strip, and the View control with the matching index is displayed.

Visual Web Developer Note

You can add a TabClick event handler to the TabStrip control by selecting the TabStrip control from the top-left drop-down list and selecting the TabClick event from the top-right drop-down list.

Note

The ASP.NET Framework includes a Menu control that you can use to create both tabstrips and pop-up menus. This control is discussed in Chapter 4, “Using the Rich Controls,” and Chapter 17, “Using the Navigation Controls.”

Creating an AddressForm Control

Let’s end this section by creating a generally useful Web User control. We’ll build an AddressForm User control that you can reuse in multiple pages or reuse multiple times in a single page (see Figure 7.3).

Displaying multiple address forms with the AddressForm User control.

Figure 7.3. Displaying multiple address forms with the AddressForm User control.

The AddressForm User control is contained in Listing 7.10.

Example 7.10. AddressForm.ascx

<%@ Control Language="VB" ClassName="AddressForm" %>
<script runat="server">

    Public Property Title() As String
        Get
                Return ltlTitle.Text
        End Get
        Set (ByVal Value As String)
                 ltlTitle.Text = value
        End Set
    End Property

    Public Property Street() As String
        Get
                 Return txtStreet.Text
        End Get
        Set (ByVal Value As String)
                 txtStreet.Text = value
        End Set
    End Property

    Public Property City() As String
        Get
                 Return txtCity.Text
        End Get
        Set (ByVal Value As String)
                 txtCity.Text = value
        End Set
    End Property

    Public Property State() As String
        Get
                 Return txtState.Text
        End Get
        Set (ByVal Value As String)
                 txtState.Text = value
        End Set
    End Property

    Public Property PostalCode() As String
        Get
                 Return txtPostalCode.Text
        End Get
        Set (ByVal Value As String)
                 txtPostalCode.Text = value
        End Set
    End Property

</script>

<fieldset>
<legend>
    <asp:Literal
        ID="ltlTitle"
        Text="Address Form"
        runat="server" />
</legend>

<div class="addressLabel">
<asp:Label
    ID="lblStreet"
    Text="Street:"
    AssociatedControlID="txtStreet"
    Runat="server" />
</div>
<div class="addressField">
<asp:TextBox
    ID="txtStreet"
    Runat="server" />
<asp:RequiredFieldValidator
    ID="reqStreet"
    Text="(required)"
    ControlToValidate="txtStreet"
    Runat="server" />
</div>

<br class="clear" />

<div class="addressLabel">
<asp:Label
    ID="lblCity"
    Text="City:"
    AssociatedControlID="txtCity"
    Runat="server" />
</div>
<div class="addressField">
<asp:TextBox
    ID="txtCity"
    Runat="server" />
<asp:RequiredFieldValidator
    ID="reqCity"
    Text="(required)"
    ControlToValidate="txtCity"
    Runat="server" />
</div>

<br class="clear" />

<div class="addressLabel">
<asp:Label
    ID="lblState"
    Text="State:"
    AssociatedControlID="txtState"
    Runat="server" />
</div>
<div class="addressField">
<asp:TextBox
    ID="txtState"
    Runat="server" />
<asp:RequiredFieldValidator
    ID="reqState"
    Text="(required)"
    ControlToValidate="txtState"
    Runat="server" />
</div>

<br class="clear" />

<div class="addressLabel">
<asp:Label
    ID="lblPostalCode"
    Text="Postal Code:"
    AssociatedControlID="txtPostalCode"
    Runat="server" />
</div>
<div class="addressField">
<asp:TextBox
    ID="txtPostalCode"
    Runat="server" />
<asp:RequiredFieldValidator
    ID="RequiredFieldValidator1"
    Text="(required)"
    ControlToValidate="txtPostalCode"
    Runat="server" />
</div>

<br class="clear" />

</fieldset>

The AddressForm control contains form controls for entering your street, city, state, and postal code. Each of these fields is validated by a RequiredFieldValidator control. Finally, the AddressForm includes a Label that can be used to provide a title for the control.

The AddressForm exposes all of its form fields with properties. The control includes public Street, City, State, and PostalCode property, which you can read from the containing page.

The page in Listing 7.11 illustrates how you can use the AddressForm control in a page.

Example 7.11. Checkout.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="AddressForm" Src="~/AddressForm.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs)
        ' Show Billing Address Form Results
        ltlResults.Text = "<br />Billing Street: " + AddressForm1.Street
        ltlResults.Text += "<br />Billing City: " + AddressForm1.City
        ltlResults.Text += "<br />Billing State: " + AddressForm1.State
        ltlResults.Text += "<br />Billing Postal Code: " + AddressForm1.PostalCode

        ltlResults.Text += "<br /><br />"

        ' Show Shipping Address Form Results
        ltlResults.Text += "<br />Shipping Street: " + AddressForm2.Street
        ltlResults.Text += "<br />Shipping City: " + AddressForm2.City
        ltlResults.Text += "<br />Shipping State: " + AddressForm2.State
        ltlResults.Text += "<br />Shipping Postal Code: " + AddressForm2.PostalCode
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        html
        {
            background-color:silver;
            font:14px Georgia,Serif;
        }
        .content
        {
            background-color:white;
            width:600px;
            margin:auto;
            padding:20px;
        }
        .addressLabel
        {
            float:left;
            width:100px;
            padding:5px;
            text-align:right;
        }
        .addressField
        {
            float:left;
            padding:5px;
        }
        .clear
        {
            clear:both;
        }

    </style>
    <title>Checkout</title>
</head>
<body>
    <form id="form1" runat="server">
    <div class="content">

    <user:AddressForm
        id="AddressForm1"
        Title="Billing Address"
        Runat="server" />

    <br />

    <user:AddressForm
        id="AddressForm2"
        Title="Shipping Address"
        Runat="server" />

    <br />

    <asp:Button
        ID="btnSubmit"
        Text="Submit Form"
        OnClick="btnSubmit_Click"
        Runat="server" />

    <hr />

    <asp:Literal
        id="ltlResults"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 7.11 contains two instances of the AddressForm control: a Billing Address and Shipping Address. When you click the Button control, the address information is retrieved from the AddressForm controls and displayed in a Literal control. (In a real application, you would grab the data and store it in a database.)

Web Standards Note

The AddressForm User control does not use an HTML table to layout its controls. You should strive to avoid using tables except when displaying tabular information. Instead, Cascading Style Sheet rules are used to position the form elements. The page looks almost identical in Internet Explorer 6, Firefox 1.0, and Opera 8.0.

AJAX and User Controls

AJAX (Asynchronous JavaScript and XML) enables you to update content in a page without posting the page back to the server. Behind the scenes, AJAX uses the XMLHttp ActiveX component (in the case of Microsoft Internet Explorer) or the XMLHttpRequest intrinsic browser object (in the case of other browsers such as FireFox).

In the ASP.NET Framework, AJAX is referred to as client callbacks. To add AJAX support to a User control, you must implement the ICallBackEventHandler interface and add the necessary JavaScript scripts to process the results of the AJAX call.

Here are the steps for implementing AJAX:

  1. Create a client script for invoking the AJAX call. You can get this script with the Page.ClientScript.GetCallbackEventReference() method.

  2. Create server methods named RaiseCallbackEvent() and GetCallbackResult(), which returns a string value from the server.

  3. Create a client method that receives the value from the server RaiseCallbackEvent() method and does something with the value.

For example, the User control in Listing 7.12 randomly displays one of three quotations. The quotation is updated automatically every 10 seconds (see Figure 7.4).

Using AJAX to display a random quotation.

Figure 7.4. Using AJAX to display a random quotation.

Example 7.12. RandomQuotation.ascx

<%@ Control Language="VB" ClassName="RandomQuotation" %>
<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>
<%@ Import Namespace="System.Collections.Generic" %>
<script runat="server">

    Public Sub RaiseCallbackEvent(ByVal result As String) Implements ICallbackEventHandler.RaiseCallbackEvent
    End Sub

    Public Function GetCallbackResult() As String Implements ICallbackEventHandler.GetCallbackResult
        Dim quotes As New List(Of String)()
        quotes.Add("All paid jobs absorb and degrade the mind -- Aristotle")
        quotes.Add("No evil can happen to a good man, either in life or after death -- Plato")
        quotes.Add("The only good is knowledge and the only evil is ignorance -- Plato")
        Dim rnd As Random = New Random()
        Return quotes(rnd.Next(quotes.Count))
    End Function

    Private Sub Page_Load()
        Dim callback As String = Page.ClientScript.GetCallbackEventReference(Me, Nothing, "UpdateQuote", Nothing, "CallbackError", True)
        Dim startupScript As String = String.Format("setInterval( ""{0}"", 5000 )", callback)
        Page.ClientScript.RegisterStartupScript(Me.GetType(), "RandomQuotation", startupScript, True)
    End Sub

</script>

<div id="divQuote" class="quote">
Random Quotation
</div>

<script type="text/javascript">

    function UpdateQuote(result)
    {
        var divQuote = document.getElementById('divQuote'),
        divQuote.innerText = result;
    }

    function CallbackError(result)
    {
        alert( result );
    }

 </script>

The Page_Load() method in Listing 7.12 generates the client script for invoking the AJAX call. A reference to the script that makes the AJAX call is retrieved from the Page.ClientScript.GetCallbackEventReference() method. The JavaScript setInterval() method is used to execute this script every five seconds.

The User control implements the ICallbackEventHandler interface. This interface has two methods that you must implement: RaiseCallbackEvent() and GetCallbackResult(). These two methods are called on the server in order. In Listing 7.12, the RaiseCallbackEvent() does nothing and the GetCallbackResult() method returns the random quotation to the client.

Finally, notice that the User control contains a client script block that contains two JavaScript functions. The first function, named UpdateQuote(), displays the random quotation returned by the RaiseCallbackEvent() in an HTML <div> tag. The second method, named CallbackError(), shows an alert dialog box when an error occurs during performance of the AJAX call.

The page in Listing 7.13 illustrates how you can use the RandomQuotation User control. It contains the User control and it also displays the current time.

Example 7.13. ShowRandomQuotation.aspx

<%@ Page Language="VB" %>
<%@ Register TagPrefix="user" TagName="RandomQuotation"
    Src="~/RandomQuotation.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .quote
        {
            width:200px;
            padding:20px;
            border:Dotted 2px orange;
            background-color:#eeeeee;
            font:16px Georgia,Serif;
        }
    </style>
    <title>Show Random Quotation</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <%= DateTime.Now %>
    <br />

    <user:RandomQuotation
        id="RandomQuotation1"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Notice that the random quotation is updated, but that the time on the page does not change. Only the area of the page that contains the random quotation is updated.

Dynamically Loading User Controls

You can dynamically load a User control at runtime and display it in a page. Imagine, for example, that you want to display different featured products randomly on the home page of your website. However, you want to display each featured product with a completely different layout. In that case, you can create a separate User control for each product and load one of the User controls randomly at runtime.

You load a User control with the Page.LoadControl() method. This method returns an instance of the Control class that you can add to a page. Typically, you add the User control to a PlaceHolder control that you have declared on the page.

Note

The PlaceHolder control was designed to do absolutely nothing. It simply acts as a placeholder on the page where you can add other controls.

For example, the page in Listing 7.14 randomly loads one of the controls from the FeaturedProducts folder and adds the control to the page.

Example 7.14. ShowFeaturedProduct.aspx

<%@ Page Language="VB" %>
<%@ Import Namespace="System.IO" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Const randomFolder As String = "~/FeaturedProducts"

    Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
        Dim featuredProductPath As String = GetRandomProductPath()
        Dim featuredProduct As Control = Page.LoadControl(featuredProductPath)
        PlaceHolder1.Controls.Add(featuredProduct)
    End Sub

    Private Function GetRandomProductPath() As String
        Dim rnd As New Random()
        Dim files() As String = Directory.GetFiles(MapPath(randomFolder),"*.ascx")
        Dim featuredProductPath As String =  Path.GetFileName(files(rnd.Next(files.Length)))
        Return Path.Combine(randomFolder,featuredProductPath)
    End Function

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Featured Products</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:PlaceHolder
        id="PlaceHolder1"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Using the Reference Directive

When you load a User control with the Page.LoadControl() method, the User control is returned as an instance of the System.Web.UI.Control class. This means that if the User control includes any custom properties, the properties aren’t available when you dynamically load the User control.

If you dynamically load a User control, then you need to cast the control to the correct type before you can access any of the control’s custom properties. To get a reference to a User control’s type, you must use the <%@ Reference %> directive.

For example, imagine that you need to create a form that displays different questions depending on the answers that a user provides for previous questions. In that case, you can dynamically load different User controls that contain the different sets of questions.

For example, the page in Listing 7.15 contains a survey form. The first question asks you whether you are currently using ASP Classic or ASP.NET. Depending on your answer, the remainder of the form displays different questions (see Figure 7.5).

Displaying a survey form with dynamically loaded questions.

Figure 7.5. Displaying a survey form with dynamically loaded questions.

Example 7.15. WebSurvey.aspx

<%@ Page Language="VB" %>
<%@ Reference Control="~/ASPSurvey.ascx" %>
<%@ Reference Control="~/ASPNetSurvey.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Private _survey As Control = Nothing

    Private Sub Page_Load()
        Select Case ddlLanguage.SelectedIndex
            Case 1
                _survey = Page.LoadControl("ASPSurvey.ascx")
            Case 2
                _survey = Page.LoadControl("ASPNetSurvey.ascx")
        End Select

        If Not IsNothing(_survey) Then
            PlaceHolder1.Controls.Add(_survey)
        End If
    End Sub

    Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs)
        Select Case ddlLanguage.SelectedIndex
            Case 1
                Dim aspResults As ASPSurvey = CType(_survey, ASPSurvey)
                ltlResults.Text = "<h1>ASP Survey</h1>"
                ltlResults.Text += "<br />Know slow? " + aspResults.KnowSlow.ToString()
                ltlResults.Text += "<br />Know outdated? " + aspResults.KnowOutdated.ToString()
            Case 2
                Dim aspNetResults As ASPNetSurvey = CType(_survey, ASPNetSurvey)
                ltlResults.Text = "<h1>ASP.NET Survey</h1>"
                ltlResults.Text += "<br />Know fast? " + aspNetResults.KnowFast.ToString()
                ltlResults.Text += "<br />Know newest? " + aspNetResults.KnowNewest.ToString()
        End Select
    End Sub

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        html
        {
            font:14px Arial,Sans-Serif;
        }
    </style>
    <title>Web Survey</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblLanguage"
        Text="What language do you use to develop Web applications?"
        Runat="server" />
    <br />
    <asp:DropDownList
        id="ddlLanguage"
        ToolTip="Web application language (reloads form)"
        AutoPostBack="true"
        Runat="server">
        <asp:ListItem Text="Select Language" />
        <asp:ListItem Text="ASP Classic"  />
        <asp:ListItem Text="ASP.NET" />
    </asp:DropDownList>

    <br /><br />

    <asp:PlaceHolder
        id="PlaceHolder1"
        Runat="server" />

    <asp:Button
        id="btnSubmit"
        Text="Submit"
        OnClick="btnSubmit_Click"
        Runat="server" />

    <hr />

    <asp:Literal
        id="ltlResults"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Web Standards Note

The DropDownList control in Listing 7.15 reloads the page automatically when you select a new option. You should never reload a page without warning the user because this can be very confusing for someone who is using an assistive device such as a screen reader. In Listing 7.15, a warning is added to the ToolTip property of the DropDownList control.

Depending on the user’s selection from the DropDownList control, one of two User controls is loaded in the Page_Load() event handler: the ASPSurvey.ascx or the ASPNetSurvey.ascx User control. These controls are contained in Listing 7.16 and Listing 7.17.

When you submit the survey form, the btnSubmit_Click() method executes. This method casts the User control loaded in the form to the correct type. It casts the User control to either the ASPSurvey or the ASPNetSurvey type.

Notice that the page in Listing 7.15 includes two <%@ Reference %> directives. These reference directives enable you to cast the User control to the correct type so that you can access custom properties of the control such as the KnowSlow and KnowOutdated properties.

Example 7.16. ASPSurvey.ascx

<%@ Control Language="VB" ClassName="ASPSurvey" %>
<script runat="server">

    Public ReadOnly Property KnowSlow() As Boolean
        Get
            Return chkSlow.Checked
        End Get
    End Property

    Public ReadOnly Property KnowOutdated() As Boolean
        Get
            Return chkOutdated.Checked
        End Get
    End Property
</script>

<asp:CheckBox
    id="chkSlow"
    Text="Did you know that ASP Classic is slow?"
    Runat="server" />

<br /><br />

<asp:CheckBox
    id="chkOutdated"
    Text="Did you know that ASP Classic is outdated?"
    Runat="server" />
<br /><br />

Example 7.17. ASPNetSurvey.ascx

<%@ Control Language="VB" ClassName="ASPNetSurvey" %>
<script runat="server">

    Public ReadOnly Property KnowFast() As Boolean
        Get
            Return chkFast.Checked
        End Get
    End Property

    Public ReadOnly Property KnowNewest() As Boolean
        Get
            Return chkNewest.Checked
        End Get
    End Property
</script>
<asp:CheckBox
    id="chkFast"
    Text="Did you know that ASP.NET is fast?"
    Runat="server" />

<br /><br />

<asp:CheckBox
    id="chkNewest"
    Text="Did you know that ASP.NET is the newest Microsoft
        technology for building Web applications?"
    Runat="server" />
<br /><br />

Creating a Multi-Page Wizard

This final section discusses how you can create a multi-page wizard by dynamically loading different user controls into the same page. This is going to be a complicated sample, but it is a realistic sample of situations when you would want to load User controls dynamically (see Figure 7.6).

Displaying a wizard with a series of User controls.

Figure 7.6. Displaying a wizard with a series of User controls.

Imagine that you must create a form with 200 questions in it. Displaying all 200 questions to a user in a single form would be overwhelming. Instead, it makes more sense to break the form into multiple pages. Each page of questions can be represented with a User control.

First, you need to define an interface, named the IWizardStep interface, which all the User controls will implement. An interface enables you to know, in advance, that a User control supports a particular set of properties or methods.

Note

You need to add the interface in Listing 7.18 to your application’s App_Code folder. In Visual Web Developer, create the interface by selecting Website, Add New Item, and select Class. Visual Web Developer prompts you to create the App_Code folder.

The IWizardStep interface is contained in Listing 7.18.

Example 7.18. IWizardStep.vb

Public Interface IWizardStep
    Sub LoadStep()
    Function NextStep() As Boolean
End Interface

The interface in Listing 7.18 contains two methods: LoadStep() and NextStep(). The LoadStep() method is called when a User control is first loaded. The NextStep() method is called when the Next button is clicked in the wizard.

Notice that the NextStep() method returns a Boolean value. If the NextStep() method returns the value False, then the user doesn’t advance to the next wizard step.

This wizard will consist of the three wizard steps contained in Listing 7.19, Listing 7.20, and Listing 7.21.

Example 7.19. WizardStepsStep1.ascx

<%@ Control Language="VB" ClassName="Step1" %>
<%@ Implements Interface="IWizardStep" %>
<script runat="server">

    Public Sub LoadStep() Implements IWizardStep.LoadStep
        If Not IsNothing(Session("FirstName")) Then
            txtFirstName.Text = CStr(Session("FirstName"))
        End If
        If Not IsNothing(Session("LastName")) Then
            txtLastName.Text = CStr(Session("LastName"))
        End If
    End Sub

    Public Function NextStep() As Boolean Implements IWizardStep.NextStep
        If Page.IsValid Then
            Session("FirstName") = txtFirstName.Text
            Session("LastName") = txtLastName.Text
            Return True
        End If
        Return False
    End Function
</script>
<h1>Step 1</h1>

<asp:Label
    id="lblFirstName"
    Text="First Name:"
    AssociatedControlID="txtFirstName"
    Runat="server" />
<asp:RequiredFieldValidator
    id="reqFirstName"
    Text="(required)"
    ControlToValidate="txtFirstName"
    Runat="server" />
<br />
<asp:TextBox
    id="txtFirstName"
    Runat="server" />

<br /><br />

<asp:Label
    id="lblLastName"
    Text="Last Name:"
    AssociatedControlID="txtLastName"
    Runat="server" />
<asp:RequiredFieldValidator
    id="reqLastName"
    Text="(required)"
    ControlToValidate="txtLastName"
    Runat="server" />
<br />
<asp:TextBox
    id="txtLastName"
    Runat="server" />

The wizard step in Listing 7.19 contains a simple form that contains Textbox controls for the user’s first and last name. Both TextBox controls are validated with RequiredFieldValidator controls.

Notice that the User control in Listing 7.19 implements the IWizardStep interface. It contains an <%@ Implements %> directive at the top of the control.

The LoadStep() method assigns values to the txtFirstName and txtLastName TextBox controls from Session state. The NextStep() method grabs the values from the txtFirstName and txtLastName TextBox controls and assigns the values to Session state.

The second step of the Wizard is contained in Listing 7.20.

Example 7.20. WizardStepsStep2.ascx

<%@ Control Language="VB" ClassName="Step2" %>
<%@ Implements Interface="IWizardStep" %>
<script runat="server">

    Public Sub LoadStep() Implements IWizardStep.LoadStep
        If Not IsNothing(Session("FavoriteColor")) Then
            txtFavoriteColor.Text = CStr(Session("FavoriteColor"))
        End If
    End Sub

    Public Function NextStep() As Boolean Implements IWizardStep.NextStep
        If Page.IsValid Then
            Session("FavoriteColor") = txtFavoriteColor.Text
            Return True
        End If
        Return False
    End Function
</script>

<h1>Step 2</h1>

<asp:Label
    id="lblFavoriteColor"
    Text="Favorite Color:"
    AssociatedControlID="txtFavoriteColor"
    Runat="server" />
<asp:RequiredFieldValidator
    id="reqFavoriteColor"
    Text="(required)"
    ControlToValidate="txtFavoriteColor"
    Runat="server" />
<br />
<asp:TextBox
    id="txtFavoriteColor"
    Runat="server" />

The User control in Listing 7.20 also implements the IWizardStep interface. In this step, the user enters a favorite color.

The final wizard step is contained in Listing 7.21.

Example 7.21. WizardStepsStep3.ascx

<%@ Control Language="VB" ClassName="Step3" %>
<%@ Implements Interface="IWizardStep" %>
<script runat="server">

    Public Sub LoadStep() Implements IWizardStep.LoadStep
        lblFirstName.Text = CStr(Session("FirstName"))
        lblLastName.Text = CStr(Session("LastName"))
        lblFavoriteColor.Text = CStr(Session("FavoriteColor"))
    End Sub

    Public Function NextStep() As Boolean Implements IWizardStep.NextStep
        Return False
    End Function
</script>

<h1>Step 3</h1>

First Name:
<asp:Label
    id="lblFirstName"
    Runat="server" />
<br />
Last Name:
<asp:Label
    id="lblLastName"
    Runat="server" />
<br />
Favorite Color:
<asp:Label
    id="lblFavoriteColor"
    Runat="server" />

The wizard step in Listing 7.21 displays a summary of answers that the user has provided in the first two wizard steps (see Figure 7.7). Notice that it also implements the IWizardStep interface. Because this is the final wizard step, the NextStep() method always returns the value False.

Displaying the wizard summary step.

Figure 7.7. Displaying the wizard summary step.

The page in Listing 7.22 contains the actual wizard. This page loads each of the wizard steps.

Example 7.22. Wizard.aspx

<%@ Page Language="VB" %>
<%@ Import Namespace="System.Collections.Generic" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Private _wizardSteps As New List(Of String)()
    Private _currentStep As Control

    ''' <summary>
    ''' The current step in the Wizard
    ''' </summary>
    Public Property StepIndex() As Integer
        Get
            If (IsNothing(ViewState("StepIndex"))) Then
                Return 0
            else
                Return CInt(ViewState("StepIndex"))
            End If
       End Get
       Set (ByVal Value As Integer)
            ViewState("StepIndex") = value
       End Set
    End Property

    ''' <summary>
    ''' Load the list of wizard steps and load
    ''' current step
    ''' </summary>
    Sub Page_Load()
        _wizardSteps.Add("~/WizardSteps/Step1.ascx")
        _wizardSteps.Add("~/WizardSteps/Step2.ascx")
        _wizardSteps.Add("~/WizardSteps/Step3.ascx")

        LoadWizardStep()
    End Sub

    ''' <summary>
    ''' Load the current wizard step
    ''' </summary>
    Private Sub LoadWizardStep()
        _currentStep = Page.LoadControl(_wizardSteps(StepIndex))
        _currentStep.ID = "ctlWizardStep"
        plhWizardStep.Controls.Clear()
        plhWizardStep.Controls.Add(_currentStep)
        CType(_currentStep, IWizardStep).LoadStep()
        ltlStep.Text = String.Format("Step {0} of {1}", StepIndex + 1, _wizardSteps.Count)
    End Sub

    ''' <summary>
    ''' Disable the Previous and Next
    ''' buttons when appropriate
    ''' </summary>
    Sub Page_PreRender()
        btnPrevious.Enabled = StepIndex > 0
        btnNext.Enabled = StepIndex < _wizardSteps.Count - 1
    End Sub

    ''' <summary>
    ''' Execute the step's NextStep() method
    ''' and move to the next step
    ''' </summary>
    Sub btnNext_Click(ByVal sender As Object, ByVal s As EventArgs)
        Dim success As Boolean = CType(_currentStep, IWizardStep).NextStep()
        If success Then
            If (StepIndex < _wizardSteps.Count - 1) Then
                StepIndex = StepIndex + 1
                LoadWizardStep()
            End If
        End If
    End Sub

    ''' <summary>
    ''' Move to the previous step
    ''' </summary>
    Sub btnPrevious_Click(ByVal sender As Object, ByVal e As EventArgs)
        If StepIndex > 0 Then
            StepIndex = StepIndex - 1
            LoadWizardStep()
        End If
    End Sub

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        html
        {
            font:14px Georgia,Serif;
        }
        fieldset
        {
            display:block;
            width:600px;
            padding:20px;
            margin:10px;
        }
    </style>
    <title>Wizard</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblStepNumber"
        Runat="server" />

    <fieldset>
    <legend><asp:Literal ID="ltlStep" runat="server" /></legend>
        <asp:PlaceHolder
            id="plhWizardStep"
            Runat="server" />
    </fieldset>

    <asp:Button
        id="btnPrevious"
        Text="&lt; Previous"
        CausesValidation="false"
        OnClick="btnPrevious_Click"
        Runat="server" />

    <asp:Button
        id="btnNext"
        Text="Next &gt;"
        OnClick="btnNext_Click"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The list of wizard steps is created in the Page_Load() method. The path to each wizard step User control is added to a collection of wizard steps.

The StepIndex property represents the index of the wizard step to display. Notice that the value of this property is stored in ViewState so that the value is available across multiple page requests.

The current wizard step is loaded by the LoadWizardStep() method. This method uses the StepIndex to grab the path to the current wizard step. Next, it uses the Page.LoadControl() method to actually load the wizard step User control.

After the LoadWizardStep() method loads the current wizard step, it calls the control’s LoadStep() method and initializes the control.

The page also contains a Previous and Next button. When you click the Previous button, the btnPrevious_Click() method is called and the StepIndex is reduced by one. When you click the Next button, the btnNext_Click() method is called.

The btnNext_Click() method first calls the current wizard step’s NextStep() method. If this method returns the value True, then one is added to the StepIndex property and the next wizard step is loaded. Otherwise, if the NextStep() method returns false, the next wizard step is not loaded.

Summary

In this chapter, you learned how to build custom controls by creating User controls. The first section covered the basics of User controls. You learned how to create a User control and register it both in a page and in a Web configuration file. You learned how to add custom properties and events to a User control.

You also explored the topic of AJAX and User controls. You learned how to update content in a User control without posting the page that contains the User control back to the web server.

Finally, you learned how to add User controls dynamically to a page. You learned how to use the <%@ Reference %> directive to cast a User control to a particular type. You also learned how to create a multi-page wizard by dynamically loading a series of User controls.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset