Hi all

Some days back I was working with update panels and I created a simple application with multiple update panels on my page. Update panels were fetching data from a third part content management system, so sending a request, fetching and displaying the data was not a tricky part of the application but that used to take around 30 to 40 seconds per request.

Initially It was good but later when we customized our application to allow user to add multiple update panels on 1 page, we got stuck with performance issues, when user tried to load a page it used to take 1 or 2 minutes to complete page output.

I searched a lot to find out the way to improve the page performance, I got the idea of lazy loading but that worked well if there was only 1 update panel on the page. After spending lot of time, finally I figured out the lazy loading implementation with unlimited number of update panels, so in ideal situation a web page loads immediately without any third party call and after page load is completed it execute update panel refresh calls from JavaScript and shows a progress bar when update panel is loading the data. So if you have n number of update panels placed on your web page, the update panel placed at first position will start loading and after that 2nd panel load will start then 3rd and so on.

So for the clear understanding of this concept I am creating a user control and that user controls has 1 update panel and some other content controls, I will drop the user control on my page 4 times, so there will be 4 update panels rendered.

Here is the sample script for user control

<asp:UpdatePanel
ID=”upUpdatePanel” runat=”server” UpdateMode=”Conditional” OnPreRender=”upUpdatePanel_PreRender”>

<ContentTemplate><asp:LinkButtonID=”LinkButton1″ runat=”server” Enabled=”false” ForeColor=”Black”Font-Bold=”true” Font-Size=”Large” onclick=”LinkButton1_Click”>LinkButton disabled so far</asp:LinkButton>

<br /><br />

<asp:LabelID=”Label2″ runat=”server” Text=”Label”></asp:Label><asp:HiddenFieldID=”HiddenField1″ runat=”server”/>

<asp:GridView
ID=”GridView1″ runat=”server” PageSize=”3″ AllowPaging=”True” OnPageIndexChanging=”GridView1_PageIndexChanging”>

</asp:GridView>

</ContentTemplate>

</asp:UpdatePanel>

<asp:UpdateProgress
ID=”updProgressTab” runat=”server” AssociatedUpdatePanelID=”upUpdatePanel”>

<ProgressTemplate><divstyle=”position: relative; top: 50%; text-align: center;”>

<asp:ImageID=”imgLoading1″ runat=”server” ImageUrl=”simple.gif” Width=”34px” Height=”30px”/>Refreshing…

</div>

</ProgressTemplate>

</asp:UpdateProgress>

Note : update panel’s update mode is “conditional” and I have registered on_PreRender event of the update panel.

For on_preRender I have implemented this logic :

protected
void upUpdatePanel_PreRender(object sender, EventArgs e)

{


string eventTarget = Request.Params[“__EVENTTARGET”] as
string;


if (string.IsNullOrEmpty(eventTarget) == false)

{


if (eventTarget.StartsWith(this.ID))

{

System.Threading.Thread.Sleep(2000);

LinkButton1.Enabled = true;

LinkButton1.Text = “Enabled Now”;


this.Label2.Text = DateTime.Now.ToString();

BindGrid1();

}

}

}

So I guess nothing special in the code logic, I am just trying to load a grid with hard coded values and with 2 second time interval.

So here is the tricky and most important part of the topic

StringBuilder sb = new
StringBuilder();

sb.Append(“var loopCounter = -1;”);

sb.Append(“function pageLoad(sender, e) {“);

sb.Append(“if (!e.get_isPartialLoad()) {“);

sb.Append(“var refreshPanels = $(\”[id*=\” + \”upUpdatePanel\” + \”]\”);”);

sb.Append(“Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);”);

sb.Append(“var panel = refreshPanels[++loopCounter];”);

sb.Append(“__doPostBack(panel.id, ‘1’);”);

sb.Append(“}function EndRequestHandler() {“);

sb.Append(“var panel = refreshPanels[++loopCounter];”);

sb.Append(“if (panel==null) {“);

sb.Append(“Sys.WebForms.PageRequestManager.getInstance().remove_endRequest(EndRequestHandler);”);

sb.Append(“}”);

sb.Append(“else {“);

sb.Append(“__doPostBack(panel.id, ‘2’);}}”);

