Click here to Skip to main content
15,867,568 members
Articles / Web Development / ASP.NET

How To Shorten ASP.NET Automatically Generated Control IDs

Rate me:
Please Sign up or sign in to vote.
4.44/5 (5 votes)
17 Mar 2009CPOL3 min read 43.2K   27   8
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.

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:

C#
public class 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>

    public override string ID
    {
        get { return NamingConfiguration.Provider.GetControlID(this, base.ID); }
        set { base.ID = NamingConfiguration.Provider.SetControlID(value, this); }
    }

    /// <summary>
    /// Creates a new <see cref="T:System.Web.UI.ControlCollection"></see>
    /// object to hold the child controls 
    /// (both literal and server) of the server control.
    /// </summary>
    /// <returns>

    /// A <see cref="T:System.Web.UI.ControlCollection"></see>
    /// object to contain the current server control's 
    /// child server controls.
    /// </returns>
    protected override 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>

    /// <param name="id">The identifier for the control to be found.</param>
    /// <param name="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>
    protected override 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 <see cref="E:System.Web.UI.Control.Init"></see> event.
    /// </summary>

    /// <param name="e">An <see cref="T:System.EventArgs"></see> object
    /// that contains the event data.</param>
    protected override void 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:

C#
protected override RepeaterItem CreateItem(int itemIndex, ListItemType itemType)
{
    RepeaterItem rptrItem = base.CreateItem(itemIndex, itemType);
    NewRepeaterItem newRptrItem = new NewRepeaterItem(itemIndex, itemType);
    newRptrItem = Utilities.BaseToDerived(rptrItem, newRptrItem);
    return newRptrItem;
}

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:

C#
public static T BaseToDerived<S, T>(S source, T target)
{
    PropertyDescriptorCollection sourceproperties = 
                      TypeDescriptor.GetProperties(source);
    PropertyDescriptorCollection targetproperties = 
                      TypeDescriptor.GetProperties(target);
    foreach (PropertyDescriptor pd in targetproperties)
    {
        foreach (PropertyDescriptor _pd in sourceproperties)
        {
            if (pd.Name == _pd.Name)
            {
                pd.SetValue(target, _pd.GetValue(source));
            }
        }
    }

    return target;
}

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.

C#
/// <summary>
/// Creates a data item in the 
/// <see cref="T:System.Web.UI.WebControls.ListView"/> control.
/// </summary>
/// <param name="dataItemIndex">The index of the data item 
/// in the underlying data source object.</param>
/// <param name="displayIndex">The display index of the data item 
/// in the <see cref="T:System.Web.UI.WebControls.ListView"/> control.</param>
/// <returns>
/// A data item that is created by using the specified parameters.
/// </returns>
protected override ListViewDataItem CreateDataItem(int dataItemIndex, int displayIndex)
{
	ListViewDataItem lvItem = base.CreateDataItem(dataItemIndex, displayIndex);
	NewListViewDataItem newLvItem = 
		new NewListViewDataItem(dataItemIndex, displayIndex);
	newLvItem = Utilities.BaseToDerived(lvItem, newLvItem);
	return newLvItem;
}

/// <summary>
/// Creates a <see cref="T:System.Web.UI.WebControls.ListViewItem"/> 
/// object with the specified type.
/// </summary>
/// <param name="itemType">One of the 
/// <see cref="T:System.Web.UI.WebControls.ListViewItemType"/> values.</param>
/// <returns>
/// A <see cref="T:System.Web.UI.WebControls.ListViewItem"/> 
/// object with the specified type.
/// </returns>
protected override ListViewItem CreateItem(ListViewItemType itemType)
{
	ListViewItem lvItem = base.CreateItem(itemType);
	NewListViewItem newLvItem = new NewListViewItem(itemType);
	newLvItem = Utilities.BaseToDerived(lvItem, newLvItem);
	return newLvItem;
}

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!

History

  • 13th March, 2009: Initial post
  • 16th March, 2009: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAjaxTooltkit TabContainer Pin
Paul Finn20-Aug-09 6:34
Paul Finn20-Aug-09 6:34 
GeneralRe: AjaxTooltkit TabContainer Pin
mnovota17-Jun-10 5:41
mnovota17-Jun-10 5:41 
GeneralPlease provide the complete code with Listview Implemented. Pin
rovisuam2-Aug-09 23:15
rovisuam2-Aug-09 23:15 
NewsSystem.Web.U.Control.ClientIDMode can be an alternative and easy way to do this all in DNF 4.0 Pin
tyousufaa23-Jul-09 0:38
tyousufaa23-Jul-09 0:38 
GeneralRe: System.Web.U.Control.ClientIDMode can be an alternative and easy way to do this all in DNF 4.0 Pin
jeff chin xyz23-Jul-09 7:35
jeff chin xyz23-Jul-09 7:35 
GeneralNice Pin
Nuno M. F. Gomes17-Mar-09 4:59
Nuno M. F. Gomes17-Mar-09 4:59 
Hi Jeff,
I'm very pleasant to know that you find my solution useful. I like to know more about your experience using this approach.
Also, I like to let you know about a helper class I made to allow dynamic tag mapping http://nunogomes.net/post/2008/05/26/ASPNET-Dynamic-Control-Mapping.aspx[^].

I believe you can use this helper in your sample as an alternative to BaseToDerived<>.

Nuno Gomes
Portugal - Europe's West Coast

GeneralRe: Nice Pin
jeff chin xyz17-Mar-09 6:46
jeff chin xyz17-Mar-09 6:46 
GeneralRe: Nice Pin
Nuno M. F. Gomes17-Mar-09 7:40
Nuno M. F. Gomes17-Mar-09 7:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.