When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, March 17, 2010

Take Control Of Web Control ClientID Values in ASP.NET 4

By Scott Mitchell


Introduction


Each server-side Web control in an ASP.NET Web Forms application has an ID property that identifies the Web control and is the name by which the Web control is accessed in the code-behind class. When rendered into HTML, the Web control turns its server-side ID value into a client-side id attribute. Ideally, there would be a one-to-one correspondence between the value of the server-side ID property and the generated client-side id, but in reality things aren't so simple. By default, the rendered client-side id is formed by taking the Web control's ID property and prefixed it with the ID properties of its naming containers. In short, a Web control with an ID of txtName can get rendered into an HTML element with a client-side id like ctl00_MainContent_txtName.

This default translation from the server-side ID property value to the rendered client-side id attribute can introduce challenges when trying to access an HTML element via JavaScript, which is typically done by id, as the page developer building the web page and writing the JavaScript does not know what the id value of the rendered Web control will be at design time. (The client-side id value can be determined at runtime via the Web control's ClientID property.)

ASP.NET 4.0 affords page developers much greater flexibility in how Web controls render their ID property into a client-side id. This article starts with an explanation as to why and how ASP.NET translates the server-side ID value into the client-side id value and then shows how to take control of this process using ASP.NET 4.0. Read on to learn more!

- continued -

ASP.NET 4.0's Status and Release Date
At the time of this writing, ASP.NET 4.0 is currently available a Release Candidate (RC). You can download the .NET Framework 4.0 and Visual Studio 2010 RC or wait until the software is officially released. Currently, ASP.NET 4.0 is scheduled to be released in April 2010.

Why ASP.NET Modifies Client-Side id Values


Each HTML element in a web page may contain an id attribute. The id attribute uniquely identifies an element within the web page; no two HTML elements should have the same id attribute value. Commonly, the id attribute is used to access an HTML element from JavaScript. For example, JavaScript's document.getElementById(id) function searches the DOM for an HTML element with an id value of id and returns a reference to that element.

Web controls in ASP.NET also have ID values that uniquely identify each Web control. At first blush it may seem that the ID property values of an ASP.NET Web control could be literally translated into the client-side id value, since both must be unique. But keep in mind that several ASP.NET files may be used in composing a single web page. For instance, when a browser requests the web page Default.aspx, the ASP.NET engine may be composing the resulting web page from a multitude of sources, including: a master page (Site.master); a content page (Default.aspx); and, perhaps, one or more User Controls. It is certainly possible that a Web control in the master page could have the same ID value as a Web control in the content page, or in a User Control. Consequently, ASP.NET needs some mechanism to ensure that the client-side id values it generates are unique for the entire web page.

The way this is accomplished is by translating the server-side ID value into a client-side id value using the following sequence of steps:

  1. Set the client-side id value to the name of the Web control's server-side ID value.
  2. Inspect the Web control's parent controls. For each naming container encountered, prefix the client-side id value with the ID property of the naming container.
Certain types of Web controls are classified as naming containers. For example, master pages and ContentPlaceHolder controls are both naming containers. To see the effect naming containers have in altering the client-side id rendered, create a simple ASP.NET site with a master page and a content page. Add a TextBox to the content page and set its server-side ID property value to something simple, like txtName. Next, visit the page through a browser and do a View/Source. You'll see that the TextBox control's rendered client-side id value is something like ctl00_ContentPlaceHolder1_txtName. This is because the TextBox is within two naming containers: the ContentPlaceHolder control in the content page (named ContentPlaceHolder1, by default) and the master page itself (named ctl00). The figure below illustrates the control hierarchy, showing the naming containers in peach. (This figure is from the Control ID Naming in Content Pages tutorial, which is one of ten tutorials in my Master Pages tutorials series.)

The naming containers, shown in peach, affect the rendered client-side id attribute.

Most Web controls that display data or use templates operate as naming containers, as well. To understand why, consider a GridView that contains a TemplateField with a Label named lblName. The GridView renders this template once for each record bound to it, so if four records are bound to the GridView then there will be four Label controls rendered in the page. Imagine what would happen if the GridView and its rows were not a naming container - the four Label controls would end up having the same id attribute value. To prevent this naming conflict the GridView and its rows are made naming containers, resulting in rendered id attributes like:

  • GridViewID_ctl00_lblName
  • GridViewID_ctl01_lblName
  • GridViewID_ctl02_lblName
  • ...

You can tell that the GridView is a naming container because its ID - GridViewID - appears in the rendered client-side id attribute. The rows of the GridView are also naming containers. They appear in the client-side id with the auto-generated names ctl00, ctl01, and so on.

Retrieving the Client-Side id Value At Runtime


At design time the page developer can compute the client-side id value that will be rendered by a Web control, but it is unwise to assume that that computed id value will always hold. If another developer changes the IDs of one of the naming containers, or adds a new naming container somewhere in the control hierarchy or removes an existing one, the client-side id value rendered by a Web control will change. It is possible to determine the client-side id attribute value that will be rendered via the Web control's ClientID property, and is typically used when crafting JavaScript that needs to reference a Web control.

The following JavaScript snippet references a textbox HTML element and sets focus to the element. The textbox in the web page was actually a TextBox Web control; to determine the client-side id attribute the TextBox control's ClientID property is used.

<script type="text/javascript">
   ...
   var txt = document.getElementById('<%=TextBoxID.ClientID%>');
   txt.focus();
   ...
</script>

The syntax <%=TextBoxID.ClientID%> emits the client-side id value directly into the markup. The browser will be sent markup like so:

<script type="text/javascript">
   ...
   var txt = document.getElementById('ctl00_ContentPlaceHolder1_TextBoxID');
   txt.focus();
   ...
</script>

Taking Control Of The Rendered Client-Side id Value With ASP.NET 4.0


ASP.NET 4.0 grants page developers finer control over how a Web control renders its client-side id value via a new property, ClientIDMode. The ClientIDMode property is defined on the Control class, meaning that it applies to all Web controls in the ASP.NET Toolbox, including the Page itself.

The ClientIDMode property can be set to one of the following four values:

  • AutoID - uses the same series of steps for computing the client-side id value as in previous versions of ASP.NET.
  • Static - the client-side id value is the same as the server-side ID property value.
  • Predictable - used for controls with repeating templates, like the GridView or ListView. When selected, the client-side id value is concatenated with a specified ClientIDRowSuffix property, which is the name of a data field whose value is appended to the generated client-side id.
  • Inherit - specifies that the control's client-side id value be generated the same way as its parent.
The ClientIDMode property can be set at different levels of the application. You can set it at the Web control level like so:

<asp:Label ID="..." runat="server" ClientIDMode="..." />

If not specified, a Web control's default ClientIDMode setting defaults to Inherit.

Alternatively, you can set the default value for the ClientIDMode property for all controls on a specific page via the page's @Page directive (or for all controls in a specific User Control via the User Control's @Control directive):

