Build Composite Controls

Combine the power of HTML controls or Web controls, or use a little of both.

ControlTower

LANGUAGES: C# | VB

TECHNOLOGIES: Custom Controls

 

Build Composite Controls

Combine the power of HTML controls or Web controls, or use a little of both.

 

By Doug Seven

 

The ASP.NET Framework enables rapid Web application development through a number of tools, including user controls and server controls. Although the framework ships with several included server controls, user controls must be built to suit a specific need and they are limited in scope to a single Web application (see my article User Controls). Server controls are compiled controls that provide predefined functionality, such as a Label control or Calendar control. Of course, not every control you might want or need is shipped with the .NET Framework. Fortunately, building your own controls is not too difficult.

 

Server controls can be broken into three categories: composite controls, templated controls, and data-bound templated controls. In this article, I will introduce you to composite controls and show you how to build one. Specifically, I will show you a couple different techniques for building composite controls, and I'll teach you how to build controls that won't conflict when multiple instances exist on the same Web form by implementing the INamingContainer interface.

 

What is a Composite Control?

A composite control is simply a control that combines multiple existing controls. This could be a combination of HTML controls or Web controls, or a combination using both kinds of controls. A composite control enables a single server control to encapsulate the functionality of multiple server controls.

 

To build a composite control, begin by creating a class that derives from the System.Web.UI.WebControls.WebControl class (or another control class that you want to derive from). By deriving from the WebControl class, your composite control inherits all the basic functionality of a standard server control, including basic properties such as Visible, Enabled, Controls, etc., and basic methods such as Render and CreateChildControls among others. I mention these two methods specifically because they provide two different means of rendering a composite control to a Web form. The Render method enables you to specify the exact rendering of the HTML to be output, while CreateChildControls enables you to build a control hierarchy and lets each control you add to the parent composite control's Controls collection handle the rendering. Like many things in ASP.NET, the Render and CreateChildControls methods exist to provide different functionality - they are not mutually exclusive, one is not necessarily a better choice than the other, and you can decide to use one method or the other (or both methods) based on the correct choice for the control you are building.

 

In this article, I will show you how to build the same control using the Render method and the CreateChildControls method by building an Order control for displaying order information from the Northwind database. Depending on the approach, the code for the controls ranges from 360 to 525 lines, so I will not list all of it in this article. I'll introduce you to each important topic with a small code sample, but you can download the complete code.

 

Build the Control Structure

Building a control is similar to building any class file - you start by declaring your namespace and class. For a control, however, you inherit from the System.Web.UI.WebControls.WebControl class, which provides your control with the basic functionality of a Web control:

 

using System;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.ComponentModel;

using System.Data;

 

namespace CompositeControls

{

   [ToolboxData("<{0}:Order runat=server>")]

  public class Order : System.Web.UI.WebControls.WebControl

  {

  }

}

 

I used the ToolboxData attribute to specify how the control tag will be added to a Web form in the IDE of an editor such as Visual Studio .NET. I could add additional attributes to ToolboxData. This control tag is added to a Web form when a developer drags my control from the IDE toolbox to the Web form designer. The ToolboxData attribute is not required, but it does provide a nice benefit for developers working with a tool such as Visual Studio .NET.

 

Within the control class I created, I can create properties that my control will expose. Because I am building a control to render a single order (and the order detail) from the Northwind database, I want to add some properties that describe the order. Figure 1 shows a list of the properties I want to expose.

 

Property

Data Type

Description

OrderID

Int32

The OrderID value from the database.

CustomerID

String

The CustomerID value from the database.

OrderDate

DateTime

The OrderDate value from the database.

RequiredDate

DateTime

The RequiredDate value from the database.

ShippedDate

DateTime

The ShippedDate value from the database.

ShippedToName

String

The ShipName value from the database.

ShippedToAddress

String

The ShipAddress value from the database.

ShippedToCity

String

The ShipCity value from the database.

ShippedToRegion

String

The ShipRegion value from the database.

ShippedToPostalCode

String

The ShipPostalCode value from the database.

ShippedToCountry

String

The ShipCountry value from the database.

Products

DataTable

A DataTable of products shipped in the order.

Figure 1. Here is a list of properties you might want your control to expose.

 

