ASP.NET Composite Controls

Custom controls are often created by picking two or more controls and combining them to produce a single result. This aspect is important because you can reuse existing controls and add more features to simplify the use of common recurring situations. Get a 40% discount on this book by clicking on the book image, and use promo code egghead40.

This article is based on ASP.NET 4.0 in Practice,  by Daniele Bochicchio, Stefano Mostarda, and Marco De Sanctis,to be published January 2011. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. Visit the book's page for more information.  Get a 40% discount by using promo code egghead40

Composite controls

This article is taken from the book ASP.NET 4.0 in Practice. The authors discuss how to build composite controls using ASP.NET.

Composite controls are treated separately because combining controls together is more challenging than creating a new one from scratch. In this situation, specific problems may surface. For example, the controls need to be wrapped and their members exposed in the corresponding control. While this task is simple to perform, it is also time consuming. The reality is that you will map only the most used and useful members and add the others as needed. The problem with this class of controls is that you’re hiding them from the outside, deciding what the external world may or may not use.

For this same reason, events handled internally by these controls can become your nightmare: in this scenario, in fact, you need to implement an event bubbling technique (to let events propagate through the control tree) or
define new events to expose the existing ones outside of the wrapped controls. In order to fully understand how these aspects will reflect on the creation of a composite control, our scenario will cover how to build composite
controls using ASP.NET.

Problem

Suppose we need to create a special DropDownList that, in a single declaration, can be used to insert both the description and the options to be selected by the user. Using this control, we can save a lot in terms of markup to be written and we can reuse the same feature over and over in our projects.

Solution

Composite controls are generally created by deriving from CompositeControl in System.Web.UI.WebControls. This class implements a lot of the logic necessary to apply custom controls that at the same time serve as Web Controls—they support styling, for example. If you do not need these features, you can opt for the simple Control class from System.Web.UI. This option will ensure that the generated markup remains very simple, but you will need to manually add the missing features that CompositeControl already provides.

Figure 1 shows the approach offered by composite controls.

Composite control

Control A

Treated as a single control

Control B

Figure 2 A composite control combines more controls together. Externally, they are treated as a single control that encapsulates the entire logic.

In both cases, we will manipulate the Page’s control tree and dynamically instantiate controls at runtime. Composite controls work by combining controls together, so the controls are added using the CreateChildControls method.

This method is called whenever a child control is needed, through a call to the EnsureChildControls method. When manipulating the control tree, in fact, you need to be careful and remember that these are controls that will be nested into the control itself and then into the page. To add a control inside another, you have to simply access its Controls properties and add it via the Add method, as explained in listing 1.

Listing 1 CreateChildControl will contain the nested controls declaration

C#

public class SuperDropDownList: CompositeControl, INamingContainer
{

protected override void CreateChildControls()
{

    if (ChildControlsCreated) #4

    return;

    Controls.Clear(); #1

    Controls.Add(new LiteralControl("<p>"));

     Label labelText = new Label();
     labelText.Text = Description;

     Controls.Add(labelText); #2

     Controls.Add(new LiteralControl( string.IsNullOrEmpty(Description)?string.Empty: ": "));

     DropDownList listControl = new DropDownList();

     Controls.Add(listControl); #3

     Controls.Add(new LiteralControl("</p>"));

     ChildControlsCreated = true; #4

}

#5

}

VB

Public Class SuperDropDownList
Inherits CompositeControl
Implements INamingContainer

Protected Overrides Sub CreateChildControls()

    If ChildControlsCreated Then #4

   Return
End If

   Controls.Clear() #1

   Controls.Add(New LiteralControl("<p>"))

   Dim labelText As New Label()

   labelText.Text = Description

   Controls.Add(labelText) #2

   Controls.Add(New LiteralControl(If(String.IsNullOrEmpty(Description), String.Empty, ": ")))

   Dim listControl As New DropDownList()

   Controls.Add(listControl) #3

   Controls.Add(New LiteralControl("</p>"))

   ChildControlsCreated = True #4

End Sub

#5

End Class

#1 Remove existing controls
#2 Add a new Label

#3 Add a new DropDownList
#4 Avoid control recreation

