Use Naming Containers - 30 Oct 2009

Instruct Controls to Handle Their Children Properly

Dino Esposito

October 30, 2009

9 Min Read
ITPro Today logo

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1| 2.0

 

UseNaming Containers

InstructControls to Handle Their Children Properly

 

By DinoEsposito

 

Can youname any connection between the INamingContainer interface and thecustom control's ability to handle postback events? Some developers think theyhave nothing to do with each other, but they're dead wrong.

 

Perhapsit's because the term "naming container" isn't quite self-explanatory."Container" is one of the more frequently used, albeit generic, words inprogramming literature. In this context it refers to controlscontainment. "Naming" refers to names; but which names? The names of containedcontrols.

 

In thisarticle, I'll describe naming containers, how to create custom controls thatwork as naming containers, and the surprises you'll experience if you don'tmark your controls as naming containers.

 

Name that Container

A namingcontainer is primarily a control that acts as a container for other controls.The naming container generates a sort of namespace so that the actual ID ofcontained controls is rooted in the ID of the parent. The role of namingcontainers is particularly evident if you think about iterative controls suchas Repeater or DataList. Each item these controls display is madeof the same set of constituent controls.

 

Typically,you specify constituent controls by populating a template property like ItemTemplate.The contents of the template are then iterated for each item displayed by the Repeateror DataList. Taken individually, each constituent control is given thesame ID and is repeated as many times as there are items to show. Does thismean that the final ASP.NET page will have duplicated IDs? Of course not. The Repeaterand DataList control act as naming containers and guarantee that the IDof each item-specific control is unique to the page.

 

Thetrick of iterative controls is in prefixing the real ID with the unique ID ofthe item object, e.g. the RepeaterItem object. Suppose you have thefollowing template declaration:

 

  

 

A TextBoxcontrol will be created for each displayed item. The assigned ID of the TextBoxcontrol is always Input; however, the actual ID of the control is a string like thefollowing:

 

MyRepeater$_ctrlN$Input

 

Themiddle _ctrlN string corresponds to the ID of the nth RepeaterItemobject. For example, the TextBox on the first item will have an ID like MyRepeater$_ctrl1$Input; the second will have MyRepeater$_ctrl2$Input, and so on. Note that the IDseparator is the dollar ($) symbol in ASP.NET 2.0 and the colon (:) in ASP.NET 1.x. I'll use the $ in the rest of this article, butbe aware of the differences between versions.

 

As adeveloper, you don't have to worry about the concatenation of the ID strings.That aspect is automatically managed by the ASP.NET Framework for all thosecontrols that act as naming containers.

 

Anothervalid metaphor for naming containers is that of files and folders. Just as youcan have files with the same name located in different folders, you can havecontrols with the same ID located under different naming containers. The namingcontainer mechanism provides an automatic, built-in way to ensure that each controlhas a unique ID throughout the page. This is important for two reasons: theaforementioned control identification, and postback event handling.

 

Meet the INamingContainer Interface

How is acontrol marked to act as a naming container? All naming container controls mustimplement the INamingContainer interface. This is not a big effort; theinterface has no methods or properties and is said to be a marker interface. Amarker interface is an empty interface that is used to mark a given componentas a component that requires certain services from the ASP.NET runtimeenvironment. INamingContainer is perhaps the most popular markerinterface, but it's not the only one. Other commonly used marker interfaces areIRequiresSessionState and IReadOnlySessionState. These interfacesare used to let the page handler know about the type of access being performedto session state, be it read-write or readonly. You normally don't use theseinterfaces directly. The ASP.NET Framework uses them to mark the dynamically createdpage class depending on the value of the EnableSessionState attribute.

 

Acontrol that acts as a naming container is typically a composite control, i.e.a control that results from the aggregation and composition of existingcontrols. Let's build a sample control and see how things change based on thepresence, or lack, of the naming container interface.

 

Building a Composite Control

Thesample composite control is named LabelTextBox and is made of a Labeland a TextBox control. The label displays left of the textbox; its textis controlled by the Caption property. The Text property of thenew composite control maps to the Text property of the embedded (andexternally invisible) TextBox control. The LabelTextBox controlderives from Control and implements INamingContainer:

 

publicclass LabelTextBox :Control, INamingContainer

 

Thestructure of the control is defined in the CreateChildControls method,as shown in Figure 1. Right after creation, the Label control is givensome default styles, and is then added to the Controls collection of theparent. The same treatment occurs to the TextBox control. The twocontrols are separated with a LiteralControl that contains a couple ofblanks. Let's place an instance of the LabelTextBox control in a sampleASPX page, like this:

 

  

  

 

Figure 2shows the hierarchy of the constituent controls in the resulting page. Note thethree controls listed as children of LabelTextBox. They are: a Label,a LiteralControl, and a TextBox. In Figure 1, I created thesecontrols dynamically and assigned them an explicit ID. The ASP.NETinfrastructure did the rest, modifying the ID to reflect the parent container. Asa result, the actual ID of each constituent control is rooted in the ID of theparent control (the LabelTextBox control).

 

