Composite Web Control Example

This example shows how to create a control named Register that demonstrates the main steps required to implement a composite ASP.NET server control. A composite control uses child controls to create a user interface (UI) and to perform other logic. Since a composite control relies on child controls for its functionality, it is significantly easier to develop a composite control than to implement all of the control's functionality yourself.

In this example, the Register control uses child controls to create a user interface (UI) for entering user information to register with a Web site. The UI consists of two TextBox controls, one for entering the user's name and another to enter the user's e-mail address, and a Button control to submit the information. Register also associates RequiredFieldValidator controls with the two TextBox controls to be sure that the user enters a name and an e-mail address. The Click event of the Button control is raised as the Register control's Submit event.

The Register control relies on the built-in features of the child controls for its behavior. For example, Register relies on the TextBox controls for handling postback data, the Button control for the postback event, and the RequiredFieldValidator controls for validation.

The code for the Register control is described in the "Code Discussion" section later in this topic. When you examine the code for the Register control, you will notice that most of the code relates to creating and managing the child controls.

Code Listing for the Register Control

' Register.vbOption Strict OnImports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace Samples.AspNet.VB.Controls
    < _
    AspNetHostingPermission(SecurityAction.Demand, _
        Level:=AspNetHostingPermissionLevel.Minimal), _
    AspNetHostingPermission(SecurityAction.InheritanceDemand, _
        Level:=AspNetHostingPermissionLevel.Minimal), _
    DefaultEvent("Submit"), _
    DefaultProperty("ButtonText"), _
    ToolboxData("<{0}:Register runat=""server""> </{0}:Register>") _
    > _
    PublicClass Register
        Inherits CompositeControl

        Private submitButton As Button
        Private nameTextBox As TextBox
        Private nameLabel As Label
        Private emailTextBox As TextBox
        Private emailLabel As Label
        Private emailValidator As RequiredFieldValidator
        Private nameValidator As RequiredFieldValidator

        PrivateSharedReadOnly EventSubmitKey AsNewObject()

        ' The following properties are delegated to child controls.
        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The text to display on the button.") _
        > _
        PublicProperty ButtonText() AsStringGet
                EnsureChildControls()
                Return submitButton.Text
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                submitButton.Text = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Default"), _
        DefaultValue(""), _
        Description("The user name.") _
        > _
        PublicProperty Name() AsStringGet
                EnsureChildControls()
                Return nameTextBox.Text
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                nameTextBox.Text = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("Error message for the name validator.") _
        > _
        PublicProperty NameErrorMessage() AsStringGet
                EnsureChildControls()
                Return nameValidator.ErrorMessage
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                nameValidator.ErrorMessage = value
                nameValidator.ToolTip = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The text for the name label.") _
        > _
        PublicProperty NameLabelText() AsStringGet
                EnsureChildControls()
                Return nameLabel.Text
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                nameLabel.Text = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Default"), _
        DefaultValue(""), _
        Description("The e-mail address.") _
        > _
        PublicProperty Email() AsStringGet
                EnsureChildControls()
                Return emailTextBox.Text
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                emailTextBox.Text = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("Error message for the e-mail validator.") _
        > _
        PublicProperty EmailErrorMessage() AsStringGet
                EnsureChildControls()
                Return emailValidator.ErrorMessage
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                emailValidator.ErrorMessage = value
                emailValidator.ToolTip = value
            EndSetEndProperty

        < _
        Bindable(True), _
        Category("Appearance"), _
        DefaultValue(""), _
        Description("The text for the e-mail label.") _
        > _
        PublicProperty EmailLabelText() AsStringGet
                EnsureChildControls()
                Return emailLabel.Text
            EndGetSet(ByVal value AsString)
                EnsureChildControls()
                emailLabel.Text = value
            EndSetEndProperty
        ' The Submit event.
        < _
        Category("Action"), _
        Description("Raised when the user clicks the button.") _
        > _
        Public Custom Event Submit As EventHandler
            AddHandler(ByVal value As EventHandler)
                Events.AddHandler(EventSubmitKey, value)
            EndAddHandlerRemoveHandler(ByVal value As EventHandler)
                Events.RemoveHandler(EventSubmitKey, value)
            EndRemoveHandlerRaiseEvent(ByVal sender AsObject, _
                ByVal e As System.EventArgs)
                CType(Events(EventSubmitKey), _
                    EventHandler).Invoke(sender, e)
            EndRaiseEventEndEvent
        ' The method that raises the Submit event.ProtectedOverridableSub OnSubmit(ByVal e As EventArgs)
            Dim submitHandler As EventHandler = _
                CType(Events(EventSubmitKey), EventHandler)
            If submitHandler IsNotNothingThen
                submitHandler(Me, e)
            EndIfEndSub
        ' Handles the Click event of the Button and raises        ' the Submit event.PrivateSub submitButton_Click(ByVal source AsObject, _
            ByVal e As EventArgs)
            OnSubmit(EventArgs.Empty)
        EndSubProtectedOverridesSub CreateChildControls()
            Controls.Clear()

            nameLabel = New Label()

            nameTextBox = New TextBox()
            nameTextBox.ID = "nameTextBox"

            nameValidator = New RequiredFieldValidator()
            nameValidator.ID = "validator1"
            nameValidator.ControlToValidate = nameTextBox.ID
            nameValidator.Text = "Failed validation."
            nameValidator.Display = ValidatorDisplay.Static

            emailLabel = New Label()

            emailTextBox = New TextBox()
            emailTextBox.ID = "emailTextBox"

            emailValidator = New RequiredFieldValidator()
            emailValidator.ID = "validator2"
            emailValidator.ControlToValidate = emailTextBox.ID
            emailValidator.Text = "Failed validation."
            emailValidator.Display = ValidatorDisplay.Static

            submitButton = New Button()
            submitButton.ID = "button1"AddHandler submitButton.Click, _
                AddressOf submitButton_Click

            Me.Controls.Add(nameLabel)
            Me.Controls.Add(nameTextBox)
            Me.Controls.Add(nameValidator)
            Me.Controls.Add(emailLabel)
            Me.Controls.Add(emailTextBox)
            Me.Controls.Add(emailValidator)
            Me.Controls.Add(submitButton)
        EndSubProtectedOverridesSub Render(ByVal writer As HtmlTextWriter)
            AddAttributesToRender(writer)

            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, _
                "1", False)
            writer.RenderBeginTag(HtmlTextWriterTag.Table)

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            nameLabel.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            nameTextBox.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            nameValidator.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            emailLabel.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            emailTextBox.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            emailValidator.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderBeginTag(HtmlTextWriterTag.Tr)
            writer.AddAttribute(HtmlTextWriterAttribute.Colspan, _
                "2", False)
            writer.AddAttribute(HtmlTextWriterAttribute.Align, _
                "right", False)
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            submitButton.RenderControl(writer)
            writer.RenderEndTag()
            writer.RenderBeginTag(HtmlTextWriterTag.Td)
            writer.Write("&nbsp")
            writer.RenderEndTag()
            writer.RenderEndTag()

            writer.RenderEndTag()
        EndSubProtectedOverridesSub RecreateChildControls()
            EnsureChildControls()
        EndSubEndClassEndNamespace