sb.Append(“}”);

Page.ClientScript.RegisterClientScriptBlock(typeof(Page), “MYScript”, sb.ToString(), true);

Let me explain in detail.

  • First thing you need to know is that this will be rendered from controls load event
  • I am calling some script after page load event.
  • $(\”[id*=\” + \”upUpdatePanel\” + \”]\”);”)” this part is basically a JQuery selector that selects all divs having id “upUpdatePanel”, which is id of my update panel.
  • After that I am registering an end request event handler, that will be raised when update panel load request is completed.
  • I am looping through all panels and calling __doPostBack of each update panel. __doPostBack will call “upUpdatePanel_PreRender” event.
  • In EndRequestHsndler I am checking if there is another update panel remaining then call its __doPostBack otherwise simple remove end request event handler.

In my page I have dropped 4 user controls

<table
border=”0″ cellpadding=”0″ cellspacing=”0″ width=”100%”>

<tr><td

style=”width: 50%”>

<uc1:WebUserControl1ID=”WebUserControl11″ runat=”server”/>

</td>

<tdstyle=”width: 50%”>

<uc1:WebUserControl1ID=”WebUserControl12″ runat=”server”/>

</td>

</tr>

<tr>

<tdstyle=”width: 50%”>

<uc1:WebUserControl1ID=”WebUserControl13″ runat=”server”/>

</td>

<tdstyle=”width: 50%”><uc1:WebUserControl1ID=”WebUserControl14″ runat=”server”

/>

</td>

</tr>

</table>

This shows page output like


In above screen you can see first panel is being refreshed.


Now first panel has been loaded and now second panel is being refreshed. Same for 3rd and fourth



http://www.4shared.com/folder/Ryz_jgeH/Lazy_Loading.html

you can download code form above link and off course leave a comment if you did like my effort 🙂 

Advertisements

I have excerpted a good article on page methods technique, hope fully you will take advantage of it.

This article shows you how to use ASP.NET AJAX PageMethods to perform Create, Read, Update and Delete (CRUD) operations with an HTML table. Here HTML table acts as a light-weight DataGrid.

Inorder to make PageMethods work, following things need to be done:

  1. ScriptManager should be added to your ASPX page.
  2. EnablePageMethods property of the ScriptManager should be set to true.
  3. System.Web.Services namespace should be added as reference on your codebehind class.
  4. Page Methods on your code-behind should be decorated with [WebMethod] attribute.

First let us start with Read.

As mentioned above, add a ScriptManager and set its ‘EnablePageMethods’ property to true. Add an HTML button and an onclick handler to it, and then add an HTML table with thead, tbody and tfoot. Since the HTML table will be referenced from javascript, add id to the table and its body. Here, only HTML tags/controls are used because, server side controls cannot be referenced in PageMethods.


Your ASPX page should look something like this.

<body>

<form id=”form1″ runat=”server”>

<asp:ScriptManager ID=”ScriptManager1″ runat=”server” EnablePageMethods =”true” ></asp:ScriptManager>

<%–This click event handles loading data from the database–%>

<input id=”btn_load” type=”button” value=”Load” onclick = “LoadData()” />

<br /><br />

<div>

<table style=” height: 100%; border: solid 1px #000″ cellpadding=”0″ cellspacing=”1″ id=”tbl_grid” border = “1”>

<thead style = “background-color: #666; color: #fff”>

<tr>

<td style=”width: 100px;”>

Column1

</td>

<td style=”width: 500px;”>

Column2

</td>

<td style=”width: 150px;”>

Edit

</td>

</tr>

</thead>

<tbody id=”tbody_grid”>

</tbody>

<tfoot>

<tr>

<td style=”width: 100px;”>

<input id=”txt_addcol1″ style =”width: 30px” type=”text” />

</td>

<td style=”width: 500px;”>