#5 The code continues after the text

As you can see in listing 1, we are adding some controls to display a DropDownList and a description. It is
important to understand that, to remove unwanted controls from the control tree (such as Literal controls that
may be added in markup), we are performing a call to
Controls.Clear to reset the control tree. The code in #4
is not necessary because it already is included by
CompositeControl; nevertheless, it is showing how to deal
with this problem when another, simpler base control (like
Control) is used. The results are shown in figure 2.

The declaration of the properties is omitted from the previous listing for brevity. When we need to set the
properties for the inner controls, a special approach must be used: we need to understand how to access an inner
object’s property from outside the control. In these situations, the most preferred way to go is the one contained in
this snippet:

C#

public IList DataSource
{

  get
  {

   EnsureChildControls(); #1

   return ((DropDownList)Controls[3]).DataSource as IList;

  }

set

  {

    EnsureChildControls();

    ((DropDownList)Controls[3]).DataSource = value;

   }

}

VB

Public Property DataSource() As IList
Get

  EnsureChildControls() #1

  Return TryCast(DirectCast(Controls(3), DropDownList).DataSource, IList)
End Get

Set

  EnsureChildControls()

  DirectCast(Controls(3), DropDownList).DataSource = value End Set

End Property

#1 Will call CreateChildControls

As you can see, we are referring to the previously created control (in this case, the DropDownList) finding it by position and directly exposing its inner property. This is generally the best way to go because you do not have to keep the inner property in sync. (It is automatically performed using this pattern.)

HOW TO AVOID REFERENCING A CONTROL BY POSITION

In order to produce a cleaner code, you can also save a reference to the controls in CreateChildControls and refer to them using this syntax, instead of finding them by position.

It is important to understand that the calls to EnsureChildControls are mandatory—this will ensure, in fact, that the controls are created before we access them. Now that the infrastructure of our control is in place, let’s take a look at using events in composite controls.

Events in composite controls

Events are used in custom controls to simplify the code necessary to handle a state. A composite control hides the
child controls, so you need to propagate their events outside of to the container by implementing an event
wrapper.

Redirecting an event is a simple technique where the event is sent outside by intercepting it locally and then propagating it outside. It is easier to show you this snippet to help you understand how it works than spend many words describing it:

C#

public event EventHandler SelectedValueChanged;

protected void OnSelectedValueChanged(EventArgs e)
{

    if (SelectedValueChanged != null)
    SelectedValueChanged(this, e);
}

VB

Public Event SelectedValueChanged As EventHandler

Protected Sub OnSelectedValueChanged(e As EventArgs)
    RaiseEvent SelectedValueChanged(Me, e)
End Sub

This snippet will expose a new event, called SelectedValueChanged, and a new OnSelectedValueChanged method, used to define the event handler in the markup.

To make the event attached to the inner control, in the CreateChildControls method right after the DropDownList instance, we add this simple code:

C#

DropDownList listControl = new DropDownList();

listControl.SelectedIndexChanged += (object sender, EventArgs e) => {
OnSelectedValueChanged(e);

};

VB

Dim listControl as New DropDownList()

listControl.SelectedIndexChanged += Function(sender As Object, e As EventArgs) Do
OnSelectedValueChanged(e)

End Function

This will ensure that, when the DropDownList’s SelectedIndexChanged event is fired, our event will be too. The result is that the event handler created inside the Page will be called as well and our event will propagate outside the contained control.

Discussion

When building composite controls, you need to pay attention to the fact that you are not generating markup but composing your controls, mixing them together, and manipulating the Page’s control tree.

While this is certainly easy to implement in a simple scenario like the one we chose here, because you are
leveraging existing controls, it may also be prone to error. As we’ve learned in this scenario, the point here is to understand how
CreateChildControls and EnsureChildControls are working.

Summary

Building custom controls is often treated as an art. It is, in fact, one of the most challenging parts that you will find
when dealing with ASP.NET. Starting with custom controls is not difficult, but advanced scenarios involve a very deep mastering of ASP.NET. However, custom controls can help you to avoid code duplication, by implementing
and supporting repetitive tasks.

By Peter Bromberg   Popularity  (4074 Views)