ASP.NET 4.0 ClientID Overview


Introduction

One of the new features being added to version 4.0 of ASP.NET is the ability to control the client side IDs that are generated by the framework.  Previously the framework would modify the client side IDs to uniquely identify each control.  This some times left you with the ID you defined in markup or sometimes left you with something that looks like this, “ctl00_MasterPageBody_ctl01_Textbox1.”

The Problem

The modification of the client side id property works great to ensure that each element is uniquely identified, however, to anyone that has tried to do any sort of client side scripting this becomes very frustrating. Chances are that if you have worked in ASP.NET for any time at all you have run into this issue.  The problem is that until runtime you do not what the client side ID could be, making it difficult to do any kind of client side scripting.  In addition any modification of the page, adding removing controls, can result in a different client side ID being generated.

Old Solution

Again if you have worked with ASP.NET for any amount of time you know there is a work around for this issue.  Each control has a property called ClientID that is a read only and supplies the unique client side ID.  You can use this in a code behind when dynamically adding scripts, or more commonly use inline code (old ASP style) to supply the value to and client side scripts.

<script type="text/javascript">
    function DoSomething(){
        alert('<%= Control.ClientID %>');
    }
</script>

ASP.NET 4.0 Solution

First off let me start by explaining why we decided to tackle this problem in version 4.0 of the framework.  While we provided a way of supplying the developer with the client side ID, with the growth of client side scripting this solution has become some what hacky.  There is not really a clean way to use this with lots of controls and lots of external script files.  Also it might have had something to do with the developer asking for control over this.  Developers do love to have control of everything, weather they use it or not, it’s just our nature :) The solution that we came up has four ‘modes’ that a user can use giving them everything from existing behavior to full control.  The controls ID property is modified according to the ClientIDMode mode and then used as the client side id.

Modes and what they do

There is now a new property on every control (this includes pages and master pages as they inherit from control) called ClientIDMode that is used to select the behavior of the client side ID.

<asp:Label ID="Label1" runat="server" ClientIDMode="[Mode Type]" />

The Mode Types

  • Legacy: The default value if ClientIDMode is not set anywhere in the control hierarchy.  This causes client side IDs to behave the way they did in version 2.0 (3.0 and 3.5 did not change this code path) of the framework. This mode will generate an ID similar to “ctl00_MasterPageBody_ctl01_Textbox1.”
  • Inherit: This is the default behavior for every control.  This looks to the controls parent to get its value for ClientIDMode.  You do not need to set this on every control as it is the default, this is used only when the ClientIDMode has been changed and the new desired behavior is to inherit from the controls parent.
  • Static: This mode does exactly what you think it would, it makes the client side ID static. Meaning that what you put for the ID is what will be used for the client side ID.  Warning, this means that if a static ClientIDMode is used in a repeating control the developer is responsible for ensuring client side ID uniqueness.
  • Predictable: This mode is used when the framework needs to ensure uniqueness but it needs to be done so in a predictable way.  The most common use for this mode is on databound controls.  The framework will traverse the control hierarchy prefixing the supplied ID with it’s parent control ID until it reaches a control in the hierarchy whose ClientIDMode is defined as static.  In the event that the control is placed inside a databound control a suffix with a value that identifies that instance will also be added to the supplied ID.  The ClientIDRowSuffix property is used to control the value that will be used as a suffix (see samples).  This mode will generate an ID similar to “Gridview1_Label1_0”

Samples

 

Legacy Mode

Legacy mode is pretty straight forward, it generates a client side ID the way that it had in version 2.0 of the framework.

markup:

<asp :TextBox ID ="txtEcho" runat ="server" Width ="65%" ClientIDMode ="Legacy" /> 

output:

<input id="ctl00_MasterPageBody_ctl00_txtEcho" style="width: 65%" name="ctl00$MasterPageBody$ctl00$txtEcho" />

Static Mode

Static is the most basic of all ClientIDMode modes, what you give for the ID is what you get for the client side ID. Once again a warning that if a static ClientIDMode is used inside of a repeated control it is the developer’s responsibility to ensure client side ID uniqueness.

markup:

<asp:TextBox ID="txtEcho2" runat="server" Width="65%" ClientIDMode="Static" />

output:

<input id="txtEcho2" style="width: 65%" name="ctl00$MasterPageBody$ctl00$txtEcho2" />

Predictable Mode

Predictable mode really tackles the heart of the problem.  The framework previously generated it’s unique IDs to prevent ID collisions and the most common place for these types of collisions are inside databound controls.  Predictable mode is really designed to work with databound controls but does not have to.  There is three ways to uses the predictable mode, each one of these is defined through the ClientIDRowSuffix property that specifies the suffix for each instance.  The ClientIDRowSuffix uses values from the control’s datakeys collection, so if the control does not have a datakeys collection this property is not viable.  If this property is not set or is not available the row index will be used in it’s place.