<input id=”txt_addcol2″ type=”text”  style =”width: 300px” />

</td>

<td style=”width: 150px;”>

<%–This click event handles adding data to the database–%>

<input id=”btn_add” type=”button” onclick = “Add()” value=”Add” />

</td>

</tr>

</tfoot>

</table>

</div>

</form>

</body>

Now add your JavaScript function to load data from the database using PageMethods. PageMethod call should always have a success handler (this will be executed if the page method is executed successfully) and an exception handler (this will be executed if an exception is thrown). Say suppose we added ‘GetData()’ as the page method on the code behind, our javascript will be PageMethods.GetData(SuccessHandler, ExceptionHandler). Just for understanding, I have named the success and exception handler appropriately, you can name them as you wish. In case, the page methods takes parameters, you can add like PageMethods.GetData(param1, param2, SuccessHandler, ExceptionHandler).

Page methods should be decorated with [WebMethod] attribute and should be declared as static. Its signature shoul look something like this:

[WebMethod]

public static string GetData()



Page method to return data to javascript.

public partial class AJAXGrid : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

}

[WebMethod]

public static IEnumerable<MyEntity> GetData()

{

try

{

Data fetch part should go here

// used List, as collections are serializable. See below for MyEntity class

List<MyEntity> MyEntities = new List<MyEntity>();

MyEntities.Add(“1”, “abc”);

MyEntities.Add(“2”, “xyz”);

MyEntities.Add(“3”, “pqr”);

MyEntities.Add(“4”, “mno”);

return MyEntities;

}

catch(Exception ex)

{

throw ex;

}

}
}

MyEntity class

public class MyEntity

{

private string _Column1;

public string Column1

{

get { return _Column1; }

set { _Column1 = value; }

}

private string _Column2;

public string Column2

{

get { return _Column2; }

set { _Column2 = value; }

}

public MyEntity(string sCol1, string sCol2)

{

_Column1 = sCol1;

_Column2 = sCol2;

}

}

Javascript function that calls page method and populates the HTML table

<script type =”text/javascript” language = “javascript “>

//Loading Data

// Handles btn_load click event

function LoadData() {

// If data was fetched successfully, SuccessHandler will be called; else, ExceptionHandler

PageMethods.GetData(SuccessHandler, ExceptionHandler);

// Incase parameters need to be passed to PageMethods, you can do like this PageMethods.GetData(param1, param2, SuccessHandler, ExceptionHandler)

}

// After fetching the data successfully

function SuccessHandler(result) {

var tbody = $get(“tbody_grid”);

// clear the table

for (var j = tbody.rows.length; j > 0; j–) {

tbody.deleteRow(j – 1);

}

// populate the table

for (var i = 0; i < result.length; i++) {

//two columns fetched from database are sent as parameters

AddRow(result[i].Column1, result[i].Column2);

}

return true;

}

// Edit and Delete buttons are added to the rows

function AddRow(col1, col2) {

var tbody = $get(“tbody_grid”);

var row = document.createElement(“tr”)

var td1 = document.createElement(“td”)

td1.innerText = col1;

var td2 = document.createElement(“td”);

td2.innerText = col2;

var td3 = document.createElement(“td”);

// add buttons

var btnEdit = document.createElement(‘input’);

btnEdit.setAttribute(‘type’, ‘button’);

btnEdit.setAttribute(‘name’, ‘Edit’);

btnEdit.setAttribute(‘value’, ‘Edit’);

// first parentNode represents <td> and the second represents <tr>

btnEdit.onclick = function() { Edit(this.parentNode.parentNode); };

var btnDelete = document.createElement(‘input’);

btnDelete.setAttribute(‘type’, ‘button’);

btnDelete.setAttribute(‘name’, ‘Delete’);

btnDelete.setAttribute(‘value’, ‘Delete’);

btnDelete.onclick = function() { DeleteRow(this.parentNode.parentNode); };

td3.appendChild(btnEdit);

td3.appendChild(btnDelete);

row.appendChild(td1);

row.appendChild(td2);

row.appendChild(td3);

tbody.appendChild(row);

}

