ASP.NET Control Builders
Define a Custom Control Builder for a Custom Server Control
October 30, 2009
LANGUAGES: C# | VB.NET
ASP.NET VERSIONS: 1.x | 2.0
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:
:
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:
:
The 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 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:
:
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 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; } }Public Class TextBoxListControlBuilder : Inherits ControlBuilder Public Overrides Function GetChildControlType( _ ByVal tagName As String, _ ByVal attributes As IDictionary) As Type If tagName.ToLower() = "inputfield" Then Return GetType(InputField) End If Return Nothing End FunctionEnd Class
Figure 2: GetChildControlType should take into account any tag named 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;} } : } _ _Public Class TextBoxList : Inherits WebControl Private _inputFields As ArrayList() Public ReadOnly Property InputFields As ArrayList Get If _inputFields Is Nothing Then _inputFields = New ArrayList() End If Return _inputFields End Get End Property : End Class
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); }Protected Overrides Sub AddParsedSubObject( _ ByVal obj As Object) If TypeOf(obj) Is InputField Then _inputFields.Add(obj) End IfEnd Sub
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; } } }Public Class InputField Private _label As String Private _text As String Public Sub New() End Sub Public Property Label As String Get Return _label End Get Set (ByVal value As String) _label = value; End Set End Property Public Property Text As String Get Return _text End Get Set (ByVal value As String) _text = value; End Set End PropertyEnd Class
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.
About the Author
You May Also Like