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 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.csusingSystem; 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 :
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.") |