namespace AspNetPro

{

   public class LabelTextBox : Control, INamingContainer

  {

     publicLabelTextBox() : base()

    {

    }

    // Internal members.

     protectedTextBox __theTextBox;

     protectedLabel __theLabel;

    // Gets and sets the caption of the label.

     public string Caption {

       get {returnConvert.ToString(ViewState["Caption"]);}

       set{ViewState["Caption"] = value;}

    }

    // Gets and sets the text of the textbox.

     public string Text {

       get {returnConvert.ToString(ViewState["Text"]);}

       set{ViewState["Text"] = value;}

    }

    // Create the child controls of thecontrol.

     protected override voidCreateChildControls() {

      // Add the label.

      theLabel = newLabel();

      _theLabel.Text = Caption;  

      theLabel.Font.Name ="verdana";

      theLabel.Font.Bold = true;

      Controls.Add(__theLabel);

      // Add a blank.

      Controls.Add(newLiteralControl("  "));

      // Add the textbox.

      theTextBox = newTextBox();

      theTextBox.Text = Text;  

      Controls.Add(__theTextBox);

    }

  }

}

Figure 1:An example of a composite control - the LabelTextBox control.

 


Figure 2: The hierarchy of controls when theLabelTextBox acts as a naming container.

 

So far,so good. But what happens if you omit the INamingContainer interface?Figure 3 shows the new structure of the controls. As you can see, the childcontrols now have an unqualified ID. Is this something you should be worriedabout?

 


Figure 3: The hierarchy of controls when theLabelTextBox doesn't act as a naming container.

 

If youhave multiple instances of the same control in the page, each constituent TextBoxand Label will be given the same ID. Because the ID is identical for allchild controls in each instance of the composite control, it will result in arun-time error. In ASP.NET, in fact, all control IDs must be unique.

 

You canwork around this issue by letting ASP.NET generate IDs on the fly. This is aviable approach, as long as it doesn't make it harder for you to identifyconstituent controls in the internal code. However, I believe there's another,stronger reason to mark a composite control as a naming container.

 

Who Clicked the Button?

Theimplementation of the INamingContainer interface guarantees that theASP.NET runtime effectively locates the control that caused the page postback.During the postback data-handling step, and before the Page_Load eventgets to the page, the ASP.NET runtime attempts to find a match between names inthe Request.Form collection, and controls in the Controlscollection of the page.

 

Supposeyou now have a composite control that includes a button or a linkbutton. Andsuppose also that you need to add some code to the control that executes whenthe user clicks on it. The Click event handler, therefore, must bedefined within the composite control. How do you ensure that the click iscorrectly detected and handled through the Click event handler definedby your constituent button control?

 

The IDof the clicked button control will be CompositeControl1$Button1 orsimply Button1, depending on whether you implemented the INamingContainerinterface. Figure 2 and Figure 3 clearly demonstrate this.

 

The rubis in the search algorithm that the ASP.NET runtime employs to find the postingcontrol in a page. This algorithm is sort of hard-coded in the FindControlmethod of the Control class from which all controls, and the Pageclass itself, inherit. If the control ID contains one or more occurrences ofthe $ orthe :symbol, then the search is based on the Controls collection of thecontrol whose name matches the prefix before the symbol. In practice, if theinterface is implemented, the ASP.NET runtime searches for a Button1control within the Controls collection of a control named CompositeControl1.In this case, the control is successfully located and the code associated withthe Click event is properly executed.

 

If thecomposite control is not a naming container, then the runtime assumes it has tofind a Button1 control within the outermost naming container - the pageitself or any other container control. If no such control exists - or if itexists it's not the right one - the Click event is lost. The same occursfor any property of constituent controls you want to update or retrieveprogrammatically across postbacks.

 

Conclusion

Eachcontrol can retrieve its own naming container through the NamingContainerproperty defined in the base Control class and inherited by all servercontrols, including the Page class. Here's the definition of theproperty:

 

public virtualControl NamingContainer {get;}

 

Thenaming container of any control is the nearest parent control in the hierarchythat implements the INamingContainer interface. A server control thatimplements INamingContainer creates a unique namespace for the IDs ofits child controls. Creating a unique namespace for server controls isparticularly important when you build composite or templated controls such asthe Repeater and DataList controls.

 

Inaddition to giving unique IDs to dynamically generated child controls, theconcept of a naming container is also fundamental to the ASP.NET runtime tofind a perfect match between the posting client component and a server-sidecontrol. Events fired by controls contained in other controls not marked asnaming containers are not detected by the ASP.NET runtime, and get lost.

 

INamingContainer is a valuable interface with azero-cost implementation. There will be no excuses allowed if you run intotrouble and you haven't implemented it.

 

The sample code in thisarticle is available for download.

 

DinoEsposito is aWintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Authorof Programming Microsoft ASP.NET and Introducing ASP.NET2.0, both fromMicrosoft Press, Dino also helped several companies architect and buildeffective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portalfor VB and .NET programmers. Write to him at mailto:[email protected] or jointhe blog at http://weblogs.asp.net/despos.

 

 

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like