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
public override string ID
{
get { return NamingConfiguration.Provider.GetControlID(this, base.ID); }
set { base.ID = NamingConfiguration.Provider.SetControlID(value, this); }
}
protected override ControlCollection CreateControlCollection()
{
return NamingConfiguration.Provider.CreateControlCollection(this);
}
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;
}
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
.
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;
}
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