<@ Page ClientIDMode="..." Title="" Language="C#" ... />

If not specified, the default ClientIDMode for the page is AutoID.

Or you can set the default value for the ClientIDMode property for all controls on all pages using the <pages> element in Web.config:

<configuration>
   <system.web>
      <pages clientIDMode="...">
         ...
      </pages>
   </system.web>
</configuration>

The AutoID and Inherit settings are pretty straightforward, I think. More interesting are the Static and Predictable settings, which we'll examine throughout the remainder of this article. The code that we'll be looking at is available for download at the end of this article.

Using the ClientIDMode's Static Setting


The Static setting works precisely as described: when set, there is a literal translation from the Web control's ID property to the rendered HTML element's id attribute. Earlier in this article I gave an example of how ASP.NET renders the client-side id for a TextBox control in a content page with its ID property set to txtName. By default, ASP.NET will set the client-side id to a value that includes the IDs of the control's naming containers. In this case, txtName may get translated into something like ctl00_MainContent_txtName.

The Static setting ensures that the generated client-side id attribute value is identical to the server-side ID value. For instance, were we to set the txtName TextBox's ClientIDMode property ot Static like so:

<asp:TextBox ID="txtName" runat="server" ClientIDMode="Static" />

We'd end up with rendered markup like so:

<input name="ctl00$ContentPlaceHolder1$TextBox2" type="text" id="txtName" />