1. With no ClientIDRowSuffix defined, this is also the behavior for databound controls without a datakeys collection e.g. Repeater Control.  Notice that the framework has traversed the control hierarchy and prefixed the ID with the parent’s ID and suffixed the ID with row index.

markup:

<asp:GridView ID="EmployeesNoSuffix" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" >
    <Columns>
        <asp:TemplateField HeaderText="ID">
            <ItemTemplate>
                <asp:Label ID="EmployeeID" runat="server" Text='<%# Eval("ID") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <asp:Label ID="EmployeeName" runat="server" Text='<%# Eval("Name") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

output:

<table id="EmployeesNoSuffix" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
    <tbody>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
        </tr>
        <tr>
            <td><span id="EmployeesNoSuffix_EmployeeID_0">1</span></td>
            <td><span id="EmployeesNoSuffix_EmployeeName_0">EmployeeName1</span></td>
        </tr>
        ...
        <tr>
            <td><span id="EmployeesNoSuffix_EmployeeID_8">9</span></td>
            <td><span id="EmployeesNoSuffix_EmployeeName_8">EmployeeName9</span></td>
        </tr>
    </tbody>
</table>

2. With a ClientIDRowSuffix defined, this looks in the control’s datakeys collection for the value and then suffixes the ID with that value.

markup:

<asp:GridView ID="EmployeesSuffix" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" ClientIDRowSuffix="ID" >
    <Columns>
        <asp:TemplateField HeaderText="ID">
            <ItemTemplate>
                <asp:Label ID="EmployeeID" runat="server" Text='<%# Eval("ID") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <asp:Label ID="EmployeeName" runat="server" Text='<%# Eval("Name") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

output:

<table id="EmployeesSuffix" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
    <tbody>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
        </tr>
        <tr>
            <td><span id="EmployeesSuffix_EmployeeID_1">1</span></td>
            <td><span id="EmployeesSuffix_EmployeeName_1">EmployeeName1</span></td>
        </tr>
        ...
        <tr>
            <td><span id="EmployeesSuffix_EmployeeID_9">9</span></td>
            <td><span id="EmployeesSuffix_EmployeeName_9">EmployeeName9</span></td>
        </tr>
    </tbody>
</table>

3. With a ClientIDRowSuffix defined, but instead of just one value a compound value will be used.  Exhibits the same behavior as one value but it will suffix both values onto the ID.

markup:

<asp:GridView ID="EmployeesCompSuffix" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" ClientIDRowSuffix="ID, Name" >
    <Columns>
        <asp:TemplateField HeaderText="ID">
            <ItemTemplate>
                <asp:Label ID="EmployeeID" runat="server" Text='<%# Eval("ID") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <asp:Label ID="EmployeeName" runat="server" Text='<%# Eval("Name") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

output:

<table id="EmployeesCompSuffix" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
    <tbody>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
        </tr>
        <tr>
            <td><span id="EmployeesCompSuffix_EmployeeID_1_EmployeeName1">1</span></td>
            <td><span id="EmployeesCompSuffix_EmployeeName_1_EmployeeName1">EmployeeName1</span></td>
        </tr>
        ...
        <tr>
            <td><span id="EmployeesCompSuffix_EmployeeID_9_EmployeeName9">9</span></td>
            <td><span id="EmployeesCompSuffix_EmployeeName_9_EmployeeName9">EmployeeName9</span></td>
        </tr>
    </tbody>
</table>

Summary

The ability to fully control the client side IDs that are generated by the framework is a request that has not generated much noise but everyone seems to want it when you mention it.  We believe that we have found a good solution to the request and think that it adds some much need functionality for developer that use lots of client side scripting.  There is an early preview and a walk through of this feature in CTP build that we released at PDC 2008.  For more information and a much more detailed description of this feature read Scott Galloway’s blog post.

author: Matthew M. Osborn | posted @ Tuesday, January 06, 2009 5:41 PM | Feedback (0)

Running the Lightweight Test Automation Framework for ASP.NET from a separate application


In some instances it might be best if your tests did not run in the same application as the website being tested.  This is where the “IApplicationPathFinder” interface comes into play.  If you implement this interface you will see that it only has one method “GetApplicationpath.”  What this does is allow you to specify a prefix of sorts for the navigate method.  When you call navigate on a HTMLPage it uses a ApplicationPathFinder to get the location it should look for the website at.  For example, the framework will implement this interface and simply return the request path by default.  Creating your own ApplicationPathFinder class allows you to specify where to look for the website, there is nothing stopping you from telling it to look for “http://foo.com.”  The fallowing code shows a simple implementation of the IApplicationPathFinder interface, that points to a second test application. This code is place in the DrivePage that is located in the “Test” folder.