To expose a property, you create a private variable instance to hold the property value and a public property with set and get accessors:

 

private Int32 _orderID;

[Bindable(true), Category("Appearance"), DefaultValue("")]

public Int32 OrderID

{

  get

  {

    return _orderID;

  }

  set

  {

    _orderID = value;

  }

}

 

The public property instance is preceded with the Bindable, Category, and DefaultValue attributes. The Bindable attribute specifies that this property supports two-way data binding, and it will be listed in the DataBindings Window in Visual Studio .NET (see Figure 2). The Category attribute specifies which category the property will be listed under in the Properties Window in Visual Studio .NET. The DefaultValue attribute specifies the default value for the property.

 


Figure 2. These are the properties supporting two-way data binding, listed in the Data Bindings Window.

 

Override the Render Method

The Render method is a method of the base class, WebControl. You can use this method to provide specific output to render in the Web page generated when the Web form the control is a part of is requested. The Render method takes one argument, HtmlTextWriter, which sends the output (HTML tags and text) to the browser. HtmlTextWriter has many methods useful for controlling the output, such as WriteBeginTag(String), WriteAttribute(String, String), Write(String), and WriteLine. You can override the Render method and specify the exact output to send to the browser in a linear fashion.

 

Figure 3 shows the Order control as it renders to a Web page. The Order control renders a table with three columns. In the first row, only the first and third column contain text (the Customer ID and the Order ID); the second row spans all three columns and is filled with the shipping information; the third row has the Order Date, Required Date, and Shipped Date, each in one of the three columns; and the fourth row has another table embedded in it, displaying all the products included in the order.

 


Figure 3. Here is how the Order control is rendered to a Web page.

 

The output format in Figure 3 is managed by providing specific instructions in the overridden Render method (see Figure 4).

 

protected override void Render(HtmlTextWriter _output)

{

  //Write the opening

tag

  _output.WriteBeginTag("table");

  _output.WriteAttribute("border", "0");

  _output.WriteAttribute("cellPadding", "4");

  _output.WriteAttribute("cellSpacing", "0");

  _output.WriteAttribute("style",

    "border-color:Tan;border-width:1px;" +

    "border-style:solid;border-collapse:collapse;");

  _output.Write(HtmlTextWriter.TagRightChar);

  _output.WriteLine();

  _output.Indent++;

 

  //

  _output.WriteFullBeginTag("tr");

  _output.WriteLine();

  _output.Indent++;

 

  //

  _output.Indent--;

  _output.WriteEndTag("td");

  _output.WriteLine();

 

  //

  _output.Indent--;

  _output.WriteEndTag("td");

  _output.WriteLine();

 

  //

  _output.Indent--;

  _output.WriteEndTag("td");

  _output.WriteLine();

 

  //

  _output.Indent--;

  _output.WriteEndTag("tr");

  _output.WriteLine();

 

  //Remaining code removed for space

  //See code download for complete code

 

  //

  _output.WriteFullBeginTag("td");

  _output.WriteLine();

  _output.Indent++;

 

  //Customer ID

  _output.WriteFullBeginTag("b");

  _output.Write("Customer ID: ");

  _output.WriteEndTag("b");

  _output.Write(" ");

  _output.Write(this.CustomerID);

 

  //

  _output.WriteFullBeginTag("td");

  _output.WriteLine();

  _output.Indent++;

 

  //This is an empty cell

 

  //

  _output.WriteBeginTag("td");

  _output.WriteAttribute("align", "right");

  _output.Write(HtmlTextWriter.TagRightChar);

  _output.WriteLine();

  _output.Indent++;

 

  //Order ID

  _output.WriteFullBeginTag("b");

  _output.Write("Order ID: ");

  _output.WriteEndTag("b");

  _output.Write(" ");

  _output.Write(this.OrderID.ToString());

 

  //

  _output.Indent--;

  _output.WriteEndTag("table");

  _output.WriteLine();

}

Figure 4. You manage the output format of the Order control by providing specific instructions in the overridden Render method.

 

The Render method takes the HtmlTextWriter (named _output) as its input argument, which is used to write the HTML output to the browser. Figure 5 lists some of the methods the HtmlTextWriter class provides.

 

