In the desktop world, the output of Windows Forms controls refers to the underlying native windows infrastructure with associated painting and messaging subsystems. Basic Windows Forms controls are the transposition of low-level windows; more sophisticated controls result from the composition of multiple low-level windows. Such windows, though, never pop their head into the public API and remain buried in the folds of the Windows Forms framework. The ID assigned to the Windows Forms control is the only ID that developers are required to know.
The Web Forms framework is built around a similar model, but with some key differences. In this article, I'll discuss how IDs for child elements are generated in Web Forms, which problems are to be faced and possibly resolved, and how ASP.NET 4.0 is coming to the rescue.
Control IDs in Web Forms
In Web Forms, the output of web server controls refers to HTML elements and the document object model that the client browser builds on top of the source HTML. Basic server controls are the transposition of a single HTML element; for example, the TextBox control is equivalent to an HTML element. Richer server controls result from the composition of multiple HTML elements. For example, the CheckBox control includes an element and a
The unique ID that identifies the server control may be insufficient on the client if the HTML output of that control produces multiple elements. In addition, in Web Forms you can use data-bound template-based controls. Such controls work by repeating an HTML template once for each bound data item. The IDs of the elements in the repeatable template must be created in a way that makes each ID unique and predictable in order to be scripted.
When you place a server control within a Web Form, you must give it a unique ID. If you don't do that explicitly in the markup, an auto-generated ID will be used as the page markup is processed into a page class. In this case, the auto-generated ID takes the form of ctlXX where XX is a progressive number. If you add controls through the Visual Studio 2008 designer, a slightly more readable ID is generated that mirrors the name of the control and adds a trailing index, such as TextBox1.
To see the ASP.NET ID auto-generation mechanism in action, let's consider the following code fragment:
This code fragment produces the following HTML markup: <%# DataBinder.Eval(Container.DataItem, "CustomerID") %>
The Repeater1 ID is not used anywhere in the HTML, but this is mostly a feature of an extremely simple control like the Repeater. The 's ID is simply emitted as is for each data bound item. As a result, the page DOM will contain multiple elements with the same ID. This doesn't prevent a successful page display, but it will make it hard to script span elements in case of need.
As a further step, let's try adding some server controls, instead, in the repeatable template.
The resulting markup changes as follows: <%# DataBinder.Eval(Container.DataItem, "CustomerID") %>
Figure 1: HTML source code of an ASP.NET template
Figure 1 shows a screenshot from Internet Explorer (IE) 8. In the client page, each tag now has its own unique ID and client scripting is much easier. Now if you want to, say, render in blue the that contains ALFKI, you can add the following script:
Not too bad if it weren't for the fact that you need to figure out what the actual ID of a bound element is going to be. The auto-generated ID ensures that each ID is unique but the actual name is not always predictable. The algorithm entails that the name of each repeated element is scoped into the naming container. Note that controls that are not assigned an explicit ID are given a system-provided progressive ctlXX string. In the previous example, each bound element is wrapped in an implicitly created RepeaterItem control with a ctlXX ID. Finally, the common name of the element is appended. Note that what makes two IDs unique is the presence of implicitly named controls such as the RepeaterItem. (Each data-bound template-based control follows a similar schema.)
UniqueID and ClientID Properties
ASP.NET server controls expose two properties for you to programmatically know the real ID being used on the client. The properties, UniqueID and ClientID, are very similar. The UniqueID property concatenates the parts of the ID using the $ symbol. The resulting ID, however, is not usable for scripting because the $ symbol is not allowed in an ASP.NET ID. The ClientID property returns a unique ID that is usable on the client. This is the only ID you should ever take into account for client scripting. The ClientID property differs from UniqueID because it replaces the $ symbol with the underscore (_) symbol. To be precise, you can set the separator for the parts on a ClientID programmatically using the ClientIDSeparator property. The ClientIDSeparator property defaults to the underscore.
At this point, you might ask, why should we deal with two similar properties such as ClientID and UniqueID? Couldn't the design team use the underscore to separate parts of a fully qualified ID? The point is that ASP.NET needed the certainty to identify any control unambiguously. For this to happen, though, it was necessary to forbid one symbol the $ symbol and allow it for internal use only. The design team picked the $ symbol because the underscore is a much more popular choice in the body of element IDs. So reserving the underscore would have had a significant impact on developers; not reserving any symbol would not have guaranteed the ability to uniquely identify any element.
When data-driven and template-based ASP.NET controls are used, controls in the templates are repeated and the IDs for actual HTML elements are generated on the fly. The code below shows how you can programmatically refer to the valid ID for one of the elements in the Repeater's output. Instead of figuring out the explicit name, you can resort to a code block that inserts the actual ClientID value:
var alfki = document.getElementById('<% =Repeater1.Controls.Controls.ClientID %>');
For data-bound controls, the real issue is traversing the tree of child controls to locate just the DOM element you need. For example, the Repeater outputs a collection of controls of type RepeaterItem. These controls populate the first Controls collection in the code snippet. Each RepeaterItem, in turn, contains the subtree that forms the output for a given item. In this case, HTML literals do matter. In our example, the item template contains a server-side tag followed by some data-bound text. Because the text is on the next line, you should account for the carriage return, too. In the code snippet, the carriage return is the first control; the server-side is the second.
In one way or another you can determine the real ID for any given ASP.NET control in an HTML template, including master pages. But can you do it in a simpler and better way?
ASP.NET 4.0 to the Rescue
ASP.NET 4.0 gives you much more control over the algorithm that generates the client ID of a server control employed within a template. In ASP.NET 4.0, the base Control class features the new ClientIDMode property. The property is declared of type ClientIDMode; Figure 2 lists the feasible values for the property.
Figure 2: The ClientIDMode enumeration
|Inherit||The control doesn't define its own policy for ID generation. The control inherits any policy valid on its parent.|
|Legacy||The control generates its child IDs using the legacy algorithm used by previous versions of ASP.NET.|
|Predictable||Any ID is generated by simply concatenating the IDs of parent elements.|
|Static||No mangled ID is generated; the assigned ID is emitted in the markup as is.|
To prevent existing applications from failing in
some way, the default value of the ClientIDMode property is set to Legacy. This
means that by default no changes occur to the way in which IDs are generated.
If you want to alter the algorithm, though, now you can. You can set the value
for the ClientIDMode property at various levels: for individual controls, for
all controls in the page via the @Page directive. Finally, you can even set
your preference for all pages in the application by storing the setting in the
Two relatively lightweight options are Inherit and Static. The former indicates that the control will use the same algorithm as its parent. When the latter option is selected, ASP.NET doesn't apply any name mangling to the original ID. Because the ID is emitted as is, in the case of repeated templates you end up having multiple IDs in the client page. As mentioned, this won't generate any runtime error in the DOM, but it will put any responsibility for correctly identifying and scripting DOM elements entirely on your shoulders.
The most interesting scenario is when you set the ClientIDMode to Predictable. In this case, ASP.NET will still apply its own predefined algorithm to mangle repeatable IDs to keep them unique. However, in ASP.NET 4.0 the Predictable option selects a different, non-legacy algorithm over which you can exercise some control. In Predictable mode, the ID is obtained by concatenating the ID of parent controls.
How is this different from the Legacy algorithm? First, in the case of content pages any content placeholder is ignored. Second, the new property RowClientIDSuffix lets you determine the context of data-bound template controls. When Predictable is used, you'll get the output in ASP.NET 4.0 that Figure 3 shows. The ID takes the following form:
Figure 3: The Predictable option in action
The key difference is in the trailing index. It now tells you explicitly about the position of the element in the bound list. You can attach a key field value by using the RowClientIDSuffix property. By setting this property to, say, CustomerID, the ID becomes
The ID of controls in HTML templates is the exact concatenation of parent IDs, plus the value of the specified data key and a 0-based progressive index. Note that not all ASP.NET data-bound controls support the RowClientIDSuffix property. It works for GridView and ListView; it is not supported by the Repeater.