// Register.csusing System;
using System.ComponentModel;
using System.Drawing;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Samples.AspNet.CS.Controls
{
    [
    AspNetHostingPermission(SecurityAction.Demand,
        Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, 
        Level=AspNetHostingPermissionLevel.Minimal),
    DefaultEvent("Submit"),
    DefaultProperty("ButtonText"),
    ToolboxData("<{0}:Register runat=\"server\"> </{0}:Register>"),
    ]
    publicclass Register : CompositeControl
    {
        private Button submitButton;
        private TextBox nameTextBox;
        private Label nameLabel;
        private TextBox emailTextBox;
        private Label emailLabel;
        private RequiredFieldValidator emailValidator;
        private RequiredFieldValidator nameValidator;

        privatestaticreadonlyobject EventSubmitKey = 
            newobject();

        // The following properties are delegated to // child controls.
        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description("The text to display on the button.")
        ]
        publicstring ButtonText
        {
            get
            {
                EnsureChildControls();
                return submitButton.Text;
            }
            set
            {
                EnsureChildControls();
                submitButton.Text = value;
            }
        }

        [
        Bindable(true),
        Category("Default"),
        DefaultValue(""),
        Description("The user name.")
        ]
        publicstring Name
        {
            get
            {
                EnsureChildControls();
                return nameTextBox.Text;
            }
            set
            {
                EnsureChildControls();
                nameTextBox.Text = value;
            }
        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description(
            "Error message for the name validator.")
        ]
        publicstring NameErrorMessage
        {
            get
            {
                EnsureChildControls();
                return nameValidator.ErrorMessage;
            }
            set
            {
                EnsureChildControls();
                nameValidator.ErrorMessage = value;
                nameValidator.ToolTip = value;
            }
        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description("The text for the name label.")
        ]
        publicstring NameLabelText
        {
            get
            {
                EnsureChildControls();
                return nameLabel.Text;
            }
            set
            {
                EnsureChildControls();
                nameLabel.Text = value;
            }
        }

        [
        Bindable(true),
        Category("Default"),
        DefaultValue(""),
        Description("The e-mail address.")
        ]
        publicstring Email
        {
            get
            {
                EnsureChildControls();
                return emailTextBox.Text;
            }
            set
            {
                EnsureChildControls();
                emailTextBox.Text = value;
            }
        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description(
            "Error message for the e-mail validator.")
        ]
        publicstring EmailErrorMessage
        {
            get
            {
                EnsureChildControls();
                return emailValidator.ErrorMessage;
            }
            set
            {
                EnsureChildControls();
                emailValidator.ErrorMessage = value;
                emailValidator.ToolTip = value;
            }
        }

        [
        Bindable(true),
        Category("Appearance"),
        DefaultValue(""),
        Description("The text for the e-mail label.")
        ]
        publicstring EmailLabelText
        {
            get
            {
                EnsureChildControls();
                return emailLabel.Text;
            }
            set
            {
                EnsureChildControls();
                emailLabel.Text = value;

            }
        }

        // The Submit event.
        [
        Category("Action"),
        Description("Raised when the user clicks the button.")
        ]
        publicevent EventHandler Submit
        {
            add
            {
                Events.AddHandler(EventSubmitKey, value);
            }
            remove
            {
                Events.RemoveHandler(EventSubmitKey, value);
            }
        }

        // The method that raises the Submit event.protectedvirtualvoid OnSubmit(EventArgs e)
        {
            EventHandler SubmitHandler =
                (EventHandler)Events[EventSubmitKey];
            if (SubmitHandler != null)
            {
                SubmitHandler(this, e);
            }
        }

        // Handles the Click event of the Button and raises// the Submit event.privatevoid _button_Click(object source, EventArgs e)
        {
            OnSubmit(EventArgs.Empty);
        }

        protectedoverridevoid RecreateChildControls()
        {
            EnsureChildControls();
        }


        protectedoverridevoid CreateChildControls()
        {
            Controls.Clear();

            nameLabel = new Label();

            nameTextBox = new TextBox();
            nameTextBox.ID = "nameTextBox";

            nameValidator = new RequiredFieldValidator();
            nameValidator.ID = "validator1";
            nameValidator.ControlToValidate = nameTextBox.ID;
            nameValidator.Text = "Failed validation.";
            nameValidator.Display = ValidatorDisplay.Static;

            emailLabel = new Label();

            emailTextBox = new TextBox();
            emailTextBox.ID = "emailTextBox";

            emailValidator = new RequiredFieldValidator();
            emailValidator.ID = "validator2";
            emailValidator.ControlToValidate = 
                emailTextBox.ID;
            emailValidator.Text = "Failed validation.";
            emailValidator.Display = ValidatorDisplay.Static;

            submitButton = new Button();
            submitButton.ID = "button1";
            submitButton.Click 
                += new EventHandler(_button_Click);

            this.Controls.Add(nameLabel);
            this.Controls.Add(nameTextBox);
            this.Controls.Add(nameValidator);
            this.Controls.Add(emailLabel);
            this.Controls.Add(emailTextBox);
            this.Controls.Add(emailValidator);
            this.Controls.Add(submitButton);
        }


        protectedoverridevoid Render(HtmlTextWriter writer)
        {
            AddAttributesToRender(writer);

            writer.AddAttribute(
                HtmlTextWriterAttribute.Cellpadding,
                "1", false);
            writer.RenderBeginTag(HtmlTextWriterTag.Table);

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            nameLabel.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            nameTextBox.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            nameValidator.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            emailLabel.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            emailTextBox.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            emailValidator.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.AddAttribute(
                HtmlTextWriterAttribute.Colspan, 
                "2", false);
            writer.AddAttribute(
                HtmlTextWriterAttribute.Align, 
                "right", false);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            submitButton.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.Write("&nbsp;");
            writer.RenderEndTag();
            writer.RenderEndTag();

            writer.RenderEndTag();
        }
    }
}

