Use Naming Containers - 30 Oct 2009

Instruct Controls to Handle Their Children Properly

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1| 2.0

 

Use Naming Containers

Instruct Controls to Handle Their Children Properly

 

By Dino Esposito

 

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

 

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

 

In this article, I'll describe naming containers, how to create custom controls that work as naming containers, and the surprises you'll experience if you don't mark your controls as naming containers.

 

Name that Container

A naming container 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 of contained controls is rooted in the ID of the parent. The role of naming containers is particularly evident if you think about iterative controls such as Repeater or DataList. Each item these controls display is made of 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 Repeater or DataList. Taken individually, each constituent control is given the same ID and is repeated as many times as there are items to show. Does this mean that the final ASP.NET page will have duplicated IDs? Of course not. The Repeater and DataList control act as naming containers and guarantee that the ID of each item-specific control is unique to the page.

 

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

 

  

 

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

 

MyRepeater$_ctrlN$Input

 

The middle _ctrlN string corresponds to the ID of the nth RepeaterItem object. 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 ID separator 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, but be aware of the differences between versions.

 

As a developer, 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 those controls that act as naming containers.

 

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

 

Meet the INamingContainer Interface

How is a control marked to act as a naming container? All naming container controls must implement the INamingContainer interface. This is not a big effort; the interface has no methods or properties and is said to be a marker interface. A marker interface is an empty interface that is used to mark a given component as a component that requires certain services from the ASP.NET runtime environment. INamingContainer is perhaps the most popular marker interface, but it's not the only one. Other commonly used marker interfaces are IRequiresSessionState and IReadOnlySessionState. These interfaces are used to let the page handler know about the type of access being performed to session state, be it read-write or readonly. You normally don't use these interfaces directly. The ASP.NET Framework uses them to mark the dynamically created page class depending on the value of the EnableSessionState attribute.

 

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

 

Building a Composite Control

The sample composite control is named LabelTextBox and is made of a Label and a TextBox control. The label displays left of the textbox; its text is controlled by the Caption property. The Text property of the new composite control maps to the Text property of the embedded (and externally invisible) TextBox control. The LabelTextBox control derives from Control and implements INamingContainer:

 

public class LabelTextBox : Control, INamingContainer

 

The structure of the control is defined in the CreateChildControls method, as shown in Figure 1. Right after creation, the Label control is given some default styles, and is then added to the Controls collection of the parent. The same treatment occurs to the TextBox control. The two controls are separated with a LiteralControl that contains a couple of blanks. Let's place an instance of the LabelTextBox control in a sample ASPX page, like this:

 

  

  

 

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

 

namespace AspNetPro

{

   public class LabelTextBox : Control, INamingContainer

  {

     public LabelTextBox() : base()

    {

    }

    // Internal members.

     protected TextBox __theTextBox;

     protected Label __theLabel;

    // Gets and sets the caption of the label.

     public string Caption {

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

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

    }

    // Gets and sets the text of the textbox.

     public string Text {

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

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

    }

    // Create the child controls of the control.

     protected override void CreateChildControls() {

      // Add the label.

      theLabel = new Label();

      _theLabel.Text = Caption;  

      theLabel.Font.Name = "verdana";

      theLabel.Font.Bold = true;

      Controls.Add(__theLabel);

      // Add a blank.

      Controls.Add(new LiteralControl("  "));

      // Add the textbox.

      theTextBox = new TextBox();

      theTextBox.Text = Text;  

      Controls.Add(__theTextBox);

    }

  }

}

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

 


Figure 2: The hierarchy of controls when the LabelTextBox 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 child controls now have an unqualified ID. Is this something you should be worried about?

 


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

 

If you have multiple instances of the same control in the page, each constituent TextBox and Label will be given the same ID. Because the ID is identical for all child controls in each instance of the composite control, it will result in a run-time error. In ASP.NET, in fact, all control IDs must be unique.

 

You can work around this issue by letting ASP.NET generate IDs on the fly. This is a viable approach, as long as it doesn't make it harder for you to identify constituent 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?

The implementation of the INamingContainer interface guarantees that the ASP.NET runtime effectively locates the control that caused the page postback. During the postback data-handling step, and before the Page_Load event gets to the page, the ASP.NET runtime attempts to find a match between names in the Request.Form collection, and controls in the Controls collection of the page.

 

Suppose you now have a composite control that includes a button or a linkbutton. And suppose also that you need to add some code to the control that executes when the user clicks on it. The Click event handler, therefore, must be defined within the composite control. How do you ensure that the click is correctly detected and handled through the Click event handler defined by your constituent button control?

 

The ID of the clicked button control will be CompositeControl1$Button1 or simply Button1, depending on whether you implemented the INamingContainer interface. Figure 2 and Figure 3 clearly demonstrate this.

 

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

 

If the composite control is not a naming container, then the runtime assumes it has to find a Button1 control within the outermost naming container - the page itself or any other container control. If no such control exists - or if it exists it's not the right one - the Click event is lost. The same occurs for any property of constituent controls you want to update or retrieve programmatically across postbacks.

 

Conclusion

Each control can retrieve its own naming container through the NamingContainer property defined in the base Control class and inherited by all server controls, including the Page class. Here's the definition of the property:

 

public virtual Control NamingContainer {get;}

 

The naming container of any control is the nearest parent control in the hierarchy that implements the INamingContainer interface. A server control that implements INamingContainer creates a unique namespace for the IDs of its child controls. Creating a unique namespace for server controls is particularly important when you build composite or templated controls such as the Repeater and DataList controls.

 

In addition to giving unique IDs to dynamically generated child controls, the concept of a naming container is also fundamental to the ASP.NET runtime to find a perfect match between the posting client component and a server-side control. Events fired by controls contained in other controls not marked as naming containers are not detected by the ASP.NET runtime, and get lost.

 

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

 

The sample code in this article is available for download.

 

Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal for VB and .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish