|
|
|
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.