Code Discussion

The Register control performs the following tasks that are common to all composite controls:

The CompositeControl class implements the common functionality needed by all composite controls. It also has an associated control designer that ensures that child controls are displayed on the design surface in a visual designer.

Note

The CompositeControl class is new in ASP.NET 2.0. If you created custom controls in ASP.NET version 1.0 or 1.1, you had to implement the INamingContainer interface to create a new naming scope for child controls. In addition, you had to override the Controls property and invoke the EnsureChildControls method. In ASP.NET 2.0, these and other steps are performed by the CompositeControl class.

You should create the child controls in the CreateChildControls method and not in OnInit or another life cycle phase. The server control architecture relies on calls to CreateChildControls whenever the Controls collection is needed, such as during data binding (if applicable).

The Register control also illustrates how to expose properties of child controls as top-level properties. This is useful when you want to allow the page developer to access the properties of a child control. For example, Register exposes the Text property of the Button child control as its own ButtonText property, as shown in the following code drawn from the example:

< _
Bindable(True), _
Category("Appearance"), _
DefaultValue(""), _
Description("The text to display on the button.") _
> _
PublicProperty ButtonText() AsStringGet
        EnsureChildControls()
        Return submitButton.Text
    EndGetSet(ByVal value AsString)
        EnsureChildControls()
        submitButton.Text = value
    EndSetEndProperty
