Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How To Shorten ASP.NET Automatically Generated Control IDs

0.00/5 (No votes)
17 Mar 2009 1  
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:

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:

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:

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.

/// <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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here