Managing Client IDs

Work out predictable ID names in ASP.NET

Dino Esposito

October 30, 2009

10 Min Read
ITPro Today logo

Considerable time has gone by since the ASP.NETplatform was first conceived in the late 1990s. ASP.NET came about when thesuccessful Visual Basic form-based model was brought to the web. In doing so,ASP.NET designers came up with a component model similar to the one availableto desktop developers. The overall idea was to give developers, say, a TextBoxcomponent where a text input field was expected in the UI. Such a TextBoxcomponent would have a number of properties and fire a number of events, sothat developers could work with it through an abstract programming interface.

In the desktop world, the output of Windows Formscontrols refers to the underlying native windows infrastructure with associatedpainting and messaging subsystems. Basic Windows Forms controls are the transpositionof low-level windows; more sophisticated controls result from the compositionof multiple low-level windows. Such windows, though, never pop their head intothe 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 arerequired to know.

The Web Forms framework is built around a similarmodel, but with some key differences. In this article, I'll discuss how IDs forchild elements are generated in Web Forms, which problems are to be faced andpossibly 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 controlsrefers to HTML elements and the document object model that the client browserbuilds on top of the source HTML. Basic server controls are the transpositionof a single HTML element; for example, the TextBox control is equivalent to an HTML element. Richer server controls result from thecomposition of multiple HTML elements. For example, the CheckBox controlincludes an element and a element. InASP.NET, you also find HTML controls that are native HTML elements exposed asserver-side programmable components.

The huge difference that exists between WindowsForms and Web Forms is that any HTML elements forming the output of a webcontrol are visible to any piece of code running within the client browser.This means that any client-side code (i.e., JavaScript code) can scriptindividual pieces of HTML produced by one or more server controls. Because youcan program the same control from both the server and client, and because theHTML transposition of a server control may originate multiple elements, youneed two distinct sets of IDs to exercise full control over the interface.

The unique ID that identifies the server control maybe insufficient on the client if the HTML output of that control producesmultiple elements. In addition, in Web Forms you can use data-boundtemplate-based controls. Such controls work by repeating an HTML template oncefor each bound data item. The IDs of the elements in the repeatable templatemust be created in a way that makes each ID unique and predictable in order tobe scripted.

Auto-Generated IDs

When you place a server control within aWeb Form, you must give it a unique ID. If you don't do that explicitly in themarkup, an auto-generated ID will be used as the page markup is processed intoa 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 VisualStudio 2008 designer, a slightly more readable ID is generated that mirrors thename of the control and adds a trailing index, such as TextBox1.

In a nutshell, any ASP.NET server control needs tohave a unique value assigned to its ID property. The value of the ID propertyis then used to set both the ID and name the HTML attributes in the resultingclient page markup. In HTML, the ID attribute is primarily used to script thecontrol via JavaScript. When you use document.getElementById to get a referenceto a DOM element, you must indicate the value of the ID property. The nameattribute, instead, is only valid within an HTML form and is used to identifyform elements such as , , and . Bydesign, posted data comes in the form of name/value pairs and the name part ofthe pair is used to match the name attribute of input elements in the form.This is just how HTML works, and it has no relationship with ASP.NET.

To see the ASP.NET ID auto-generation mechanism inaction, let's consider the following code fragment:

<%# DataBinder.Eval(Container.DataItem, "CustomerID") %>
This code fragment produces the following HTML markup:ALFKI 
:

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 aresult, the page DOM will contain multiple elements with the same ID. Thisdoesn't prevent a successful page display, but it will make it hard to scriptspan elements in case of need.

As a further step, let's try adding some servercontrols, instead, in the repeatable template.

<%# DataBinder.Eval(Container.DataItem, "CustomerID") %>
The resulting markup changes as follows:ALFKI 
ANATR 
:


Figure 1: HTML source code of an ASP.NET template

Figure 1 shows a screenshot from Internet Explorer (IE) 8. Inthe client page, each tag now has its own unique ID and clientscripting 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 tofigure out what the actual ID of a bound element is going to be. Theauto-generated ID ensures that each ID is unique but the actual name is notalways predictable. The algorithm entails that the name of each repeatedelement is scoped into the naming container. Note that controls that are notassigned an explicit ID are given a system-provided progressive ctlXX string. In the previous example, eachbound element is wrapped in an implicitly created RepeaterItem control with actlXX ID. Finally, the common name ofthe element is appended. Note that what makes two IDs unique is the presence ofimplicitly named controls such as the RepeaterItem. (Each data-boundtemplate-based control follows a similar schema.)

UniqueID and ClientID Properties

