ASP.NET Tip: Creating a Composite Web Control

Adding user controls to a web application is a fairly easy thing to do. You essentially create a portion of a web page, add your HTML, and then use the control within your application. Although easy to create, user controls are harder to share between projects. As an alternative to user controls, you can create a new web control in a separate library. This type of control—a composite control—is harder to create but much easier to share between applications. This tip demonstrates how to create a relatively simple composite control.

A composite control is one made up of other built-in controls, such as text boxes. You also can create a control from scratch if you don’t want to use the built-in controls. This example, however, puts three text boxes together to allow a user to enter a date. It’ll have a little bit of logic to combine the values of the boxes into a single date.

To build the example application, you need two projects: a Web Control Library project and a web site project. When you’re building a web control, you house a control within a class file. In this case, the DateEditBox control class, which just shows three text boxes on the screen, is shown here:

namespace MyControls
{
   [DefaultProperty("Value")]
   [ToolboxData("<{0}:DateEditBox runat=server></{0}:DateEditBox>")]
   public class DateEditBox : CompositeControl
   {
      private TextBox txtMonth = new TextBox();
      private TextBox txtDay   = new TextBox();
      private TextBox txtYear  = new TextBox();

      protected override void OnInit(EventArgs e)
      {
         base.OnInit(e);
         txtMonth.ID        = this.ID + "_month";
         txtMonth.MaxLength = 2;
         txtMonth.Width     = this.Width;

         txtDay.ID        = this.ID + "_day";
         txtDay.MaxLength = 2;
         txtDay.Width     = this.Width;

         txtYear.ID        = this.ID + "_year";
         txtYear.MaxLength = 4;
         txtYear.Width     = this.Width;
      }

      [Bindable(true)]
      [Category("Appearance")]
      [DefaultValue("")]
      [Localizable(true)]
      public DateTime Value
      {
         get
         {
            EnsureChildControls();
            if (txtMonth.Text == "" || txtDay.Text == "" ||
                txtYear.Text == "")
               return DateTime.MinValue;
            else
               return new DateTime(Convert.ToInt32(txtYear.Text),
                  Convert.ToInt32(txtMonth.Text),
                  Convert.ToInt32(txtDay.Text));
         }

         set
         {
            EnsureChildControls();
            txtMonth.Text = value.Month.ToString();
            txtDay.Text   = value.Day.ToString();
            txtYear.Text  = value.Year.ToString();
         }
      }
      protected override void CreateChildControls()
      {
         this.Controls.Add(txtMonth);
         this.Controls.Add(txtDay);
         this.Controls.Add(txtYear);
         base.CreateChildControls();
      }
      public override void RenderControl(HtmlTextWriter writer)
      {
         writer.RenderBeginTag(HtmlTextWriterTag.Table);
         writer.RenderBeginTag(HtmlTextWriterTag.Tr);
         writer.RenderBeginTag(HtmlTextWriterTag.Td);
         txtMonth.RenderControl(writer);
         writer.RenderEndTag();    // td
         writer.RenderBeginTag(HtmlTextWriterTag.Td);
         writer.Write("/");
         writer.RenderEndTag();    // td
         writer.RenderBeginTag(HtmlTextWriterTag.Td);
         txtDay.RenderControl(writer);
         writer.RenderEndTag();    // td
         writer.RenderBeginTag(HtmlTextWriterTag.Td);
         writer.Write("/");
         writer.RenderEndTag();    // td
         writer.RenderBeginTag(HtmlTextWriterTag.Td);
         txtYear.RenderControl(writer);
         writer.RenderEndTag();    // td
         writer.RenderEndTag();    // tr
         writer.RenderEndTag();    // table
      }
   }
}

There’s a lot going on in this class. To begin with, the declaration of the class includes a couple of attributes required to make the control work on the web page. The first is the default value, which is specified as your Value property. The second is the format used when someone wants to add the control to a web page, typically by dragging the control from the Visual Studio toolbox. You also should note that this class derives from CompositeControl, which is a class designed specifically for controls that use only built-in controls. See the help for more details on what this base class includes.

In the OnInit event, set some attributes on the child controls; they are txtMonth, txtDay, and txtYear. Copy the CssClass and Width properties from the parent control to the child controls, which lets these controls use attributes specified on the parent for control. You also could add extra properties to the class to control each child control if you wanted to. You also set the ID properties of each child control to be prefixed by the ID assigned to the parent control. This lets you access the child controls later.

You then have your Value property, used to set and read the value of the composite control. As part of your basic validation, send back the date only if all three boxes are filled in. You could also add extra validation here to make sure that the fields are numeric. The set portion of the property breaks the specified date into three parts to fill each of the three boxes.

Now, you have the CreateChildControls method, followed by the RenderControl method. The CreateChildControls is responsible strictly for getting the controls into the page for postback use. If you leave out this method, your child controls won’t have the ability to maintain viewstate. The RenderControl method is responsible for showing the web control on the web page. In this case, you show the three textboxes in a table like this:

table
   tr
      td [txtMonth] /td
      td [/] /td
      td [txtDay] /td
      td [/] /td
      td [txtYear] /td
   /tr
/table

The HtmlTextWriter control includes methods to properly add and close table cells, as well as all the other defined HTML tags. This method takes care of displaying your control on the web page. To use this control in a separate web page, you need to reference your project and then, on the page where you want to use it, add this directive:

<%@ Register Assembly="MyControls"
             Namespace="MyControls"
             TagPrefix="MC" %>

Once that reference is in place and the web project has a copy of the MyControls DLL, you’ll get the IntelliSense to add MC:DataEditBox as a control. From your code-behind, you can look at the control’s Value property and set the Width to control the width of each box that makes up the control.

Covering the Basics

There are lots of other features you could add, but this tip showed you just the basics required of every composite control: creating the child controls, setting the ID properties, adding them to the Controls collection, and then rendering the control. After that, you can add any extra properties, methods, or events to make the control an even more valuable part of your web toolbox.

About the Author

Eric Smith is the owner of Northstar Computer Systems, a web-hosting company based in Indianapolis, Indiana. He also is a MCT and MCSD who has been developing with .NET since 2001. In addition, he has written or contributed to 12 books covering .NET, ASP, and Visual Basic. Send him your questions and feedback via e-mail at questions@techniquescentral.com.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read