Note that the client-side id value mirrors the server-side ID value. Note that while the client-side id value has followed the naming guidelines, the name attribute remains a lengthy string that includes the naming containers IDs. The reason is because on postback the browser sends back the name/value pairs for the <input> elements. ASP.NET needs to fully qualified name to be able to determine the TextBox in the ASP.NET page that the submitted value belongs to.

When using the Static setting you must take care to ensure that you are not creating HTML elements with identical id attributes. In its ASP.NET 4 and Visual Studio 2010 Web Development Overview white paper Microsoft points out:

It is up to you to make sure that the rendered control IDs are unique. If they are not, it can break any functionality that requires unique IDs for individual HTML elements, such as the client document.getElementById function.

Using the ClientIDMode's Predictable Setting


When using the AutoID setting, any naming containers without an explicitly specified ID value have an auto-generated ID value computed and used when crafting the client-side id attribute. For example, the default behavior for crafting the id attribute for a TextBox control in a content page is to include the name of the master page. Since the master page doesn't have an ID value explicitly specified, ASP.NET names it ctl00 since it is the first such auto-named control. This results in a client-side id like ctl00_ContentPlaceHolderID_TextBoxID. The problem is that this client-side id isn't predictable. It can change if other naming containers without explicitly defined IDs are added to the page. For instance, it might become ctl01_MainContent_txtName.

To better allow for predictable, yet unique client id values, use the Predictable setting. In its simplest incarnation, the Predictable setting simply omits any auto-named naming container IDs when crafting the client-side id. Returning to our txtName example, when using the Predictable setting the rendered client-side id value would be MainContent_txtName. (Note how the ctl00 prefix has been omitted.)

Recall that when rendering a templated control, like a GridView, the GridView control and each of its row operate as naming contains and are used to assign a unique id attribute for each control in each rendered grid row. However, the GridView's rows' IDs are not explicitly set, so they use auto-generated values like so:

  • GridViewID_ctl00_lblName
  • GridViewID_ctl01_lblName
  • GridViewID_ctl02_lblName
  • ...
But if we use the Static setting these names would vanish, leaving us with the following client-side id values:
  • GridViewID_lblName
  • GridViewID_lblName
  • GridViewID_lblName
  • ...
This is undesirable because now the id names are no longer unique. To rectify this, the Predictable setting automatically appends the index of the row number to the client-side id. Consequently, instead of rendering the above ids, the Predictable setting will generate the following:
  • GridViewID_lblName_0
  • GridViewID_lblName_1
  • GridViewID_lblName_2
  • ...
Alternatively, you can specify one or more data fields whose values you want to use in place of the row index via the data Web control's ClientIDRowSuffix property. Take the following GridView, which displays the categories in the Northwind database. Note that its ClientIDMode is set to Predictable and its ClientIDRowSuffix is set to CategoryName, one of the names of the data fields bound to the grid. Also note that this GridView contains a Label Web control in a TemplateField named lblCategoryName.

<asp:GridView ID="gvCategories" runat="server" AutoGenerateColumns="False"
   ClientIDMode="Predictable" ClientIDRowSuffix="CategoryName" ...>
   <Columns>
      <asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
         <ItemTemplate>
            <asp:Label ID="lblCategoryName" runat="server" Font-Bold="True"
               Text='<%# Bind("CategoryName") %>'></asp:Label>

         </ItemTemplate>
      </asp:TemplateField>
      <asp:BoundField DataField="Description" HeaderText="Description"
         SortExpression="Description" />
   </Columns>
</asp:GridView>

When the above GridView is rendered, it will generate client-side ids for the CategoryName Label like so:

  • ContentPlaceHolder1_gvCategories_lblCategoryName_Beverages
  • ContentPlaceHolder1_gvCategories_lblCategoryName_Condiments
  • ContentPlaceHolder1_gvCategories_lblCategoryName_Confections
  • ...
