This article is based on ASP.NET 4.0 in Practice published on May 15, 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information.
| ASP.NET 4 in Practice
By Daniele Bochicchio, Stefano Mostarda, and Marco De
Sanctis
Custom controls are often
created by combining existing ones, enhancing their features. Combining
controls is more challenging than creating a new one from scratch. In this
article from the book ASP.NET 4.0 in Practice, you’ll learn how to how to
build composite controls using ASP.NET.
You may also
be interested in…
|
Custom controls are often created by combining existing ones,
enhancing their features. In most situations, this process consists of picking
two or more controls and combining them to produce a single result. Knowing how
to do this is important because you can reuse existing controls and add more
features to simplify the use of common, recurring situations.
Combining controls is more challenging than creating a new one
from scratch. When you create composite controls, you’ll encounter special
problems. For example, the controls need to be wrapped, and their members need to
be exposed in the corresponding control. This task is simple to perform but
it’s also time consuming. The reality is that you’ll map only the most used and
useful members and add the others as you need them.
The problem with this class of controls is that you’re hiding
them from the outside, deciding what the external world may and may not use.
For this reason, events handled internally by these controls can become a
nightmare. You need to implement an event bubbling technique
(to let events propagate through the control tree), or opt to define new events
to expose just the existing ones outside the wrapped controls. To fully
understand how all this will affect how you create a composite control, our
scenario will cover how to build composite controls using ASP.NET.
Problem
Let’s suppose you need to create a special DropDownList
that,
in a single declaration, can be used to both insert the description and the
options to be selected by the user.
By using this control, you can save a lot of time in terms of markup
to be written, and you can reuse the same feature over and over in your
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 implement custom controls
that are web controls, too—composite controls support styling, for example. If
you don’t need these features, you can opt for the simple Control class from System.Web.UI
.
Using the Control class
will ensure that the generated markup remains simple, but you’ll need to
manually add the missing features that Composite-Control already provides.
Figure 1 illustrates the concept of composite controls. Whether
you use the CompositeControl
class or the Control
class, you need to manipulate the page’s control tree and
dynamically instantiate controls at runtime.
Figure 1 A composite control combines other controls.
Externally, they’re treated as a single control that encapsulates the entire
logic.
Composite controls work by combining controls together, so the
controls are added using the CreateChildControls
method.
The CreateChildControls
method is called via a call to the EnsureChildControls
method
whenever a child control is needed. When you’re manipulating the control tree,
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 access its Controls properties and add it via the Add method, as
shown in the following listing.
Listing 1 CreateChildControl contains the Nested
Controls Declaration
C#
public class SuperDropDownList: CompositeControl, INamingContainer
{
protected override void CreateChildControls()
{
if (ChildControlsCreated) #1
return;
Controls.Clear(); #A
Controls.Add(new LiteralControl("<p>"));
Label labelText = new Label();
labelText.Text = Description;
Controls.Add(labelText);
Controls.Add(new LiteralControl(
string.IsNullOrEmpty(Description)?
string.Empty:": "));
DropDownList listControl = new DropDownList();
Controls.Add(listControl);
Controls.Add(new LiteralControl("</p>"));
ChildControlsCreated = true; #1
}
... #B
}
VB
Public Class SuperDropDownList
Inherits CompositeControl
Implements INamingContainer
Protected Overrides Sub CreateChildControls()
If ChildControlsCreated Then #1
Return
End If
Controls.Clear() #A
Controls.Add(New LiteralControl("<p>"))
Dim labelText As New Label()
labelText.Text = Description
Controls.Add(labelText)
Controls.Add(New LiteralControl(If(String.IsNullOrEmpty(Description),
String.Empty, ": ")))
Dim listControl As New DropDownList()
Controls.Add(listControl)
Controls.Add(New LiteralControl("</p>"))
ChildControlsCreated = True #1
End Sub
... #B
End Class
#1 Avoids control creation
#A Removes existing controls
#B Continues code
As you can see in this listing, we’re basically adding some
controls in order to display a DropDownList
and a description. To remove unwanted
controls from the control tree (which could be Literal
controls that can be added in
markup), we’re performing a call to Controls.Clear
to reset the control tree.
The code in #1 isn’t actually necessary because it’s already included
by Composite-Control
.
Listing 1 shows how to deal with this problem when another simpler base control
(as Control)
is used. Look at figure 2 to see the results.
Figure 2 The new SuperDrop-DownList control is in action.
This control combines different controls to provide a simple implementation.
We’ve omitted the declaration of the properties from listing 1
for brevity. When you need to set the properties for the inner controls, you have
to use a special approach: you need to access an inner object’s property from
outside the control. In these situations, the preferred way to go is shown in
the following snippet:
C#
public IList DataSource
{
get
{
EnsureChildControls(); #A
return ((DropDownList)Controls[3]).DataSource as IList;
}
set
{
EnsureChildControls();
((DropDownList)Controls[3]).DataSource = value;
}
}
VB
Public Property DataSource() As IList
Get
EnsureChildControls()
Return TryCast(DirectCast(Controls(3), DropDownList).DataSource, IList)
End Get
Set
EnsureChildControls() #A
DirectCast(Controls(3), DropDownList).DataSource = value
End Set
End Property
#A Will call CreateChildControls
As you can see, we’re referring to the control we created in
listing 1 (in this case, the DropDownList
), finding it by position, and directly
exposing its inner property. Because you don’t have to keep the inner property
in sync (it’s automatically performed using this pattern), this example shows
you the best way to handle this situation.
HOW TO AVOID REFERENCING A CONTROL BY POSITION To
produce cleaner code, you can also save a reference to the controls in CreateChildControls and
then refer to the controls using this syntax (instead of finding them by position).
The calls to EnsureChildControls
are not only important—they’re mandatory. These calls ensure 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 how to use 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 the container by implementing an
event wrapper.
Redirecting an event is a simple technique. The event is sent
outside by first intercepting it locally and then propagating it outside. Take
a look at the following snippet to understand how it works. In this case, the
code is worth 1,000 words.
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, which is used to define the
event handler in the markup. The last addition we need to make in order to
attach the event to the inner control is to add this simple code in the CreateChildControls
method,
right after the DropDownList
instance:
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 snippet ensures that when the DropDownList
’s SelectedIndexChanged
event is fired, our
event will be fired, too. The result is that the event handler created inside
the page will also be called, and our event will propagate outside the
contained control.
Discussion
When you’re building composite controls, you need to pay
attention to the fact that you’re not generating markup, but composing your
controls, mixing them together, and manipulating the page’s control tree. This
task is certainly easy to implement in a simple scenario like the one we
covered here because you’re leveraging existing controls but it can also be
prone to error. As you’ve learned in this scenario, you need to understand how CreateChildControls
and EnsureChildControls
work.
Summary
Getting started with custom controls isn’t difficult, but
advanced scenarios involve a deep understanding of ASP.NET. In more simple
situations, custom controls can help you avoid code duplication by implementing
and supporting repetitive tasks. You can easily add PostBack and templates to
every control, and implementing support for data binding isn’t all that
difficult.
Here are some other Manning titles you might be
interested in: