Control Parsing and Control Builders


We'll now take a brief look at how the page parser parses a declarative instance of a control and how you can override the default parsing logic.

Note

The information provided in this section is not specific to composite controls but is general background information. It is relevant to any control that allows nested content within its tags.


In Chapter 10, you saw how you can specify parsing logic for your control by marking your control with the ParseChildrenAttribute metadata attribute. Specifying ParseChildren(true) tells the parser to interpret content within the control's tags as properties. Alternatively, specifying ParseChildren(true, "<PropertyName>") tells the parser that content nested within the control's tags corresponds to the property name passed into the attribute. In many Web controls, the ParseChildrenAttribute metadata attribute provides adequate parsing functionality. However, if your control needs specialized parsing logic, it is useful to understand the more general parsing functionality and the extensibility features that allow you to modify the existing functionality.

The page parser uses classes referred to as control builders to parse any content that exists between the tags of a server control. A control builder is an instance of the System.Web.UI.ControlBuilder class (or of a class that derives from ControlBuilder ). The parser builds a parse tree once it has parsed the content of an .aspx page. This parse tree is similar to the control tree; however, it is a tree comprised of control builder instances rather than control instances. This parse tree is then converted into code that is compiled based on the dynamic compilation model described in Chapter 2, "Page Programming Model."

Based on the metadata provided in the ParseChildrenAttrribute metadata attribute, the parser divides controls into two broad parsing categories:

  • Controls marked with the ParseChildren(true) metadata attribute. In this case, the parser interprets nested content as properties of the control and uses a set of internal control builders to parse the nested properties, including subproperties , templates, and collection properties.

  • Controls not marked with the ParseChildren(true) metadata attribute. In this case, the parser uses the ControlBuilder associated with your control to interpret the content within your control's begin and end tags. Every control has the base ControlBuilder class associated with it by default. The default ControlBuilder implementation allows server controls to contain objects, other server controls, and static text (in the form of LiteralControl s). These parsed objects are added to your control via the AddParsedSubObject method of your control. By default, the AddParsedSubObject method expects server controls and adds these parsed controls into your control's Controls collection as child controls.

Controls that derive from Control (which does not have the ParseChildrenAttribute metadata attribute applied), controls that derive from WebControl but are marked with the ParseChildren(false) metadata attribute, and other controls marked with the ParseChildren(true) metadata attribute, are all parsed as described in the second bullet. The rest of this section is devoted to controls in this parsing category ”that is, controls whose nested content does not correspond to properties .

To modify the default parsing logic of a control whose nested content does not correspond to properties, you can implement a class that derives from the ControlBuilder class and override its methods . You can then associate that control builder with your control by applying the ControlBuilderAttribute metadata attribute to your control. In addition, you can override the AddParsedSubObject method in your control to make it do something other than adding the parsed objects to your control tree. Note that any logic you implement in a ControlBuilder is executed once at parse time before code generation, whereas any logic you implement in AddParsedSubObject is executed upon every request. It is therefore much more efficient to perform all parsing logic in the ControlBuilder . For example, the built-in control builders for handling properties perform type conversions from strings into strongly typed objects at parse time rather than performing this conversion upon every request.

We will now implement an InnerTextLabel control that has an associated custom control builder that trims white spaces inside the control's tags and decodes HTML specified within the control's tags. InnerTextLabel also overrides the AddParsedSubObject method to parse the text within its control's tags. Listing 12-10 contains the code for the control as well as the code for the InnerTextLabelBuilder control builder associated with the control.

