Serving less HTML code starting from .NET 4.0 with ClientIDMode property

   —   
One of the best practices in general from performance perspective is to transfer as little data between a client and a server as possible. When it comes to web sites, we need to ensure that the final HTML code is clean and we are not transferring any extra information which is not utilized by the client’s browser at all (e.g. comments in HTML code, unnecessary long control IDs etc.). This article will help you to end up with both cleaner HTML code and optimized performance.
As you probably know, .NET of version 3.5 and previous renders control’s ID by concatenating the ID values of each parent naming container with the ID value of the control. In other words, you could end up with really long control ID:

<a id="p_lt_ctl03_MyShoppingCartPreview_lnkMyAccount" class="ShoppingCartLink" href="/70DEV/SpecialPages/User/My-account.aspx">My account</a>

In this specific example above, the yellow highlighted part is what you can configure in the “Control ID” property of Shopping Cart Preview web part, so keeping the value as short as possible is one of the best practices. However, the best idea would be not to render such concentrated ID at all (red + yellow part of the ID) and only keep the defined control ID (marked as green). Luckily you can change this behavior from .NET of version 4.0 with ClientIDMode property which can be configured on control or page level. Available options are AutoID, Static, Predictable and Inherit and you can read more about each of these options here. If we use “Static” option, then the final HTML code from the example above will look like this:

<a id="lnkMyAccount" class="ShoppingCartLink" href="/70DEV/SpecialPages/User/My-account.aspx">My account</a>

One way on how to end up with above is to simply set the ClientIDMode in your web.config file. This will however influence all the HTML code application-wise and administration interface (UI) of Kentico might not work properly. What if we would like to change this behavior for (A) all web parts on a page or (B) only for a specific web parts? 

In case we would do it on a page level (A), we can access Page property from any control (web part) and simply set its ClientIDMode property. You can create custom web part for this purpose on your own or simply download an existing one here. Such web part's code could like this:

/// <summary> /// OnPrerender override (Set ClientIDMode). /// </summary> protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); switch(ClientIDMode) { case "AutoID": this.Page.ClientIDMode = System.Web.UI.ClientIDMode.AutoID; break; case "Inherit": this.Page.ClientIDMode = System.Web.UI.ClientIDMode.Inherit; break; case "Predictable": this.Page.ClientIDMode = System.Web.UI.ClientIDMode.Predictable; break; case "Static": this.Page.ClientIDMode = System.Web.UI.ClientIDMode.Static; break; } }