Method

Description

WriteBeginTag(String)

Writes the opening of an HTML element's beginning tag. For example: _output.WriteBeginTag("table") renders .

WriteAttribute(String, String)

Writes the attribute name and value. For example: _output.WriteAttribute("border", "0") renders border="0".

Write(String)

Writes out the test passed to it to the browser. You can pass the public field HtmlTextWriter.TagRightChar as the argument, which outputs the closing bracket of the HTML tag (>).

WriteFullBeginTag(String)

Writes the full (opening and closing bracket) HTML tag for the String argument passed in. For example: _output.WriteFullBeginTag("b") renders .

WriteEndTag(String)

Writes the closing tag for the String argument passed in. For example: _output.WriteEndTag("b") renders .

Indent

A property of the HtmlTextWriter class. Specifies how many spaces to indent at the beginning of a line of HTML. Used to provide clean HTML source code. Double plus signs (++) indicate that the indent should go into the next tab stop; a minus sign (-) indicates that the indent should move back to the last tab stop.

WriteLine

Writes the String argument passed in (if there is one) followed by a line break in the HTML.

Figure 5. Here is a list of some of the methods HtmlTextWriter provides.

 

Once you build the control, you can compile it into a dll and distribute it for use in any ASP.NET Web application. You can use a command-line compiler to build the dll with a command similar to this:

 

csc.exe /t:library /out:CompositeControls.dll

  *.cs /r:System.dll,System.Web.dll,System.Data.dll

 

If you are using Visual Studio .NET, you can build the dll by selecting the Build menu option and choosing Build Solution.

 

Use a Custom Control on a Web Form

Once you create the control dll, you can place it in the \bin folder of an ASP.NET Web application, which gives you access to the control in that application (you also can register the control in the Global Assembly Cache to have access to it from all Web applications on the Web server). On a Web form, you register the control using the @ Register directive, which allows you to use the control throughout the form:

 

<%@ Register TagPrefix="cc1" Namespace="CompositeControls"

  Assembly="CompositeControls" %>

 

You can then place the control anywhere on the form:

 

 

Add Controls in Visual Studio .NET

If you are using Visual Studio .NET, you can make a reference to the control assembly, add the control to the toolbox in the IDE, and drag and drop the control onto a Web form. Here's how you add a reference to the assembly. Right-click on the References folder below the project name in the Solution Explorer and click on Add Reference.... Next, while the .NET tab is selected, click on Browse and navigate to the dll in the \bin folder. Then, select the dll and click on Open. Finally, click on OK.

 

Next, add the control to the toolbox. Right-click on the toolbox and select Customize Toolbox. Then select the.NET Framework Components tab. Click on Browse and navigate to the dll in the \bin folder. Select the dll and click on Open, then click on OK.

 

A generic icon for the control should appear in the toolbox. You now can drag and drop this control onto a Web form. Visual Studio .NET adds the @ Register directive for you automatically, places the control tag in the appropriate place in the Web form presentation code, and maps an object instance to the control instance in the code-behind class.

 

In the code of the Web form, you can get the order information for an order and set the appropriate properties of the control in the Page_Load event handler (see Figure 6).

 