<script runat="server">
    public class TestApplicationPathFinder : Microsoft.Web.Testing.Light.IApplicationPathFinder
    {
        public string GetApplicationPath()
        {
              return "http://localhost/TestApp2";
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        Microsoft.Web.Testing.Light.ServiceLocator.ApplicationPathFinder = 
		new TestApplicationPathFinder();
    }
</script>

Okay, lets take a look at what is really going on here in the code.  First I choose to include the  implementation of the IApplicationPathFinder interface in the DriverPage, however, this could easily be moved to its own file/assembly.  Other than that the code is pretty straight forward, set the ApplicationPathFinder property on the ServiceLocator to an instance of your implementation.

There are numerous reason you might want to run the the test application separately from the site you are testing.  Here is a simple implementation that allows you to store your test application in a central location and pass the location of the site to be test to it.  You could use a URL like “http://localhost/Test?tag=localhost2&filter=true&path=http%3A%2F%2Flocalhost2&run=true” to the navigate and have the test page look for the site at “http://localhost2,” show only the test with the tag name “localhost2,” and run them once navigation is complete.  You could see how you could use this to set up a regression test bed, by storing all your tests in one location and pointing them to the proper servers for the pages.

<script runat="server">
    public class TestApplicationPathFinder : Microsoft.Web.Testing.Light.IApplicationPathFinder
    {
        public string Path { get; set; }

        public TestApplicationPathFinder(string path)
        {
            Path = path;
        }
        
        public string GetApplicationPath()
        {
            return Path;
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        string path = this.Request.Params["path"];
        if(!string.IsNullOrEmpty(path))
            Microsoft.Web.Testing.Light.ServiceLocator.ApplicationPathFinder = 
		new TestApplicationPathFinder(path);
    }
</script>
Hopefully this gives you some insight into the power that the Lightweight Test Automation Framework for ASP.NET has and how you can leverage that power.  You can download the framework here. Please let me know if you have any questions.

author: Matthew M. Osborn | posted @ Wednesday, December 03, 2008 10:34 AM | Feedback (0)

Running the Lightweight Test Automation Framework for ASP.NET from a separate application


In some instances it might be best if your tests did not run in the same application as the website being tested.  This is where the “IApplicationPathFinder” interface comes into play.  If you implement this interface you will see that it only has one method “GetApplicationpath.”  What this does is allow you to specify a prefix of sorts for the navigate method.  When you call navigate on a HTMLPage it uses a ApplicationPathFinder to get the location it should look for the website at.  For example, the framework will implement this interface and simply return “http://localhost” by default.  Creating your own ApplicationPathFinder class allows you to specify where to look for the website, there is nothing stopping you from telling it to look for “http://foo.com.”  The fallowing code shows a simple implementation of the IApplicationPathFinder interface, that points to a second test application. This code is place in the DrivePage that is located in the “Test” folder.

<script runat="server">
    public class TestApplicationPathFinder : Microsoft.Web.Testing.Light.IApplicationPathFinder
    {
        public string GetApplicationPath()
        {
              return "http://localhost/TestApp2";
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);

        Microsoft.Web.Testing.Light.ServiceLocator.ApplicationPathFinder = new TestApplicationPathFinder();
    }
</script>

Okay, lets take a look at what is really going on here in the code.  First I choose to include the  implementation of the IApplicationPathFinder interface in the DriverPage, however, this could easily be moved to its own file/assembly.  Other than that the code is pretty straight forward, set the ApplicationPathFinder property on the ServiceLocator to an instance of your implementation.

There are numerous reason you might want to run the the test application separately from the site you are testing.  Here is a simple implementation that allows you to store your test application in a central location and pass the location of the site to be test to it.  You could use a URL like “http://localhost/Test?tag=localhost2&filter=true&path=http%3A%2F%2Flocalhost2&run=true” to the navigate and have the test page look for the site at “http://localhost2,” show only the test with the tag name “localhost2,” and run them once navigation is complete.  You could see how you could use this to set up a regression test bed, by storing all your tests in one location and pointing them to the proper servers for the pages.

<script runat="server">
    public class TestApplicationPathFinder : Microsoft.Web.Testing.Light.IApplicationPathFinder
    {
        public string Path { get; set; }

        public TestApplicationPathFinder(string path)
        {
            Path = path;
        }
        
        public string GetApplicationPath()
        {
            return Path;
        }
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        string path = this.Request.Params["path"];
        if(!string.IsNullOrEmpty(path))
            Microsoft.Web.Testing.Light.ServiceLocator.ApplicationPathFinder = new TestApplicationPathFinder(path);
    }
</script>
Hopefully this gives you some insight into the power that the Lightweight Test Automation Framework for ASP.NET has and how you can leverage that power.  You can download the framework here. Please let me know if you have any questions.

author: Matthew M. Osborn | posted @ Tuesday, December 02, 2008 2:04 AM | Feedback (0)

Microsoft .NET gets a facelift


asp.net For those of you who have been living under a rock today you might not have noticed that the Microsoft .NET logo got a face lift.  I know it’s about time!  Find out about all the new features being released at PDC08 at http://microsoftpdc.com

author: Matthew M. Osborn | posted @ Monday, October 27, 2008 5:19 PM | Feedback (0)

Tech·Ed EMEA 2008


clip_image001Well it’s official I will be going to Tech·Ed EMEA this year.  This will be my first time in Spain so it should be fun.  I will be working the ASP.NET booth for the developer portion of the conference, November 10th through the 14th.  If your going to be there look me up at the booth.  If there is anything you want to find out while I’m in Spain or at Tech·Ed let me know.  Any ideas for a small 20-30 minute talk?  If you’re interested there is still time to register and make the trip!

author: Matthew M. Osborn | posted @ Thursday, October 16, 2008 8:48 PM | Feedback (0)

CSS201: Theory and Practice



To fallow along with this screen cast visit http://css201.osbornm.com

To download the source for this screen cast visit http://www.osbornm.com/css201.zip

author: Matthew M. Osborn | posted @ Monday, September 29, 2008 3:24 PM | Feedback (0)

NexusLight: The ASP.NET QA Framework


 Logo

Yesterday, the Microsoft ASP.NET QA team released a sample to Codeplex that exposed the world to NexusLight.  NexusLight is the framework that the ASP.NET team uses to test all of our Features.  Before I get a million questions about this let me say that there is nothing amazing or revolutionary in this sample.  It was released simply to introduce the world to how Microsoft goes about testing it’s cycle and how we do things.  You might like it you might not, but never-the-less its there to be downloaded and played with.  Within the next couple of days I will be posting a more in-depth series of screen casts about how to use NexusLight. Please, let us know if there is anything you’d like to learn more about and we’ll see what we can do for you.

NexusLight Release Notes

NexusLight Web Test Automation Framework Samples

author: Matthew M. Osborn | posted @ Thursday, September 25, 2008 9:41 PM | Feedback (0)

CSS 101: The Basics



To fallow along with this screen cast visit http://css101.osbornm.com

To download the source for this screen cast visit http://www.osbornm.com/css101.zip

author: Matthew M. Osborn | posted @ Monday, September 22, 2008 5:07 PM | Feedback (0)

Master pages and their not so master life style


There is no great secrete that I'm letting out of the bag but it is something that I never really had the need to stop and think about, it was just something that I took for granted.  Maybe I thought that it worked by some form of magic, maybe I just never cared, but today I'd like to talk about what a master page really is.  I always thought that a master page was simply that, a page.  This makes since based on the name and the behavior that it has right?, wrong.  If you open up reflector and take a look at the master page class it inherits from System.Web.UI.UserControl and not from page.  Once I stopped and thought about this for a minute it makes since. Master pages were introduced in FX 2.0 and by not adding anything as the parent of page there would be very little work that would need to be done to implement this. So how do we get the master page to appear to be the parent of page you might ask yourself.  Well here is were the ContentPlaceHolder and Content controls come into play.  They simply control where the page's content is placed when the page is rendered.  ASP.net is aware of the master page control, because of the reference in the page declaration, and places the markup inside the Content Control inside of the ContentPlaceHolder Control.  This gives the effect of the master page acting as the parent of page without actually changing the control hierarchy. Like I said this is no great secrete but simply something I had never stopped to think about.  The reason I stopped to think about it is that I plays a part in a feature I am working on for the next version of the framework, stay tuned for more information about that feature after PDC08.

author: Matthew M. Osborn | posted @ Friday, August 22, 2008 8:10 PM | Feedback (0)

Mojave: The Next Microsoft OS


Many people HATE vista, even people that are not tech savvy seem to hate it!  It’s slow, it crashes, and people have heard nothing but bad things about it.  Well I’m going to let all of you in on a little secret! This will all be fixed by Mojave: The Next Microsoft OS!  Check it out for yourself http://www.mojaveexperiment.com/

author: Matthew M. Osborn | posted @ Tuesday, July 29, 2008 12:42 PM | Feedback (1)