Listing 12-10 InnerTextLabel.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.Web; usingSystem.Web.UI; usingSystem.Web.UI.WebControls; namespaceMSPress.ServerControls{ publicclassInnerTextLabelControlBuilder:ControlBuilder{ publicoverrideboolAllowWhitespaceLiterals(){ returnfalse; } //AControlBuilderallowsyoutocheckerrors //atparsetime,whichismoreefficientthan //performingchecksatruntimeinacontrol's //AddParsedSubObjectmethod. //Inthissample,theAppendSubBuilderiscommented //todemonstratetheAddParsedSubObjectmethodofthe //InnerTextLabelcontrol.Ifyouuncommentthe //followingcode,apagethatnestsobjectswithinthe //tagsofanInnerTextLabelwillcause //parseerrorsratherthanruntimeexceptions. 
 // //publicoverride //voidAppendSubBuilder(ControlBuildersubBuilder){ //thrownewException(//"AnInnerTextLabelcontrolcannotcontainobjects."); //} publicoverrideboolHtmlDecodeLiterals(){ returntrue; } } [ ControlBuilder(typeof(InnerTextLabelControlBuilder)), DefaultProperty("Text"), ParseChildren(false) ] publicclassInnerTextLabel:WebControl{ protectedoverrideHtmlTextWriterTagTagKey{ get{ returnHtmlTextWriterTag.Label; } } [ Bindable(true), Category("Appearance"), DefaultValue(""), Description("Thetextproperty"), PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty) ] publicstringText{ get{ strings=(string)ViewState["Text"]; return(s==null)?String.Empty:s; } set{ ViewState["Text"]=value; } } protectedoverridevoidAddParsedSubObject(objectobj){ if(objisLiteralControl){ Text=((LiteralControl)obj).Text; } 
 else{ //Insteadofperformingthefollowing //checkatruntime,itismoreefficient //toperformitatparsetime, //inthecontrol'sControlBuilder, //asshowninInnerTextLabelControlBuilder. thrownewArgumentException("Theinnercontentmustcontainstatictext"); } } protectedoverridevoidRenderContents(HtmlTextWriterwriter){ writer.Write(HttpUtility.HtmlEncode(Text)); } } } 

The InnerTextLabelControlBuilder overrides the AllowWhitespaceLiterals method to trim the white spaces in content within the control's begin and end tags. InnerTextLabelControlBuilder also overrides the HtmlDecodeLiterals to remove HTML encoding in text entered by the page developer between the control's tags. In addition, InnerTextLabelControlBuilder shows how you can override the AppendSubBuilderMethod to perform checks at parse time, which are more efficient than run-time checks, since the code in the control builder executes only once, when the page is parsed.

The InnerTextLabel control is marked with the ParseChildren(false ) metadata attribute so that the parser interprets content within its tags as a control. InnerTextLabel overrides the AddParsedSubObject method to allow only literal text between its tags. The control assigns the value of the Text property of the parsed LiteralControl passed into it by the control builder to its own Text property. Finally, InnerTextLabel renders the HTML-encoded value of its Text property.

The InnerTextLabel allows a page developer to specify its Text property within the control's tags, as shown in Listing 12-11. Without the special parsing logic we have implemented, simple properties such as the Text property can be persisted only on the control's tag.

Listing 12-11 InnerTextLabelTest.aspx
 <%@PageLanguage="C#"%> <%@RegisterTagPrefix="msp"NameSpace="MSPress.ServerControls" Assembly="MSPress.ServerControls"%> <html> <body> <msp:InnerTextLabelid="innerTextLabel1"runat="server" Font-Size="Medium"Font-Names="Verdana"> Textpropertypersistedasinnertext. </msp:InnerTextLabel> </body> </html> 

Here are a few scenarios that require a custom ControlBuilder :

  • Overriding the AllowWhitespaceLiterals and HtmlDecodeLiterals methods and customizing the handling of literal text within a control's begin and end tags, as shown in the InnerTextLabelControlBuilder example in Listing 12-10.

  • Overriding the GetChildControlType and customizing the mapping of tag names to types. The default ControlBuilder implementation maps every tag that contains the runat="server" attribute to its associated class based on the tag's prefix and name. Your custom control builder can map tags to types even if the tag does not contain the runat="server" attribute or a tag prefix. This allows types to be declared by using a more terse declarative syntax. To enable this scenario, a control builder must override the GetChildControlType method whose arguments represent the complete tag and a dictionary of name/value pairs corresponding to the attributes of the tag.

  • Providing a different parsing logic from that implemented by the ASP.NET parser. For example, the Xml control in ASP.NET uses a control builder that gets the content within the control's tags and invokes the XML parser to parse that content. A control builder indicates that it is interested in the content within a control's tag by overriding the NeedsTagInnerText to return true . The parser then calls the control builder's SetTagInnerText to hand it the string of text containing the contents of the control.

The use of control builders is not limited to controls. You can associate a control builder with any class that is used declaratively within your control's tags. For example, the ListItem class in ASP.NET is associated with a ControlBuilder similar to the InnerTextLabelControlBuilder (shown in Listing 12-10) to allow a page developer to specify the text of a ListItem within the <asp:ListItem> tag.

Note

Debugging a control builder is tricky because a control builder executes during page parsing, which occurs only when the page containing the control is first requested . If you want the control builder to execute again, you must make some change in the page to cause it to be reparsed.

To debug a control builder, request a page that does not use the control whose control builder you want to debug and attach the debugger. Next make a first request to a page that contains the control whose control builder you want to debug. The debugger will stop at the first breakpoint in the control builder, and you can perform the usual debugging operations. (We described how to debug a control in Chapter 5, "Developing a Simple Custom Control.")




Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net