// Handles exception

function ExceptionHandler(result) {

}

After populating the HTML table



Javascript functions to handle Edit, Update, Delete and Insert:


Editing Data

//  this function handles edit button click

function Edit(row) {

var col1 = row.childNodes[0].innerText;

var col2 = row.childNodes[1].innerText;

// populates values in textboxes and displays Update and Cancel buttons

var editableRow = document.createElement(“tr”)

var td1 = document.createElement(“td”)

var txtBox1 = document.createElement(‘input’);

txtBox1.setAttribute(‘type’, ‘text’);

txtBox1.setAttribute(‘name’, ‘col1’);

txtBox1.setAttribute(‘value’, col1);

txtBox1.setAttribute(‘width’, 30);

td1.appendChild(txtBox1);

var td2 = document.createElement(“td”);

var txtBox2 = document.createElement(‘input’);

txtBox2.setAttribute(‘width’, 300);

txtBox2.setAttribute(‘type’, ‘text’);

txtBox2.setAttribute(‘name’, ‘col1’);

txtBox2.setAttribute(‘value’, col2);

td2.appendChild(txtBox2);

var td3 = document.createElement(“td”);

var btnUpdate = document.createElement(‘input’);

btnUpdate.setAttribute(‘type’, ‘button’);

btnUpdate.setAttribute(‘name’, ‘Update’);

btnUpdate.setAttribute(‘value’, ‘Update’);

btnUpdate.onclick = function() { Update(this.parentNode.parentNode); };

var btnCancel = document.createElement(‘input’);

btnCancel.setAttribute(‘type’, ‘button’);

btnCancel.setAttribute(‘name’, ‘Cancel’);

btnCancel.setAttribute(‘value’, ‘Cancel’);

btnCancel.onclick = function() { Cancel(this.parentNode.parentNode); };

td3.appendChild(btnUpdate);

td3.appendChild(btnCancel);

editableRow.appendChild(td1);

editableRow.appendChild(td2);

editableRow.appendChild(td3);

row.parentNode.replaceChild(editableRow, row);

}

After edit button click

Updating Data

//  this function handles update button click

function Update(row) {

// fetches values entered in the textboxes

// first childNode represent <td> inside <tr> and second childNode represents textbox

var col1 = row.childNodes[0].childNodes[0].value;

var col2 = row.childNodes[1].childNodes[0].value;

// values sent to server

PageMethods.UpdateData(col1, col2, UpdateSuccess(row), ExceptionHandler);

}

// After updating the values successfully

function UpdateSuccess(row) {

var col1 = row.childNodes[0].childNodes[0].value;

var col2 = row.childNodes[1].childNodes[0].value;

var editableRow = document.createElement(“tr”)

var td1 = document.createElement(“td”)

td1.innerText = col1;

var td2 = document.createElement(“td”);

td2.innerText = col2;

var td3 = document.createElement(“td”);

var btnEdit = document.createElement(‘input’);

btnEdit.setAttribute(‘type’, ‘button’);

btnEdit.setAttribute(‘name’, ‘Edit’);

btnEdit.setAttribute(‘value’, ‘Edit’);

btnEdit.onclick = function() { Edit(this.parentNode.parentNode); };

var btnDelete = document.createElement(‘input’);

btnDelete.setAttribute(‘type’, ‘button’);

btnDelete.setAttribute(‘name’, ‘Delete’);

btnDelete.setAttribute(‘value’, ‘Delete’);

btnDelete.onclick = function() { DeleteRow(this.parentNode.parentNode); };

td3.appendChild(btnEdit);

td3.appendChild(btnDelete);

editableRow.appendChild(td1);

editableRow.appendChild(td2);

editableRow.appendChild(td3);

row.parentNode.replaceChild(editableRow, row);

}

// this function handles cancel button click