Private Sub Page_Load(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles MyBase.Load

 ' Data access code removed for text - see code

 ' download for complete code

  Order1.OrderID = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(0)

  Order1.CustomerID = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(1)

  Order1.OrderDate = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(2)

  Order1.RequiredDate = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(3)

  Order1.ShippedDate = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(4)

  Order1.ShippedToName = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(5)

  Order1.ShippedToAddress = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(6)

  Order1.ShippedToCity = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(7)

  Order1.ShippedToRegion = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(8)

  Order1.ShippedToPostalCode = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(9)

  Order1.ShippedToCountry = _

   _dataSet.Tables("OrderInfo").Rows(0).Item(10)

End Sub

Figure 6. In Visual Basic .NET, you can obtain order information and set control properties in the Page_Load event handler.

 

Override the CreateChildControls Method

The CreateChildControls method is another method of the base class Control that allows you to build a control tree of child controls that render when the parent control is rendered. Overriding the CreateChildControls method enables you to build a control tree of both HTML controls and ASP.NET Web Controls and add them to the parent control's Controls collection. The control's basic structure is the same as the previous example, but rather than overriding the Render method you override the CreateChildControls method and build the composite control using ASP.NET Web Controls (see Figure 7).

 

protected override void CreateChildControls()

{

  //Define the child controls

  Table _table = new Table();

  TableCell _tableCell;

  TableRow _tableRow;

 

  Table _productsTable;

  TableHeaderCell _productsHeader;

  TableCell _productsCell;

  TableRow _productsRow;

 

  _table.ID = "InfoTable";

  _table.CellPadding = 4;

  _table.CellSpacing = 0;

  _table.BorderColor = Color.Tan;

  _table.BorderWidth = Unit.Pixel(1);

  _table.BorderStyle = BorderStyle.Solid;

  _table.GridLines = GridLines.None;

 

  //Create the header row

  _tableRow = new TableRow();

 

  _tableCell = new TableCell();

  _tableCell.Text = "Customer ID: " +

   this.CustomerID;

  _tableRow.Cells.Add(_tableCell);

 

  _tableCell = new TableCell();

  _tableRow.Cells.Add(_tableCell);

 

  _tableCell = new TableCell();

  _tableCell.Text = "Order ID: " + this.OrderID;

  _tableCell.HorizontalAlign = HorizontalAlign.Right;

  _tableRow.Cells.Add(_tableCell);

 

  _table.Rows.Add(_tableRow);

 

  //Remaining code removed for space

  //See code download for complete code

 

  //Add the Table to the Controls collection

  this.Controls.Add(_table);

}

Figure 7. To create a composite control of ASP.NET Web Controls, you override the CreateChildControls method instead of the Render method.

 

Figure 7 contains the same HTML layout as the first Order control from Figure 4, but this time I used ASP.NET Web Controls to create the control. The resulting output is identical to the original Order control, but the actual HTML rendering is handled by the ASP.NET Controls I added to the parent control's Controls collection.

 

Implement INamingContainer

Implementing the INamingContainer interface ensures unique naming of all child controls in a composite control by prefixing the child control names with the name of the parent control and an underscore.

 

In Figure 7, I added an ID value to the parent Table control. When the OrderToo control was rendered, the parent

element was rendered like this:

 

cellpadding="4" bordercolor="Tan" border="0"

style="border-color:Tan;border-width:1px;

border-style:Solid;border-collapse:collapse;">

 

The

element was given the ID value "InfoTable". If I had multiple instances of this control on a single Web form, there would be multiple controls with the ID InfoTable. To prevent this, implement INamingContainer on the OrderToo control:

 

public class OrderToo :

 System.Web.UI.WebControls.WebControl, INamingContainer

 

Now the resulting HTML output is rendered like this:

 

cellpadding="4" bordercolor="Tan" border="0"

style="border-color:Tan;border-width:1px;

border-style:Solid;border-collapse:collapse;">

 

Note that the ID value of the

element is now OrderToo1_InfoTable.

 

The INamingContainer interface is used on composite and complex controls such as DataGrid and DataList to provide unique naming to the child controls in the parent control's Controls collection. It is a good practice to implement INamingContainer on any composite control.

 

You can create reusable controls by building a class that derives from the System.Web.UI.WebControls.WebControl class, or any other control class you want to inherit from. By overriding the Render method, you can control the HTML output of your control explicitly. Alternately, you can use the CreateChildControls method to build a control tree of both HTML and ASP.NET Web Controls and allow the child controls to control their own rendering. By implementing INamingContainer, you can guarantee unique naming for all the child controls in your composite control, preventing any naming conflicts when multiple instances of your controls are used on a single Web form.

 

The code referenced in this article is available for download.

 

Doug Seven is a co-founder of the .NET training company DotNetJunkies (http://www.dotnetjunkies.com). He has had previous technical roles at Nordstrom, Microsoft, and GiftCertificates.com, and he has experience as a training specialist at Seattle Coffee Company. Doug co-authored Programming Data-Driven Web Applications with ASP.NET and ASP.NET: Tips, Tutorials & Code (Sams), and he has several titles in the works. E-mail Doug at mailto:[email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

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

Plain text

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