[
Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("The text to display on the button.")
]
publicstring ButtonText
{
    get
    {
        EnsureChildControls();
        return submitButton.Text;
    }
    set
    {
        EnsureChildControls();
        submitButton.Text = value;
    }
}

The Register control does not store ButtonText or other properties it delegates to child controls in view state because the child controls use view state for storing these properties. For information on how to manage the state of properties that are not delegated to child controls, see Server Control Properties Example. That topic contains information about using the ViewState dictionary for storing simple properties and implementing custom state management for properties that have subproperties.

The Register control shows how to raise an event from the handler of a child control's event. The Register control defines an event named Submit and raises the event by attaching a handler to the Click event of the Button child control.

Test Page for the Register Control

The following example shows an ASP.NET Web page uses the Register control.

<%@ Page Language="VB"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    Sub Page_Load(ByVal sender AsObject, ByVal e As EventArgs)
        Label1.Visible = FalseEndSubSub Register_Submit(ByVal sender AsObject, ByVal e As EventArgs)
        ' The application developer can implement        ' logic here to enter registration data into        ' a database or write a cookie        ' on the user's computer.        ' This example merely writes a message        ' using the Label control on the page.
        Label1.Text = String.Format( _
            "Thank you, {0}! You are registered.", Register1.Name)
        Label1.Visible = True
        Register1.Visible = FalseEndSub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>
      Register Control Test Page
    </title>
  </head>
  <body>
    <form id="form1" runat="server">
      <aspSample:Register ButtonText="Submit" 
        OnSubmit="Register_Submit" ID="Register1"
        Runat="server" NameLabelText="Name:" EmailLabelText="Email:" 
        EmailErrorMessage="You must enter your e-mail address." 
        NameErrorMessage="You must enter your name." />
       <br />
      <asp:Label ID="Label1" Runat="server" Text="Label">
      </asp:Label>
      <asp:ValidationSummary ID="ValidationSummary1" 
        Runat="server" DisplayMode="List" />
    </form>
  </body>
</html>
<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
  void Page_Load(object sender, EventArgs e)
  {
    Label1.Visible = false;
  }
  void Register_Submit(object sender, EventArgs e)
  {
    // The application developer can implement// logic here to enter registration data into// a database or write a cookie// on the user's computer.// This example merely writes a message// using the Label control on the page.
    Label1.Text = String.Format(
        "Thank you, {0}! You are registered.",Register1.Name);
    Label1.Visible = true;
    Register1.Visible = false;
  }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>
      Register Control Test Page
    </title>
  </head>
  <body>
    <form id="form1" runat="server">
      <aspSample:Register ButtonText="Register" 
        OnSubmit="Register_Submit" ID="Register1"
        Runat="server" NameLabelText="Name:" 
        EmailLabelText="Email:" 
        EmailErrorMessage="You must enter your e-mail address." 
        NameErrorMessage="You must enter your name." />
       <br />
      <asp:Label ID="Label1" Runat="server" Text="Label">
      </asp:Label>
      <asp:ValidationSummary ID="ValidationSummary1" 
        Runat="server" DisplayMode="List" />
    </form>
  </body>
</html>

Building and Using the Example

For information about compiling and using the custom control examples, see Building the Custom Server Control Examples.

See Also

Other Resources

Developing Custom ASP.NET Server Controls