Welcome to part 8 of my tour through ASP.NET 4.0. In this episode, we’ll look at a new feature that directly affects two things: page size (and thus scalability and perceived performance) and the use of JavaScript APIs such jQuery. It’s called ClientIDMode.

The Problem

Consider a typical development situation. You’re building a page which inherits its structure from a master page or two. The page itself contains a custom user control which its own set of server-side controls in it.  In this particular example, let’s have a simple table.

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="UserControl.ascx.cs" Inherits="UserControl" %>
<asp:Table runat="server" ID="tblInfo">
  <asp:TableRow runat="server" ID="tblTopRow">
    <asp:TableCell runat="server" ID="tdTopLeft">
      <asp:Label runat="server" ID="lblChoices" Text="Choices" />
    </asp:TableCell>
    <asp:TableCell runat="server" ID="tdTopRight">
      <asp:DropDownList runat="server" ID="ddlChoiceList">
        <asp:ListItem Text="Choice One" />
        <asp:ListItem Text="Choice Two" />
        <asp:ListItem Text="Choice Three" />
      </asp:DropDownList>
    </asp:TableCell>
  </asp:TableRow>
</asp:Table>

On running this page, we’ll see that the generated HTML for the table is perhaps not as succinct as we might otherwise wish. (Note that main and submain are the names of ContentPlaceholder controls in the master pages that the page containing the table is based on.)

<table id="ctl00_ctl00_main_submain_uc1Control_tblInfo" border="0"> 
  <tr id="ctl00_ctl00_main_submain_uc1Control_tblTopRow"> 
    <td id="ctl00_ctl00_main_submain_uc1Control_tdTopLeft">
      <span id="ctl00_ctl00_main_submain_uc1Control_lblChoices">Choices</span>
    </td>
    <td id="ctl00_ctl00_main_submain_uc1Control_tdTopRight">
      <select name="ctl00$ctl00$main$submain$uc1Control$ddlChoiceList" 
              id="ctl00_ctl00_main_submain_uc1Control_ddlChoiceList"> 
        <option value="Choice One">Choice One</option> 
        <option value="Choice Two">Choice Two</option> 
        <option value="Choice Three">Choice Three</option> 
      </select>
    </td> 
  </tr> 
</table> 

When ASP.NET was first created, it was decided that it should autogenerate IDs for each element on the page based on the chain of their parent naming containers. This would mean that you would not be able to generate an HTML page where two elements had the same ID. As you can see above, the IDs generated in this way are neither particularly short nor semantically meaningful, but they are unique.

The length and meaning of client-side IDs didn’t matter back in 2000, but the invention and subsequent mass adoption of AJAX a few years ago has repopularised client-side scripting on the page. Scripting libraries like jQuery have only sped this tide of JavaScript up further thanks to the ease with which quite complex, cross-browser functionality can be achieved. And if there’s one thing that JavaScript uses a lot, it’s the ID of elements on a page to identify them.

The problem with ASP.NET up to v3.5 then is that there is no way to dictate the client-side ID of an HTML element and it’s hard to know exactly what the ‘AutoID’ will be for use in your scripts. A fairly complex page with several dozen controls on it will also be significantly larger than it needs to be because of AutoIDs and on high-volume sites, that means extra bandwidth used unnecessarily (and so more cost) and a perceived performance hit on the site from users with slower connections waiting for the page to download.

The Solution

It is possible to work around this issue in several ways, but with the new ClientIDMode property in ASP.NET 4.0, we’re presented with a way to instruct ASP.NET to generate element IDs in one of three ways. This new property can be set for any webform control, page, or master page. It can also be set as a property of the <system.web>/<pages> element in web.config. 

ClientIDMode has four possible values

  • AutoID has ASP.NET generate IDs as it does in v3.5 and earlier versions.
  • Static has ASP.NET use exactly the same ID given to the server control for its client-side ID.
  • Predictable has ASP.NET try to generate IDs that are guessable from looking at the structure of the page. It tries to strike a happy medium between AutoID and Static. It is also the default value for ClientIDMode in ASP.NET 4.0.
  • Inherit has ASP.NET generate a client-side ID for the page or control using the same method as its parent.

Let’s taking the example code above, and see how the <table> element is rendered in each ClientIDMode. First, let’s set the <asp:Table> control’s ClientIDMode to Predictable. As noted, this is the default in ASP.NET 4.0, so we need add no extra property. The resultant HTML looks like this:

<table id="main_submain_uc1Control_tblInfo">
  <tr id="main_submain_uc1Control_tblTopRow">
    <td id="main_submain_uc1Control_tdTopLeft">
      <span id="main_submain_uc1Control_lblChoices">Choices</span>
    </td>
    <td id="main_submain_uc1Control_tdTopRight">
      <select name="ctl00$ctl00$main$submain$uc1Control$ddlChoiceList" 
              id="main_submain_uc1Control_ddlChoiceList">
        <option value="Choice One">Choice One</option>
        <option value="Choice Two">Choice Two</option>
        <option value="Choice Three">Choice Three</option>
      </select>
    </td>
  </tr>
</table>

As you can see, the resultant IDs are mostly the same as the v3.5 ones but are no longer prepended with a (random) number of ctl00_ strings. Now let’s set ClientIDMode on the table to AutoID.

<asp:Table runat="server" ID="tblInfo" ClientIDMode="AutoID">

This should generate old-style IDs for everything again as would happen in v3.5, but actually something different happens.

<table id="ctl00_ctl00_main_submain_uc1Control_tblInfo">
  <tr id="main_submain_uc1Control_tblTopRow">
    <td id="main_submain_uc1Control_tdTopLeft">
      <span id="main_submain_uc1Control_lblChoices">Choices</span>
    </td>
    <td id="main_submain_uc1Control_tdTopRight">
      <select name="ctl00$ctl00$main$submain$uc1Control$ddlChoiceList" 
              id="main_submain_uc1Control_ddlChoiceList">
        <option value="Choice One">Choice One</option>
        <option value="Choice Two">Choice Two</option>
        <option value="Choice Three">Choice Three</option>
      </select>
    </td>
  </tr>
</table>

As you can see, only the ID for the table itself has been generated in the v3.5 way. Every other ID is still generated using the ‘Predictable Mode’ algorithm. Now try setting ClientIDMode to AutoID as an attribute of the @Master, @Page, or @Control directive. For example

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="UserControl.ascx.cs" Inherits="UserControl" 
    ClientIDMode="AutoID" %>

Sure enough, all the IDs for the table and its children have changed back to the v3.5 naming style as seen earlier. Try setting ClientIDMode to Static.

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="UserControl.ascx.cs" Inherits="UserControl" 
    ClientIDMode="Static" %>

Straight to the mark, the HTML element IDs now match their respective server-side control IDs exactly (but their name attributes are still generated as in v3.5-)

<table id="tblInfo">
  <tr id="tblTopRow">
    <td id="tdTopLeft">
      <span id="lblChoices">Choices</span>
    </td>
    <td id="tdTopRight">
      <select name="ctl00$ctl00$main$submain$uc1Control$ddlChoiceList" 
              id="ddlChoiceList">
        <option value="Choice One">Choice One</option>
        <option value="Choice Two">Choice Two</option>
        <option value="Choice Three">Choice Three</option>
      </select>
    </td>
  </tr>
</table>

Move the ClientIDMode attribute back from the @Control directive to the <asp:Table> element and you’ll see only the <table> element return its shorter ID with the rest returning to their ‘predictable’ form.

The rule then is that ClientIDMode only changes the ID generation mode for the control on which it is set. It does not affect the ID generation mode for its child controls. The only way to change the default ClientIDMode for more than one control at a time is to set it in the @Control, @Page, or @Master directives or in web.config. The only exception to this is when setting ClientIDMode on templated list controls as we’ll see later.

If you already use jQuery in your v3.5 sites and want to upgrade them to use the v4 Framework, you would be well may need to default the ClientIDMode for the site to AutoID in web.config until you’ve checked your code works against predictable mode.

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

In general however, a good approach to using ClientIDMode is to leave 'Predictable' as the default and for those few controls which are targeted by JQuery, set ClientIDMode to Static. ASP.NET will not interfere with an ID set as static so if you inadvertently set two or more identical IDs in a page, you’ll have to identify and disambiguate them yourself.

Using ClientIDRowSuffix With Templated List Controls

Consider the case where you’re binding some data into a Templated List control. For example a ListView.

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="DataBoundControls.aspx.cs" Inherits="DataBoundControls" %>
...
<body>
  <form id="form1" runat="server">
  <div>
    <asp:ListView runat="server" ID="lvwDemo">
      <LayoutTemplate>
        <table>
          <asp:PlaceHolder runat="server" ID="itemPlaceholder" />
        </table>
      </LayoutTemplate>
      <ItemTemplate>
        <tr>
          <td>
            <asp:Label runat="server" ID="lblItemName" Text='<%#Eval("Name") %>' />
          </td>
        </tr>
      </ItemTemplate>
    </asp:ListView>
  </div>
  </form>
</body>
</html>

And in the code-behind, a simple List is bound to the ListView.