function Cancel(row) {

// values are again populated in labels instead of textboxes

var col1 = row.childNodes[0].childNodes[0].value;

var col2 = row.childNodes[1].childNodes[0].value;

var editableRow = document.createElement(“tr”)

var td1 = document.createElement(“td”)

td1.innerText = col1;

var td2 = document.createElement(“td”);

td2.innerText = col2;

var td3 = document.createElement(“td”);

var btnEdit = document.createElement(‘input’);

btnEdit.setAttribute(‘type’, ‘button’);

btnEdit.setAttribute(‘name’, ‘Edit’);

btnEdit.setAttribute(‘value’, ‘Edit’);

btnEdit.onclick = function() { Edit(this.parentNode.parentNode); };

var btnDelete = document.createElement(‘input’);

btnDelete.setAttribute(‘type’, ‘button’);

btnDelete.setAttribute(‘name’, ‘Delete’);

btnDelete.setAttribute(‘value’, ‘Delete’);

btnDelete.onclick = function() { DeleteRow(this.parentNode.parentNode); };

td3.appendChild(btnEdit);

td3.appendChild(btnDelete);

editableRow.appendChild(td1);

editableRow.appendChild(td2);

editableRow.appendChild(td3);

row.parentNode.replaceChild(editableRow, row);

}

//  this function handles ‘add’ button click

function Add() {

var col1 = $get(“txt_addcol1”).value;

var col2 = $get(“txt_addcol2”).value;

// data sent to the database

PageMethods.InsertData(col1, col2, AddSuccess(col1, col2), ExceptionHandler);

}

// After adding the data successfully

function AddSuccess(col1, col2) {

// add the values to the table

AddRow(col1, col2);

// clear the textboxes in the footer

$get(“txt_addcol1”).value = “”;

$get(“txt_addcol2”).value = “”;

}

Deleting Data

// this function handles delete button click

function DeleteRow(row) {

var col1 = row.childNodes[0].innerText;

// delete from the database

PageMethods.DeleteData(col1, DeleteSuccess(row), ExceptionHandler);

}

function DeleteSuccess(row) {

// delete the row from the table

var tbody = $get(“tbody_grid”);

tbody.removeChild(row);

}

</script>

Page methods to handle Edit, Update, Delete and Insert:

[WebMethod]

public static void UpdateData(string sCol1, string sCol2)

{

try

{

Data update part should go here

}

catch(Exception ex)

{

throw ex;

}

}

[WebMethod]

public static void InsertData(string sCol1, string sCol2)

{

try

{

Data insert part should go here

}

catch(Exception ex)

{

throw ex;

}

}

[WebMethod]

public static void DeleteData(string sCol1)

{

try

{

Data delete part should go here

}

catch (Exception ex)

{

throw ex;

}

}

}

We can add paging and sorting too.


This code has been tested in IE7+, Firefox, Chrome and Safari.

Some of the limitations in using ASP.NET AJAX PageMethods:

1.       We can’t access asp.net server controls (like TextBox control) in the WebMethod directly as we normally access in the server side methods.

2.       We can’t access any variable declared in the code behind.

Advantage: PageMethods is a simple lightweight way to submit/fetch data to the server using ASP.NET AJAX. This doesn’t submit whole page data to the server and also as opposed to the ASP.NET AJAX call back this doesn’t even fire the Page_Load and other Page events of the code behind page

Introduction

One of the new features being added to version 4.0 of ASP.NET is the ability to control the client side IDs that are generated by the framework.  Previously the framework would modify the client side IDs to uniquely identify each control.  This some times left you with the ID you defined in markup or sometimes left you with something that looks like this, “ctl00_MasterPageBody_ctl01_Textbox1.”

The Problem

The modification of the client side id property works great to ensure that each element is uniquely identified, however, to anyone that has tried to do any sort of client side scripting this becomes very frustrating. Chances are that if you have worked in ASP.NET for any time at all you have run into this issue.  The problem is that until runtime you do not what the client side ID could be, making it difficult to do any kind of client side scripting.  In addition any modification of the page, adding removing controls, can result in a different client side ID being generated.

