Introduction

This article shows you how to shorten ASP.NET automatically generated control IDs by making ASP.NET change its algorithm for generating control IDs, even for container controls.

Background

The auto-generated server control IDs produced by ASP.NET can be very long, and may hinder the SEO friendliness of your web application. This is because search engine spiders tend to stop analyzing the HTML in your page after a certain amount of bytes. Often, over half the size of an ASP.NET page may be attributed to its server control ID names.

Thankfully, Nuno Gomes has already done most of the work in his blog. There are four parts, located here and here. The source code can be found here.

Please take the time to read his posts before continuing here since it will make following the rest of my article much easier. The major improvement here is that I will show you how to modify his work to work with container controls such as the ListView, Repeater, and GridView. This is important because many pages have one of these controls encompassing all other controls on the page, so none of your controls will have shortened IDs if you use Nuno's code as-is.

Implementation

In Nuno's project, he basically creates a derived custom control for any server control whose ID he wishes to shorten. He then overrides several methods. For example, I can create a NewButton class which derives from Button:

publicclass NewButton : System.Web.UI.WebControls.Button
{
#region Naming Management
///<summary>/// Gets or sets the programmatic identifier assigned to the server control.
///</summary>///<value></value>///<returns>The programmatic identifier assigned to the control.</returns>publicoverridestring ID
{
get { return NamingConfiguration.Provider.GetControlID(this, base.ID); }
set { base.ID = NamingConfiguration.Provider.SetControlID(value, this); }
}
///<summary>/// Creates a new <seecref="T:System.Web.UI.ControlCollection"></see>/// object to hold the child controls
/// (both literal and server) of the server control.
///</summary>///<returns>/// A <seecref="T:System.Web.UI.ControlCollection"></see>/// object to contain the current server control's
/// child server controls.
///</returns>protectedoverride ControlCollection CreateControlCollection()
{
return NamingConfiguration.Provider.CreateControlCollection(this);
}
///<summary>/// Searches the current naming container for a server
/// control with the specified id and an integer,
/// specified in the pathOffset parameter, which aids
/// in the search. You should not override this version
/// of the FindControl method.
///</summary>///<paramname="id">The identifier for the control to be found.</param>///<paramname="pathOffset">The number of controls
/// up the page control hierarchy needed to reach a
/// naming container.</param>///<returns>/// The specified control, or null if the specified control does not exist.
///</returns>protectedoverride Control FindControl(string id, int pathOffset)
{
Control ctrl = base.FindControl(id, pathOffset);
if (ctrl == null)
{
ctrl = NamingConfiguration.Provider.FindControl(this, id, pathOffset);
}
return ctrl;
}
///<summary>/// Raises the <seecref="E:System.Web.UI.Control.Init"></see> event.
///</summary>///<paramname="e">An <seecref="T:System.EventArgs"></see> object
/// that contains the event data.</param>protectedoverridevoid OnInit(EventArgs e)
{
this.EnsureID();
this.ID = base.ID;
base.OnInit(e);
}
#endregion Naming Management
}

The above code works for any non-container control, but not for a container control, which I define as any control that implements INamingContainer. The reason is because a control like the Repeater will contain a collection of RepeaterItem classes within it. You want to replace each instance of the Repeater with your own NewRepeater instance via the tagmapping element in the web.config file, but you won't be able to replace the RepeaterItem instance with your own NewRepeaterItem via tag-mapping because the code within RepeaterItem specifically creates only RepeaterItem instances. In short, tag-mapping only works for controls defined declaratively in a *.aspx or *.ascx file.

To get around this problem, we need to override another method in our NewRepeater class:

You'll notice in the code above that I make a call to Utilities.BaseToDerived. This method will basically perform reverse casting, i.e., it will take a base class and return a derived version of it. The derived version will be an instance of our NewRepeaterItem class. The method implementation is shown below:

You can see that we are using Reflection to copy all the matching properties from one class to the other. It's really more copying the matching properties than casting I admit, but it gets the job done. I have not had any problems in manipulating the derived class in any data binding events so far.

For the Repeater control, there are only RepeaterItem objects within it that you need to worry about. Other container controls like the ListView can contain ListViewItem objects AND ListViewDataItem objects. Therefore you would need to override more than one CreateXXX method within your NewListView class. These two methods are CreateItem and CreateDataItem.

Note that using NewListView requires that ItemPlaceholderID property be EXPLICITLY set. I'm not exactly sure why.

Conclusion

Shortening the auto-generated id names for ASP.NET server controls can significantly reduce your page sizes and improve SEO. In this article, I showed you how to do this by using code from an entry in Nuno Gomes' blog, and then modifying his code so that it works with container controls. Happy coding!