ASP.NET server controls expose two properties foryou to programmatically know the real ID being used on the client. Theproperties, UniqueID and ClientID, are very similar. The UniqueID propertyconcatenates 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.NETID. 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. TheClientID property differs from UniqueID because it replaces the $ symbol withthe underscore (_) symbol. To be precise, you can set the separator for theparts on a ClientID programmatically using the ClientIDSeparator property. TheClientIDSeparator property defaults to the underscore.

At this point, you might ask, why should we dealwith two similar properties such as ClientID and UniqueID? Couldn't the designteam use the underscore to separate parts of a fully qualified ID? The point isthat ASP.NET needed the certainty to identify any control unambiguously. Forthis to happen, though, it was necessary to forbid one symbol the $ symbol andallow it for internal use only. The design team picked the $ symbol because theunderscore is a much more popular choice in the body of element IDs. Soreserving the underscore would have had a significant impact on developers; notreserving any symbol would not have guaranteed the ability to uniquely identifyany element.

When data-driven and template-based ASP.NET controlsare used, controls in the templates are repeated and the IDs for actual HTMLelements are generated on the fly. The code below shows how you canprogrammatically refer to the valid ID for one of the elements in the Repeater'soutput. Instead of figuring out the explicit name, you can resort to a codeblock that inserts the actual ClientID value:

var alfki = document.getElementById('<%=Repeater1.Controls[0].Controls[1].ClientID %>');

For data-bound controls, the real issue istraversing the tree of child controls to locate just the DOM element you need.For example, the Repeater outputs a collection of controls of typeRepeaterItem. These controls populate the first Controls collection in the codesnippet. Each RepeaterItem, in turn, contains the subtree that forms the outputfor a given item. In this case, HTML literals do matter. In our example, theitem template contains a server-side tag followed by somedata-bound text. Because the text is on the next line, you should account forthe carriage return, too. In the code snippet, the carriage return is the firstcontrol; the server-side is the second.

In one way or another you can determine the real IDfor any given ASP.NET control in an HTML template, including master pages. Butcan 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 thealgorithm that generates the client ID of a server control employed within atemplate. In ASP.NET 4.0, the base Control class features the new ClientIDModeproperty. The property is declared of type ClientIDMode; Figure 2 lists the feasiblevalues for the property.

Figure 2: The ClientIDMode enumeration

Member

Description

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 insome way, the default value of the ClientIDMode property is set to Legacy. Thismeans 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 valuefor the ClientIDMode property at various levels: for individual controls, forall controls in the page via the @Page directive. Finally, you can even setyour preference for all pages in the application by storing the setting in the section of the web.config file.

Two relatively lightweight options are Inherit andStatic. The former indicates that the control will use the same algorithm asits parent. When the latter option is selected, ASP.NET doesn't apply any namemangling to the original ID. Because the ID is emitted as is, in the case ofrepeated templates you end up having multiple IDs in the client page. Asmentioned, this won't generate any runtime error in the DOM, but it will putany responsibility for correctly identifying and scripting DOM elementsentirely on your shoulders.

The most interesting scenario is when you set theClientIDMode to Predictable. In this case, ASP.NET will still apply its ownpredefined algorithm to mangle repeatable IDs to keep them unique. However, inASP.NET 4.0 the Predictable option selects a different, non-legacy algorithmover which you can exercise some control. In Predictable mode, the ID isobtained 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-boundtemplate controls. When Predictable is used, you'll get the output in ASP.NET4.0 that Figure 3 shows. The ID takes the following form:

Repeater1_ctl00_Element_0

Figure 3: The Predictable option in action

The key difference is in the trailing index. It now tells youexplicitly about the position of the element in the bound list. You can attacha key field value by using the RowClientIDSuffix property. By setting thisproperty to, say, CustomerID, the ID becomes

GridView1_ALFKI_0

The ID of controls in HTML templates is the exactconcatenation of parent IDs, plus the value of the specified data key and a0-based progressive index. Note that not all ASP.NET data-bound controlssupport the RowClientIDSuffix property. It works for GridView and ListView; itis not supported by the Repeater.

An Improvement

Born to make web development a lot easier at a timein which it required too many HTML and JavaScript skills, ASP.NET today facesthe revenge of HTML. The growing complexity of modern HTML interfaces,accessibility, styling, AJAX, and subtle browser differences make it vital fordevelopers to control many more aspects of HTML than in the recent past. Thelist also includes element IDs. Until version 4.0, ASP.NET had no way tocontrol (and guess, to some extent) the algorithm for ID generation. Things getbetter with ASP.NET 4.0. Try it out!

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