Old Solution

Again if you have worked with ASP.NET for any amount of time you know there is a work around for this issue.  Each control has a property called ClientID that is a read only and supplies the unique client side ID.  You can use this in a code behind when dynamically adding scripts, or more commonly use inline code (old ASP style) to supply the value to and client side scripts.

<script type="text/javascript">
    function DoSomething(){
        alert('<%= Control.ClientID %>');
    }
</script>

ASP.NET 4.0 Solution

First off let me start by explaining why we decided to tackle this problem in version 4.0 of the framework.  While we provided a way of supplying the developer with the client side ID, with the growth of client side scripting this solution has become some what hacky.  There is not really a clean way to use this with lots of controls and lots of external script files.  Also it might have had something to do with the developer asking for control over this.  Developers do love to have control of everything, weather they use it or not, it’s just our nature 🙂 The solution that we came up has four ‘modes’ that a user can use giving them everything from existing behavior to full control.  The controls ID property is modified according to the ClientIDMode mode and then used as the client side id.

Modes and what they do

There is now a new property on every control (this includes pages and master pages as they inherit from control) called ClientIDMode that is used to select the behavior of the client side ID.

<asp:Label ID="Label1" runat="server" ClientIDMode="[Mode Type]" />

The Mode Types

  • Legacy: The default value if ClientIDMode is not set anywhere in the control hierarchy.  This causes client side IDs to behave the way they did in version 2.0 (3.0 and 3.5 did not change this code path) of the framework. This mode will generate an ID similar to “ctl00_MasterPageBody_ctl01_Textbox1.”
  • Inherit: This is the default behavior for every control.  This looks to the controls parent to get its value for ClientIDMode.  You do not need to set this on every control as it is the default, this is used only when the ClientIDMode has been changed and the new desired behavior is to inherit from the controls parent.
  • Static: This mode does exactly what you think it would, it makes the client side ID static. Meaning that what you put for the ID is what will be used for the client side ID.  Warning, this means that if a static ClientIDMode is used in a repeating control the developer is responsible for ensuring client side ID uniqueness.
  • Predictable: This mode is used when the framework needs to ensure uniqueness but it needs to be done so in a predictable way.  The most common use for this mode is on databound controls.  The framework will traverse the control hierarchy prefixing the supplied ID with it’s parent control ID until it reaches a control in the hierarchy whose ClientIDMode is defined as static.  In the event that the control is placed inside a databound control a suffix with a value that identifies that instance will also be added to the supplied ID.  The ClientIDRowSuffix property is used to control the value that will be used as a suffix (see samples).  This mode will generate an ID similar to “Gridview1_Label1_0”

Samples

Legacy Mode

Legacy mode is pretty straight forward, it generates a client side ID the way that it had in version 2.0 of the framework.

markup:

<asp :TextBox ID ="txtEcho" runat ="server" Width ="65%" ClientIDMode ="Legacy" />

output:

<input id="ctl00_MasterPageBody_ctl00_txtEcho" style="width: 65%" name="ctl00$MasterPageBody$ctl00$txtEcho" />

Static Mode

Static is the most basic of all ClientIDMode modes, what you give for the ID is what you get for the client side ID. Once again a warning that if a static ClientIDMode is used inside of a repeated control it is the developer’s responsibility to ensure client side ID uniqueness.

markup:

<asp:TextBox ID="txtEcho2" runat="server" Width="65%" ClientIDMode="Static" />

output:

<input id="txtEcho2" style="width: 65%" name="ctl00$MasterPageBody$ctl00$txtEcho2" />

Predictable Mode

Predictable mode really tackles the heart of the problem.  The framework previously generated it’s unique IDs to prevent ID collisions and the most common place for these types of collisions are inside databound controls.  Predictable mode is really designed to work with databound controls but does not have to.  There is three ways to uses the predictable mode, each one of these is defined through the ClientIDRowSuffix property that specifies the suffix for each instance.  The ClientIDRowSuffix uses values from the control’s datakeys collection, so if the control does not have a datakeys collection this property is not viable.  If this property is not set or is not available the row index will be used in it’s place.

