ASP.NET Control Builders
By Dino Esposito
When you author an ASP.NET Web page you use a special
markup language to identify constituent controls. The same markup language is
employed by Visual Studio when you drag and drop controls from the toolbox onto
a Web form. When you save the project, Visual Studio generates a markup script
to describe the controls in the page and their settings. As a result, the final
ASP.NET page is saved as a text file with the popular .aspx extension. When a
request for the .aspx resource arrives, ASP.NET processes the source code of
the .aspx file to build a page class dynamically.
The markup provides a description of the control; the
ASP.NET parser is responsible for interpreting that markup and generating
proper C# or Visual Basic .NET code. Are there any rules to guide the behavior
of the parser? How can the parser know about the intended behavior of a particular
markup element?
Each and every ASP.NET server control is associated with a
control builder class. A control builder works side by side with the page
parser and helps to analyze the markup for the control and to build all the
necessary child controls. In this article, I’ll review the typical behavior of
a control builder and discuss how to define a custom control builder for a
custom server control.
The ControlBuilder Class
The control builder class handles any child markup element
that the main tag of a control contains. The base class for all ASP.NET control
builders is ControlBuilder. What’s the typical behavior of a control builder?
The ControlBuilder class parses any top-level block of
markup that is flagged with the runat attribute. The builder processes every
nested element it encounters within the control’s tags and adds a child control
to the Controls collection of the control. In addition, it creates literal
controls for the text located between nested control tags.
Most built-in server controls have their own control
builders; for custom controls the default control builder works just fine (except
in a few circumstances). You’ll use a custom control builder if the control you’re
authoring has a complex layout or contains child tags that require ad hoc parsing.
Note, though, that as a control developer, you have other
tools to partially govern the parsing process of the control’s markup. Let’s
tackle this point first.
Parsing Attributes
If you derive a custom control from an existing control,
you typically stick to the existing builder and don’t change the markup layout.
As a result, either your control doesn’t have child elements, or any child
elements are taken care of by the default builder. If you create your control
from an abstract class, such as WebControl or CompositeControl, you can make
yourself responsible for the control markup. When do you need to add child
elements to a control’s markup? Style and collection properties may require a
child tag. The default control builder, though, can take care of these
situations with a little help from a couple of attributes: ParseChildren and
PersistenceMode.
The ParseChildren attribute tells the ASP.NET parser and
control builder how to parse the nested content of a control. The attribute takes
a Boolean value that indicates whether the nested content should be parsed as
properties or child controls.
The PersistenceMode attribute indicates how a control
property is persisted declaratively in a host page. Figure 1 lists possible
modes of persistence. The most common setting is InnerProperty, which instructs
Visual Studio to save the contents of the property as a nested tag named after
the property:
<x:MyControl runat="server" ... >
<MyProperty>
:
</MyProperty>
</x:MyControl>
|
Property
|
Description
|
|
Attribute
|
The property persists as an encoded HTML attribute in
the final markup.
|
|
EncodedInnerDefaultProperty
|
The property persists as the only inner text of the
control. The property value is HTML encoded. Only a string can be given this designation.
|
|
InnerDefaultProperty
|
The property persists in the control as inner text and
is the element’s default property. Only one property can be designated the
default property.
|
|
InnerProperty
|
The property persists in the control as a nested tag.
This is commonly used for complex objects with templates and styles.
|
Figure 1: Persistence
modes for control properties.
If you choose InnerDefaultProperty, you can have only one
nested tag; by opting for InnerProperty, you can have as many nested tags as needed.
This is good for rich controls with multiple templates and styles.
You need a control builder when you need to take full
control of the contents inside the opening and closing tag of the control.
Designing a Control Builder
The control builder class is automatically replaced when
you apply the ControlBuilder attribute to a custom control, as follows:
[ControlBuilder(typeof(MyControlBuilder))]
public class MyControl


{
:
}

The MyControlBuilder class kicks in when the ASP.NET
parser gets to process the markup of any instance of MyControl.
MyControlBuilder makes itself responsible for any control tree that results
from the parsing process.
To understand the role of the control builder, let’s
consider the markup for a list control, such as DropDownList:
<asp:dropdownlist runat="server">
<asp:listitem
/>
<asp:listitem
/>
<asp:listitem
/>
:
</asp:dropdownlist>

The <asp:ListItem> element
indicates an object of type ListItem that is parsed to populate the Items
collection of the DropDownList. A control builder determines the type of the
object behind the <asp:ListItem> tag and how the
information it may contain is stored inside the control.
Let’s consider the possible markup of a custom list
control, such as a TextBoxList control — a control that renders out a table
with a column for the label and a column for the textbox:

<x:textboxlist runat="server">
<x:inputfield
/>
<x:inputfield
/>
<x:inputfield
/>
:
</x:textboxlist>

To implement a control in accordance with the schema just mentioned, three classes are needed: the TextBoxList class
for the control, the InputField class for the child element, and the control
builder class to control the page parser.
The Custom Control Builder Class
The control builder class is rarely a very complex piece
of code. Its structure is extremely simple and agile and basically consists of
a series of overrides. The only method you absolutely need to override for a
significant and functional implementation is GetChildControlType.
The GetChildControlType method returns the type of the
control’s children tags. The default implementation of the base class simply
returns null. The method takes two arguments: the name of the child tag found,
and its collection of attributes. What programmers should do to implement the
method depends mostly on the schema they have in mind. The method is
responsible for getting the type that a particular child tag represents. If you
need to map the nested markup to some custom structures, the GetChildControlType
method is crucial.
In the sample control builder, the GetChildControlType
method should take into account any tag named <InputField> and force the
runtime to create an instance of the InputField type (see Figure 2).
public class TextBoxListControlBuilder : ControlBuilder


{
public override Type GetChildControlType(
string tagName,
IDictionary attributes)

{
if (tagName.ToLower() == "inputfield")
return typeof(InputField);
return null;
}
}

Figure 2: GetChildControlType
should take into account any tag named <InputField> and force the runtime
to create an instance of the InputField type.
With a similar implementation, only recognized tags are
processed in a custom way. All other tags will be treated by the page parser as
literal markup and converted to literal controls, which are added to the
control’s child control tree. The TextBoxList control features an internal
array of InputField instances (see Figure 3).
[ControlBuilder(typeof(TextBoxListControlBuilder))]
[ParseChildren(false)]
public class TextBoxList : WebControl


{
private ArrayList m_formFields = new ArrayList();

public ArrayList Items
{

get
{return m_formFields;}
}
:
}

Figure 3: TextBoxList
features an internal array of InputField instances.
The ControlBuilder attribute indicates the type of the
control builder that must be used for this control. The ParseChildren attribute
explicitly states that the general rule that child tags map to properties is
not true in this case; parsing still occurs, but it is taken care of by the
custom builder. The control also needs an additional override — the AddParsedSubObject
method:
protected override void AddParsedSubObject(object obj)


{
if (obj is ProAspNet.CS.Ch20.FormField)
m_formFields.Add(obj);
}
Fundamental is the role played by the AddParsedSubObject
method. Any tag that the GetChildControlType method recognizes is transformed
into a living instance of the specified type. This object is then passed to the
AddParsedSubObject method for further processing. If the object is of the
correct type, it’s added to the internal collection of InputField objects. At
this point, once the InputField class is defined, the control is ready for
rendering.
The InputField Class
The InputField class is a class that gathers information
about the textboxes to create within the main control. The class features a
couple of string properties named Label and Text. The Label property indicates
the text for the label; the Text property indicates the default text for the
textbox. Figure 4 shows the full source code of the class. The physical textbox
is created when the control renders out. Other properties in the control’s
programming interface can let page authors access the contents of the child
textboxes.
public class InputField


{
private string _label;
private string _text;

public InputField()

{
}
public string Label

{

get
{ return _label; }

set
{ _label = value; }
}
public string Text

{

get
{ return _text; }

set
{ _text = value; }
}
}
Figure 4: The
InputField class.
The TextBoxList control will typically be a composite
control, and as such will render its contents through the CreateChildControls
method. Internally, the method loops through the _inputFields collection and
creates as many labels and textboxes as necessary.
In the end, the goal of the control builder is to help the
parser understand the contents of the control’s markup and to load the proper
information inside the control instance bound to the page. If you have a
made-to-measure control builder, you can design at will the markup of the
control. The PersistenceMode attribute assigned to control properties helps
Visual Studio persist correctly the markup of the control when you use it in
the Web Forms designer.
Conclusion
Most ASP.NET built-in controls have their own builder, so
there’s no need for you to change or replace it. Custom controls may constitute
a different story. If the custom control is designed after an existing control,
you normally have no reason to replace the builder. If you want to design a completely
custom control and go for a rich and descriptive markup, then use a control
builder.
Dino Esposito is a
Solid Quality Learning mentor and the author of Programming
Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy,
Dino is a frequent speaker at industry events worldwide. Join the blog at http://weblogs.asp.net/despos.
posted @
2008-07-17 17:33 Liu Jian 阅读(74) |
评论 (0) |
编辑