protected void Page_Load(object sender, EventArgs e)
{
  List<Item> items = new List<Item> {
    new Item { Id = 11, Name = "Cat" },
    new Item { Id = 12, Name = "Dog" },
    new Item { Id = 13, Name = "Dragon" }
  };

  lvwDemo.DataSource = items;
  lvwDemo.DataBind();
}

With all defaults left on, a ClientIDMode of ‘Predictable’ produces the following HTML for the table templated in the ListView.

<table>
  <tr><td><span id="lvwDemo_lblItemName_0">Cat</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_1">Dog</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_2">Dragon</span></td></tr>
</table>

Note that an auto-incremented number is used to make the id for the span unique. You can replace that auto-incremented number with something more meaningful using the new ClientIDRowSuffix property on the ListView. The defines the data field (typically the primary key) whose value is suffixed to the row ID to make it unique. If we set it to Id on the ListView…

<asp:ListView runat="server" ID="lvwDemo" ClientIDRowSuffix="Id">

the Id for each Item object in the list is used in the span ids.

<table>
  <tr><td><span id="lvwDemo_lblItemName_11">Cat</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_12">Dog</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_13">Dragon</span></td></tr>
</table>

We could set it to Name instead of Id, as long as we can guarantee that Name is a unique field and get the following HTML.

<table>
  <tr><td><span id="lvwDemo_lblItemName_Cat">Cat</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_Dog">Dog</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_Dragon">Dragon</span></td></tr>
</table>

ClientIDRowSuffix actually takes a string[], so you can set multiple fields to this property should you wish. FOr example,

<asp:ListView runat="server" ID="lvwDemo" ClientIDRowSuffix="Id, Name">

will generate the following.

<table>
  <tr><td><span id="lvwDemo_lblItemName_11_Cat">Cat</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_12_Dog">Dog</span></td></tr>
  <tr><td><span id="lvwDemo_lblItemName_13_Dragon">Dragon</span></td></tr>
</table>

A second new property – ClientIDRowSuffixDataKeys – will return the values used to generate the client ID for a given row.

ClientIDRowSuffix and Static\AutoID Modes Don’t Mix

Be aware that ClientIDRowSuffix has no effect on either AutoID or Static ID modes and also that server-side controls placed within templates DO inherit the ClientIDMode of their parent control – in this case, the ListView or GridView in whose templates they are placed. For example, should you set ClientIDMode to Static on the ListView control in the current example…

<asp:ListView runat="server" ID="lvwDemo" ClientIDRowSuffix="Id" ClientIDMode="Static">

every span id will remain identical despite the presence of ClientIDRowSuffix.

<table>
  <tr><td><span id="lblItemName">Cat</span></td></tr>
  <tr><td><span id="lblItemName">Dog</span></td></tr>
  <tr><td><span id="lblItemName">Dragon</span></td></tr>
</table>

If you set it AutoID rather than Static, this is not a problem as AutoIDs are unique by design but there’s no influence on them via ClientIDRowSuffix either.

<table>
  <tr><td><span id="lvwDemo_ctrl0_lblItemName">Cat</span></td></tr>
  <tr><td><span id="lvwDemo_ctrl1_lblItemName">Dog</span></td></tr>
  <tr><td><span id="lvwDemo_ctrl2_lblItemName">Dragon</span></td></tr>
</table>

You could set the ClientIDMode to Predictable on the Label control in your template

<asp:ListView runat="server" ID="lvwDemo" ClientIdRowSuffix="Id" ClientIDMode="Static">
  ...
  <ItemTemplate>
    <tr>
      <td>
        <asp:Label ClientIDMode="Predictable"
          runat="server" ID="lblItemName" Text='<%#Eval("Name") %>' />
      </td>
    </tr>
  </ItemTemplate>
</asp:ListView>

but then you’ll notice that the span ids become slightly less predictable than you might like.

<table>
  <tr><td><span id="ctrl0_lblItemName_11">Cat</span></td></tr>
  <tr><td><span id="ctrl1_lblItemName_12">Dog</span></td></tr>
  <tr><td><span id="ctrl2_lblItemName_13">Dragon</span></td></tr>
</table>

What We’ve Learnt

In this episode, we’ve seen that the new ClientIDMode property gives us more control over the client-side IDs generated by server-side controls. This helps both with keeping the overall size of the page down and for referencing the correct client-side IDs in our Javascript code. We’ve seen when ClientIDMode affects or does not affect the child controls of the one on which it is set and also how ClientIDRowSuffix augments predictable mode when it comes to generating IDs for controls within the templates of a databound list control. We also noted that ClientIDRowSuffixDataKeys returns the values of the keys set in ClientIDRowSuffix used to generate a unique ID for a given row.