ASP.NET Controls - Improving automatic ID generation : The ShortIDs Naming Provider (Part 4)

In the previous posts on this subject I wrote about why automatic ID generation should be improved and how we can improve it. Now I will step forward and show you my own implementation of a specific naming provider.

As we saw in part 3, to create a specific Naming provider you only need to develop your own implementation of SetControlID method.

I named my naming provider ShortIDsProvider and it will have only one specification to meet:

  • it will create IDs in the form Txxx

where T denotes the 'T' character and xxx denotes an unique incremental integer value.

Why using the 'T' character? Well, since Control.ID must always start by an alphabetic character ... I choose on my own risk the 'T' char.

So ... here it is:

public class ShortIDsProvider : NamingProvider
{
    #region Private Fields
    private const string ID_PREFIX = "T";
    private static object KeepLongIDsAttributeValueKey = new object();
    #endregion Private Fields

    #region Public Methods
    private bool KeepOriginalID(System.Web.UI.Control control)
    {
        bool keepOriginalIDs = false;

        #region KeepLongIDs Attribute Value Management
        if (!this.KeepOriginalIDs)
        {
            if (HttpContext.Current.Items.Contains(KeepLongIDsAttributeValueKey))
            {
                keepOriginalIDs = (bool)HttpContext.Current.Items[KeepLongIDsAttributeValueKey];
            }
            else
            {
                string path = System.Web.HttpContext.Current.Request.Path.Replace(System.Web.HttpContext.Current.Request.ApplicationPath, string.Empty);

                if (this.ExceptionList != null && this.ExceptionList.Contains(path))
                {
                    keepOriginalIDs = true; ;
                }
                else
                {
                    if (control.Page != null)
                    {
                        object[] atts = control.Page.GetType().GetCustomAttributes(true);

                        foreach (Attribute att in atts)
                        {
                            if (att is KeepOriginalIDsAttribute)
                            {
                                keepOriginalIDs = ((KeepOriginalIDsAttribute)att).Enabled;
                                break;
                            }
                        }
                    }
                }
                HttpContext.Current.Items[KeepLongIDsAttributeValueKey] = keepOriginalIDs;
            }
        }
        #endregion KeepLongIDs Attribute Value Management

        return keepOriginalIDs;
    }
    #endregion Public Methods

    #region NamingProvider Implementation

    /// <summary>
    /// Generates the Control ID.
    /// </summary>
    /// <param name="name">The controls name.</param>
    /// <param name="control">The control.</param>
    /// <returns></returns>
    public override string SetControlID(string name, System.Web.UI.Control control)
    {
        if (this.KeepOriginalID(control))
        {
            return name;
        }
        if (control == null)
        {
            throw new ArgumentNullException("control");
        }
        if (control.NamingContainer == null)
        {
            return name;
        }
        NamingContainerControlCollection controlsCollection = control.NamingContainer.Controls as NamingContainerControlCollection;
        if (controlsCollection == null)
        {
            return name;
        }

        string shortid = null;
        if (!controlsCollection.ContainsName(name))
        {
            shortid = string.Format("{0}{1}", ID_PREFIX, controlsCollection.GetUniqueControlSufix());

            if (string.IsNullOrEmpty(name))
            {
                name = shortid;
            }
            controlsCollection.RegisterControl(shortid, name, control);
        }
        else
        {
            shortid = control.ID;
        }
        return shortid;
    }

    #endregion NamingProvider Implementation

}

As you can see it's not rocket science, and enable us to create any automatic id generation strategy.

Naturally this ShortIDsProvider is only valuable in conjugation with a set of improved web controls.

Making an improved web control is also very simple and straight forward. Here is the improved TextBox control:

public class TextBox : System.Web.UI.WebControls.TextBox
{
    #region Naming Management

    /// <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>
    /// 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>
    /// 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 <see cref="Overload:System.Web.UI.Control.FindControl"></see> 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 last step is configuration: first to instruct asp.net to use our improved web controls instead of the usual ASP.NET controls. We do this thru TagMapping configuration, like this:

<system.web>
  <pages>
    <tagMapping>
      <add tagType="System.Web.UI.WebControls.TextBox, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
           mappedTagType="IDsSample.Controls.TextBox"/>
    </tagMapping>
  </pages>
</system.web>

And second, setting the default Naming provider. The web.config should look similar to this one:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="NamingConvention" type="IDsSample.Configuration.NamingSection"
          allowDefinition="MachineToApplication"
          restartOnExternalChanges="true" />

  </configSections>
  <appSettings/>
  <connectionStrings/>
  <system.web>
    <compilation debug="true"/>
    <authentication mode="Windows"/>

    <pages>
      <tagMapping>
        <add tagType="System.Web.UI.WebControls.TextBox, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
             mappedTagType="IDsSample.Controls.TextBox"/>
      </tagMapping>
    </pages>
  </system.web>

  <NamingConvention defaultProvider="ShortIDs">
    <providers>
      <add name="ShortIDs" type="IDsSample.Providers.ShortIDsProvider"
           exceptionlist="/Exception.aspx"
           keeporiginalids="false"/>
    </providers>
  </NamingConvention>

</configuration>

Please note that TagMapping will only work for controls declared in markup. If you need to create dynamic controls then use the DynamicControlBuilder class.

I'm currently working on a SiteKit that can be applied to any existing web application in order to increase performance by reducing the rendered html size.

While waiting for the SiteKit you can try the sample solution.

kick it on DotNetKicks.com

7 Comments

  • I've been suffering from the same problems of the long Id's for some time. In many projects it's not much of an issue in terms of bandwidth but I'm all too aware that there could be huge increases in load times on sites if the damn id's were shorter. When you view source it scares me to see the amount of code bloat there is on a page when the Id's are so long and I cringe at the amount of kB's that could be saved. Many a time I've pondered going to another platform just so that the html can remain clean just like the old ASP days (hell I've even cosidered going back to ASP sometimes). I wish Microsoft would produce some sort of a plugin for Visual Studio that could iterate through the Id's on a build/publish and shorten them in both the code behind and the html. Your site kit sounds like a great idea, I'm eager to try it once you've finished. Drop me an email when you do at lloyd dot phillips at creationaldesign dot com.

  • Hello, Im trying to get this to work, and have modified alot of the code into VB, though have no idea where NamingConfiguration is from, or how it works, could you give me a heads up

  • I'm having some problem with "NamingConfiguration" in the control:

    Error 11 The name 'NamingConfiguration' does not exist in the current context ...\controls\Textbox.ascx.cs 39 29

  • What your stating is entirely genuine. I recognize that everyone ought to
    say the the identical issue, then again I just think you to location it in a tactic that
    every particular person can fully grasp. I also adore the photographs you arranged in right here.
    They suit so effectively with what youre making an try to say.
    Im certain youll attain so a few men and women with
    what youve acquired to say.

  • Hello! I recently want to make a large thumbs up with that the great
    information you’ve got here with this post. I will be coming back to your website for more soon.

  • you use a fantastic weblog here! when you’d just like
    to cook some invite posts in my blog?

  • It’s tough to acquire knowledgeable folks with this topic, nevertheless, you
    seem such as there’s more you are referring to!
    Thanks

Comments have been disabled for this content.