Note how all auto-generated naming control IDs are omitted from the rendered client-side id and how the name of the category is appended. Typically, you'll set the ClientIDRowSuffix to the primary key field(s) to ensure a unique client-side id value. (In theory, two categories could have the same CategoryName value, in which case the rendered id values on the page would not be unique.)

The above example shows the ClientIDRowSuffix assigned to a single data field, but you can set this property to include multiple data fields - just separate each one with a comma. When specifying multiple data fields each data field value is appended to the id and separated by an underscore.

The Effect Of Parent Naming Containers When Using The Predictable Setting


Bear in mind that the Predictable ClientIDMode setting respects the ClientIDMode setting of its parent naming containers. This behavior can be used to establish a base from which the naming container is crafted. Imagine that we had a User Control that contained a GridView and that this User Control was added to a number of content pages in an ASP.NET website. Using the AutoID setting (the default), the client-side ids generated by the controls in the GridView would include all naming container IDs, including auto-generated ones. This includes the master page, the ContentPlaceHolder, the User Control, the GridView, and the GridView's rows, resulting in id attributes like:
  • ctl00_ContentPlaceHolder1_UserControlID_GridViewID_ctl00_lblName
  • ctl00_ContentPlaceHolder1_UserControlID_GridViewID_ctl01_lblName
  • ctl00_ContentPlaceHolder1_UserControlID_GridViewID_ctl02_lblName
  • ...
If you set the GridView's ClientIDMode to Predictable then the auto-generated IDs would be removed and the row index or specified data field value would be appended, resulting in ids like the following:
  • ContentPlaceHolder1_UserControlID_GridViewID_lblName_0
  • ContentPlaceHolder1_UserControlID_GridViewID_lblName_1
  • ContentPlaceHolder1_UserControlID_GridViewID_lblName_2
  • ...
Note how the ContentPlaceHolder ID is still part of the rendered id. We could omit this portion of the id by setting the User Control's ClientIDMode to Static, which can be done via the User Control's @Control directive. This would result in ids like so:
  • UserControlID_GridViewID_lblName_0
  • UserControlID_GridViewID_lblName_1
  • UserControlID_GridViewID_lblName_2
  • ...
Understand that the rendered id is crafted recursively. In short, the lblName control says, "My client-side id will be my ID value prefixed with whatever my parent's client-side id happens to be." lblName's parent does the same thing, as does its parent, and its parent, all the way up to the root. When the User Control's ClientIDMode is set to Static, the recursion essentially stops at the User Control because the User Control no longer says, "Give me my parent's id." Instead, it says, "Hey, I'm set to Static, I'll just use my own server-side ID value as the client-side id," thereby short-circuiting the recursion.

Also, keep in mind that this logic can be applied to any naming container, not just User Controls.

Conclusion


Since its inception, ASP.NET has not offered developers any control over the client-side id attribute values generated by its Web controls. The auto-generated id values are, at times, unnecessarily long. While the client-side id value can be determined programmatically at runtime, there's no way to safely know it at design time, which can complicate the JavaScript you need to write to access the rendered HTML element.

ASP.NET 4.0 gives Web Forms developers greater control over the id values rendered by Web controls thanks to the new ClientIDMode property. When set to AutoID, Web controls generate their client-side id values just like they did in previous versions of ASP.NET. Setting it to Static causes the Web control to emit a client-side id value that's identical to its server-side ID property value. The Predictable setting strips creates an id attribute value that omits all auto-named naming containers and, for data Web controls, appends either the row index or the values of one or more data fields as specified by the data Web control's ClientIDRowSuffix property.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the code used in this article

    Further Readings:


  • Setting Client IDs in ASP.NET 4.0
  • Control ID Naming in Content Pages (VB version) | C# version
  • ASP.NET 4.0: Way too much information on Control IDs and ASP.NET 4.0 Client Id Enhancements


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article