By exposing custom property (let's call it ClientIDMode), you can change this setting comfortably any time via web part configuration dialog:

screen-(1).png

In case we would like to programmatically set this property for each web part separately (B) we would need to touch the code of Kentico’s system objects (built-in web parts). This will definitely lead to hotfix/upgrade related issues in future. Let’s implement this functionality by not touching any of the system code files.
  1. First of all, we need to introduce custom web part property (let’s call it ClientIDMode) which will be system property available for all web parts. System web part properties are defined in xml files sitting on a file system in ~\App_Data\CMSModules\PortalEngine\Properties\_Groups folder. Following is a default XML code (default.xml) representing two built-in properties WebPartControlID and WebPartTitle:
<field column="WebPartControlID" fieldcaption="Web part control ID" visible="true" columntype="text" fieldtype="textbox" allowempty="false" columnsize="50" fielddescription="Serves as an identifier for the web part. This ID must be unique within the context of each page template. The value of this property may only contain alphanumeric characters and the underscore character ( _ ) and it must start with a letter." regularexpression="\w+" validationerrormessage="Control ID must match identificator format." guid="3718b090-f3a5-464d-919a-a11e9b5eb934" /> <field column="WebPartTitle" fieldcaption="Web part title" visible="true" columntype="text" fieldtype="textbox" allowempty="true" columnsize="200" fielddescription="Title of the web part displayed on the Design tab in administration and in on-site editing mode. If empty, the value of the Web part control ID property is used for this purpose." guid="2bf266ee-b4fb-4f3b-9a91-523cf7b55e7e" />

 
We can of course extend this file, but it would not be wise decision because it is a system file. Let’s create custom one (~\App_Data\CMSModules\KB102\PortalEngine\Properties\_Groups\Default.xml) and extend custom one with following code (marked as bold):

<field column="WebPartControlID" fieldcaption="Web part control ID" visible="true" columntype="text" fieldtype="textbox" allowempty="false" columnsize="50" fielddescription="Serves as an identifier for the web part. This ID must be unique within the context of each page template. The value of this property may only contain alphanumeric characters and the underscore character ( _ ) and it must start with a letter." regularexpression="\w+" validationerrormessage="Control ID must match identificator format." guid="3718b090-f3a5-464d-919a-a11e9b5eb934" /> <field column="ClientIDMode" fieldcaption="Client ID mode" visible="true" columntype="text" fieldtype="CustomUserControl" allowempty="true" columnsize="11" fielddescription="documentation.webpartproperties.clientidmode" guid="3718b090-f3a5-464d-919a-a11e9b5eb935"> <settings> <controlname>DropDownListControl</controlname> <Options> autoid;AutoID static;Static inherit;Inherit predictable;Predictable </Options> </settings> </field> <field column="WebPartTitle" fieldcaption="Web part title" visible="true" columntype="text" fieldtype="textbox" allowempty="true" columnsize="200" fielddescription="Title of the web part displayed on the Design tab in administration and in on-site editing mode. If empty, the value of the Web part control ID property is used for this purpose." guid="2bf266ee-b4fb-4f3b-9a91-523cf7b55e7e" />
  1. As a second step, we need to force Kentico to load the custom xml file instead of the original. What we can do is to implement custom storage provider overriding couple of default methods where we will ensure loading of different file instead. Let’s create a class files KB102CustomStorageProvider (inheriting from CMS.IO.StorageProvider) and KB102CustomFile (inheriting from CMS.CMSStorage.File) in ~\App_Code\CMSModules\KB102 location (in case of web site project type) where we will override ReadAllText method in following way:
  
/// <summary> /// Opens a text file, reads all lines of the file, and then closes the file. /// </summary> /// <param name="path">Path to file</param> public override string ReadAllText(string path) { if (path.ToLower().Contains("cmsmodules\\portalengine\\properties\\_groups\\default.xml")) { path = path.Replace("\\PortalEngine\\Properties\\_Groups\\Default.xml", "\\KB102\\PortalEngine\\Properties\\_Groups\\Default.xml"); } return base.ReadAllText(path); }

 
The above code will load our default.xml file for web part system properties from default group / category. We also need to register our custom storage provider. We will use another class file (KB102HandlerLoader inheriting from CMSLoaderAttribute) for this purpose and register the custom storage provider:
 
       
public override void PreInit() { // Initialize storage provider settings - application start SettingsHelper.AppSettings["CMSStorageProviderAssembly"] = "KB102CustomStorageProvider"; SettingsHelper.AppSettings["CMSExternalStorageName"] = "KB102Custom"; // Register custom storage provider AbstractStorageProvider myKB102Provider = new KB102CustomStorageProvider(); StorageHelper.MapStoragePath("~/", myKB102Provider); } private static void GetCustomClass(object sender, ClassEventArgs e) { if (e.Object == null) { switch (e.ClassName) { case "KB102CustomStorageProvider": e.Object = new KB102CustomStorageProvider(); break; } } }


At this moment, you should be able to see a new web part property to set / configure:
 
screen.png

  1. The last step would be to make sure the selected value is implemented in code of the web part. We will register for OnBeforeUserControlPreRender event of CMSAbstractWebPart class where we can access the web part and its properties:
       
public override void Init() { CMSAbstractWebPart.OnBeforeUserControlPreRender += new CMS.ExtendedControls.BeforeEventHandler(CMSAbstractWebPart_OnBeforeUserControlPreRender); } bool CMSAbstractWebPart_OnBeforeUserControlPreRender(object sender, EventArgs e) { if (sender is CMSAbstractWebPart) { CMSAbstractWebPart webPart = sender as CMSAbstractWebPart; string clientIdMode = ValidationHelper.GetString(webPart.GetValue("ClientIDMode"), ""); if (!string.IsNullOrEmpty(clientIdMode)) { switch (clientIdMode.ToLower()) { case "static": webPart.ClientIDMode = ClientIDMode.Static; break; case "inherit": webPart.ClientIDMode = ClientIDMode.Inherit; break; case "predictable": webPart.ClientIDMode = ClientIDMode.Predictable; break; default: webPart.ClientIDMode = ClientIDMode.Predictable; break; } } } return true; }


Complete implementation can be downloaded as export package here and imported into Kentico of version 7.0.23 or later. Please make sure that “Import Files” option is checked during the import procedure.

NOTE: Kentico of version 7.x still supports .NET of version 3.5 and this is one of the reasons why the above functionality is not integrated in the out-of-the-box solution. Kentico version 8 will support only .NET 4.0 and later.
-mr-


Applies to: 7.x
Share this article on   LinkedIn

Miro Remias

Hi, I am a Product Management Team Lead here at Kentico. My missions i to lead, advise and empower our Product Management team in seeking the product-market fit.