1. With no ClientIDRowSuffix defined, this is also the behavior for databound controls without a datakeys collection e.g. Repeater Control.  Notice that the framework has traversed the control hierarchy and prefixed the ID with the parent’s ID and suffixed the ID with row index.

markup:

<asp:GridView ID="EmployeesNoSuffix" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" >
    <Columns>
        <asp:TemplateField HeaderText="ID">
            <ItemTemplate>
                <asp:Label ID="EmployeeID" runat="server" Text='<%# Eval("ID") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <asp:Label ID="EmployeeName" runat="server" Text='<%# Eval("Name") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

output:

<table id="EmployeesNoSuffix" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
    <tbody>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
        </tr>
        <tr>
            <td><span id="EmployeesNoSuffix_EmployeeID_0">1</span></td>
            <td><span id="EmployeesNoSuffix_EmployeeName_0">EmployeeName1</span></td>
        </tr>
        ...
        <tr>
            <td><span id="EmployeesNoSuffix_EmployeeID_8">9</span></td>
            <td><span id="EmployeesNoSuffix_EmployeeName_8">EmployeeName9</span></td>
        </tr>
    </tbody>
</table>

2. With a ClientIDRowSuffix defined, this looks in the control’s datakeys collection for the value and then suffixes the ID with that value.

markup:

<asp:GridView ID="EmployeesSuffix" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" ClientIDRowSuffix="ID" >
    <Columns>
        <asp:TemplateField HeaderText="ID">
            <ItemTemplate>
                <asp:Label ID="EmployeeID" runat="server" Text='<%# Eval("ID") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <asp:Label ID="EmployeeName" runat="server" Text='<%# Eval("Name") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

output:

<table id="EmployeesSuffix" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
    <tbody>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
        </tr>
        <tr>
            <td><span id="EmployeesSuffix_EmployeeID_1">1</span></td>
            <td><span id="EmployeesSuffix_EmployeeName_1">EmployeeName1</span></td>
        </tr>
        ...
        <tr>
            <td><span id="EmployeesSuffix_EmployeeID_9">9</span></td>
            <td><span id="EmployeesSuffix_EmployeeName_9">EmployeeName9</span></td>
        </tr>
    </tbody>
</table>

3. With a ClientIDRowSuffix defined, but instead of just one value a compound value will be used.  Exhibits the same behavior as one value but it will suffix both values onto the ID.

markup:

<asp:GridView ID="EmployeesCompSuffix" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" ClientIDRowSuffix="ID, Name" >
    <Columns>
        <asp:TemplateField HeaderText="ID">
            <ItemTemplate>
                <asp:Label ID="EmployeeID" runat="server" Text='<%# Eval("ID") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <asp:Label ID="EmployeeName" runat="server" Text='<%# Eval("Name") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

output:

<table id="EmployeesCompSuffix" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
    <tbody>
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
        </tr>
        <tr>
            <td><span id="EmployeesCompSuffix_EmployeeID_1_EmployeeName1">1</span></td>
            <td><span id="EmployeesCompSuffix_EmployeeName_1_EmployeeName1">EmployeeName1</span></td>
        </tr>
        ...
        <tr>
            <td><span id="EmployeesCompSuffix_EmployeeID_9_EmployeeName9">9</span></td>
            <td><span id="EmployeesCompSuffix_EmployeeName_9_EmployeeName9">EmployeeName9</span></td>
        </tr>
    </tbody>
</table>

Summary

The ability to fully control the client side IDs that are generated by the framework is a request that has not generated much noise but everyone seems to want it when you mention it.  We believe that we have found a good solution to the request and think that it adds some much need functionality for developer that use lots of client side scripting.  There is an early preview and a walk through of this feature in CTP build that we released at PDC 2008.  For more information and a much more detailed description of this feature read Scott Galloway’s blog post.

if it is helpful, plese dont forget to leave a comment.