Click to Rate and Give Feedback
Related Articles
Here the author introduces SQL Server Data Services, which exposes its functionality over standard Web service interfaces.

By David Robinson (July 2008)
Here the author answers questions regarding the Entity Framework and provides an understanding of how and why it was developed.

By Elisa Flasko (July 2008)
Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.

By Josh Smith (July 2008)
Systems that handle failure without losing data are elusive. Learn how to achieve systems that are both scalable and robust.

By Udi Dahan (July 2008)
More ...
Articles by this Author
As we'll show, with just a few lines of JavaScript you can build a general-purpose framework for incorporating page turns into Silverlight 1.0 apps.

By Jeff Prosise (May 2008)
: Jeff Prosise presents great tips for Silverlight development, which while it's gaining wide adoption, still needs more documentation and best practices so developers can make the most of the dazzling new features.

By Jeff Prosise (Launch 2008)
Jeff Prosise shows how you can implement drag-and-drop functionality in your Web app with ASP.NET AJAX.

By Jeff Prosise (January 2008)
Jeff Prosise explains when it's better to use UpdatePanel and when it's better to use asynchronous calls to WebMethods or page methods instead.

By Jeff Prosise (June 2007)


By Jeff Prosise (March 2007)
Jeff Prosise describes performance problems in an ASMX Web service that relied on legacy COM and Visual Basic 6.0 to perform key processing tasks and the approach he took to find a fix.

By Jeff Prosise (October 2006)


By Jeff Prosise (July 2006)
Data-driven site navigation is among the niftiest and most useful features in ASP. NET 2. 0. To get it working, all you do is create an XML site map file (or a SQL site map if you're using the MSDN®Magazine SqlSiteMapProvider), add a SiteMapDataSource, and bind a TreeView or Menu to the SiteMapDataSource.

By Jeff Prosise (June 2006)
More ...
Popular Articles
Here we present a rundown of the various language paradigms of CLR-based languages via short language introductions and code samples.

By Joel Pobar (May 2008)
Kenny Kerr sings the praises of the new Visual C++ 2008 Feature Pack, which brings modern conveniences to Visual C++.

By Kenny Kerr (May 2008)
In this article we introduce you to BizTalk Services, new technology that offers the Enterprise Service Bus features of BizTalk Server as a hosted service.

By Jon Flanders and Aaron Skonnard (June 2008)
Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.

By Josh Smith (July 2008)
More ...
Read the Blog
There are many things called threat modeling. Rather than argue about which is "the one true way," a good practice is to consider your needs and what your skills, abilities, and schedules are, and then work with a method that's best for you. In the July 2008 issue of MSDN Magazine, ...
Read more!
Want to develop games for Xbox Live? Want to get paid for it, too? Click on over to the XNA Team Blog to learn more about their initial rollout of the XNA Creators Club for XNA Game Studio. ...
Read more!
The Microsoft Entity Data Model (EDM), based on Dr. Peter Chen's Entity Relationship (ER) model, is the driving force behind the ADO.NET Entity Framework. The EDM is also the feature that most significantly differentiates the Entity Framework from other ORM-style technologies in the marketplace. In the July 2008 issue of MSDN ...
Read more!
System.IO.File is a handy helper class for reading and writing data, but its methods support only synchronous operation. Is there an easy way to provide File’s functionality for asynchronous file I/O? In the July 2008 issue of MSDN Magazine, Stephen Toub walks through several ...
Read more!
Remember .NET Terrarium, the interactive game meant to introduce .NET development techniques? Well, the Windows SDK team has released the source code for .NET Terrarium 2.0 on CodePlex. You can read more about this release on the Windows SDK blog and at Microsoft ...
Read more!
The Enumerable class plays an important role in every LINQ query you create. Because the Enumerable class's extension methods can process many other classes—including Array and List—you can use methods of the Enumerable class not only to create LINQ queries, but also to manipulate the behavior of arrays and other data structures. In the July 2008 issue of MSDN ...
Read more!
More ...
Wicked Code
Client-side Paging for DataGrids
Jeff Prosise

Code download available at: WickedCode0402.exe (120 KB)
Browse the Code Online
Developers enjoy a love-hate relationship with the ASP.NET DataGrid control. On one hand, DataGrids vastly simplify the task of rendering data sources into stylish HTML tables. On the other hand, DataGrids do the bulk of their work on the server side, causing pages to post back to the server more often than most developers would like. A pageable DataGrid, for example, posts back to the server every time the user navigates from one page to another. Postbacks reduce performance and inhibit responsiveness. No wonder, then, that high on many a developer's wish list is a smarter DataGrid that pages through records without repeatedly posting back to the server.
Conceptually, building a pageable HTML table that does its paging on the client side isn't difficult, thanks to the magic of client-side scripting. The HTML page in Figure 1 shows one technique for implementing client-side paging. When viewed in a browser (see Figure 2), the page displays what appears to be one table. But actually, there are three tables encapsulated in <div> elements. The style attribute attached to each <div> ensures that only one table is visible at a time, and a few lines of JavaScript wired to the Previous and Next links programmatically hide the <div> that's currently displayed, while making the one before or after it visible. The appearing and disappearing tables create the illusion that a single table is used to page through records.
Figure 2 Pageable HTML Table 
The same technique can be used to create DataGrids that support client-side paging. The challenge is to modify the DataGrid class to produce output similar to that in Figure 1. In case you haven't guessed, that's precisely what this installment of Wicked Code is about: improving the ASP.NET DataGrid control by building a plug-in replacement that pages on the client. In addition to adding a useful new tool to your toolbox, it's a wonderful example of how to modify ASP.NET controls by deriving from them and adding functionality of your own.

Introducing the ClientPageDataGrid Control
ClientPageDataGrid is a DataGrid-derived class that adds client-side paging support to ASP.NET. Because it's a functional replacement for the DataGrid, you can use it wherever you'd normally use a DataGrid. Client-side paging comes absolutely for free and can even be turned off.
The Web page in Figure 3 does paging the normal way: by repeatedly posting back to the server. It hosts a DataGrid that's configured to paginate its output (AllowPaging="true") and show 16 records per page (PageSize="16"). Clicking either of the arrows in the pager at the bottom of the grid posts back to the server and fires a PageIndexChanged event, activating the OnNewPage method. The OnNewPage method sets the DataGrid's CurrentPageIndex property to the index of the next or previous page, thus causing that page to render back to the client.
Figure 4 shows the same page implemented with ClientPageDataGrid, with changes shown in red. The DataGrid control has been replaced with ClientPageDataGrid. AllowPaging="true" has been changed to AllowClientPaging="true" to enable client-side paging, and PageSize="16" now reads ClientPageSize="16". Both the OnPageIndexChanged attribute and the OnNewPage method have been deleted. Neither is needed now that paging will be handled entirely on the client.
To take the ClientPageDataGrid control for a test drive, copy ClientPaging.aspx to a virtual directory (for example, wwwroot) on your ASP.NET Web server. Then copy the control assembly—ClientPageDataGrid.dll—to the virtual directory's bin subdirectory and open ClientPaging.aspx in your browser. You'll see a page similar to the one in Figure 5. Use the arrows to page backward and forward. ClientPaging.aspx looks and feels exactly like ServerPaging.aspx, but under the hood it is quite different. ServerPaging.aspx places more load on the Web server and can only page as quickly as the connection between client and server will allow. ClientPaging.aspx, by contrast, takes slightly longer to load due to the fact that it's pulling down all of the records rather than just one page worth, but once loaded it pages quite nimbly, regardless of the connection speed.
Figure 5 ClientPageDataGrid 
ClientPageDataGrid's programmatic interface consists of four public properties that it adds to those inherited from DataGrid (see Figure 6). AllowClientPaging turns paging on and off. The default is true, so you can omit AllowClientPaging="true" from ClientPageDataGrid tags if you want. ClientPageSize controls the number of records shown on each page, while ClientPageCount retrieves the page count. ClientCurrentPageIndex determines which page is currently displayed. The following Page_Load method configures the ClientPageDataGrid named "MyDataGrid" to initially show Page 2 (whose zero-based index is 1) rather than Page 1:
void Page_Load (Object sender, EventArgs e)
{
    if (!IsPostBack) {
        BindDataGrid ();
        MyDataGrid.ClientCurrentPageIndex = 1;
    }
}
When a page that hosts a ClientPageDataGrid posts backs to the server, the ClientCurrentPageIndex property is updated to indicate which page was displayed on the client when the postback occurred. Between postbacks, the server does not know which page the user is currently viewing.

Inside ClientPageDataGrid
The ability of ClientPageDataGrid to output paginated HTML tables begins with the Render method, which is excerpted in Figure 7. (You can find the complete code download for this column at the link at the top of this article.) Render is a virtual method that all server controls inherit from System.Web.UI.Control. The Render method is called by the Microsoft® .NET Framework to render a control into HTML each time the page hosting the control is requested.
ClientPageDataGrid overrides DataGrid's Render method and calls the base class implementation not once, but several times. Specifically, ClientPageDataGrid calls DataGrid.Render once per page of output, and it encapsulates the output in <div> elements by positioning calls to base.Render between calls to RenderBeginTag and RenderEndTag. Before calling the base class's Render method, ClientPageDataGrid.Render invokes a local method named ShowItems to hide rows that don't appear on the page currently being rendered by setting the Visible properties of those rows to false.
For a ClientPageDataGrid configured to display 16 rows per page, the result is a Page 1 consisting of a <div> containing the grid's first 16 items, a Page 2 consisting of a <div> containing the next 16 items, and so on. All <div> elements but the one representing the page that's visible include style="display: none" attributes that prevent them from being seen in a browser. Only the <div> representing the current page—the page whose index equals ClientCurrentPageIndex—is rendered with a style="display: block" attribute to make it visible to the user (see Figure 8).
Each "page" rendered by ClientPageDataGrid includes a pager. The pager is present in the DataGrid because ClientPageDataGrid overrides DataGrid's DataBind method and calls it with AllowPaging set to true and PageSize set to the total number of items in the data source. Much of the code in ClientPageDataGrid.cs is devoted to making the pager work and ensuring that it supports both previous/next-style pagers (<PagerStyle Mode="NextPrev">) and numeric pagers (<PagerStyle Mode="NumericPages">).
A conventional DataGrid pager contains LinkButtons that post back to the server and fire PageIndexChanged events. ClientPageDataGrid replaces these LinkButtons with hyperlinks pointing to JavaScript functions that page on the client. The UpdatePager method (see Figure 9), which is called just before Render calls the base class's Render method, does the replacing. UpdatePager finds the pager by scanning the DataGrid for a row whose type is ListItemType.Pager. Then it deletes the controls inside the pager and adds controls of its own.
Here's an example of the HTML that a conventional DataGrid control outputs when it renders a pager:
<tr>
  <td colspan="3">
    <a href= "javascript:__doPostBack('MyDataGrid$_ctl20$_ctl0','')">
      &lt;</a>&nbsp;
    <a href="http://javascript:__doPostBack('MyDataGrid$_ctl20$_ctl1','')">
      &gt;</a>
  </td>
</tr>
Here's the same pager rendered by a ClientPageDataGrid control:
<tr>
  <td colspan="3">
    <a href="http://javascript:__onPrevPage ('MyDataGrid');">&lt;</a>&nbsp;
    <a href="http://javascript:__onNextPage ('MyDataGrid');">&gt;</a>
  </td>
</tr>
The JavaScript functions referenced in the anchor tags are registered when ClientPageDataGrid.OnPreRender calls Page.RegisterClientScriptBlock. Calling RegisterClientScriptBlock ensures that the script block containing the functions is included in the output only once, even if a page hosts multiple instances of the ClientPageDataGrid control.
Even though a ClientPageDataGrid pager doesn't induce any postbacks of its own, other controls on the page might—and probably will—cause postbacks to occur. This presents two challenges. First, ClientCurrentPageIndex should be updated when a postback occurs so server-side code can determine which page was displayed when the postback occurred. Second, the ClientPageDataGrid rendered back to the client following a postback should preserve the current page. In other words, if Page 3 is displayed when the page posts back to the server, ClientPageDataGrid's rendering code should attach a style="display: block" attribute to the <div> element representing Page 3 rather than to the <div> element representing Page 1.
To meet these challenges, ClientPageDataGrid registers a hidden field named controlid__ PAGENUM:
Page.RegisterHiddenField (ClientID + "__PAGENUM",
    ClientCurrentPageIndex.ToString ());
The JavaScript functions that page the output on the client update the hidden field each time the current page changes. When the page posts back to the server, the value of the hidden field is included in the postback data. The LoadViewState method of ClientPageDataGrid reads the value from the postback data and assigns it to ClientCurrentPageIndex by calling a local method named RestoreCurrentPageIndex, which does the following:
string page = Page.Request[ClientID + "__PAGENUM"];
  •••
ClientCurrentPageIndex = Convert.ToInt32 (page);
A postback event handler can now determine which page the user was viewing by reading the ClientCurrentPageIndex property. And because ClientPageDataGrid.Render uses ClientCurrentPageIndex to determine which <div> to make visible, the page that was visible before the postback occurred will still be visible following the postback.
Restoring the current page index from postback data in LoadViewState has a couple of advantages. First, the current page index can't be set until after the control is populated with items, and it's in LoadViewState that the control gets populated following a postback. Additionally, because LoadViewState is called before the host page fires a Load event, a Page_Load method can find out what page the user was viewing when the postback occurred by reading ClientCurrentPageIndex. That might be important because the current page index could be used to affect other changes to the page.
RestoreCurrentPageIndex is also called by the DataBind method of the ClientPageDataGrid, but only if it wasn't called from LoadViewState. Why? Because if view state is disabled (that is, if the control's EnableViewState property is false), then LoadViewState isn't called by the ASP.NET runtime. If view state is disabled, the page repopulates the control by calling DataBind, so the DataBind method is the natural place for a second attempt at restoring the current page index from postback data.
You might have noticed that the DataBind method of ClientPageDataGrid binds to the data source not once, but twice (see Figure 10). The first call to the base class's DataBind method provides ClientPageDataGrid with a record count. The second call, which is made with AllowPaging set to true and PageSize set to the record count, produces a DataGrid containing all the records in the data source with a pager at the bottom. This DataGrid lies just beneath the surface of the ClientPageDataGrid and serves as the source of all rendering.
Another unusual aspect of ClientPageDataGrid's DataBind method is that it converts DataReaders into DataSets. The reason is twofold. First, a DataReader can only be bound to once because it's a forward-only data source. Second, ordinary DataGrids don't support DataReaders as data sources if AllowPaging is true unless AllowCustomPaging is also true. Internally converting DataReaders into DataSets neatly solves both problems and ensures that ClientPageDataGrid works as seamlessly with DataReaders as it does with DataSets.

Caveats
Are there drawbacks to paging on the client? You bet. The larger the recordset, the more slowly the page will load because all the records—not just the records comprising the current page—are returned in the HTTP response. For relatively small recordsets—say, 1,000 records or less—you probably don't care. For very large recordsets, load time could be unacceptably high.
An additional factor to consider when it comes to bandwidth utilization is the DataGrid's propensity to serialize its contents into view state. Large DataGrids increase download times in two ways. First, they generate large HTML tables. Second, they increase the view-state size. Because ASP.NET persists view state using a hidden field, a DataGrid's contents essentially appear once in every HTTP request and twice in every HTTP response. That's all the more reason to keep the record count small and resort to server-side paging for large recordsets. If you must use a ClientPageDataGrid to page through a large number of records, consider setting the control's EnableViewState property to false and binding to the data source in every page request (as opposed to only binding when Page.IsPostBack is false). This could decrease the size of the response by up to two-thirds.
A final thought to keep in mind regarding ClientPageDataGrid is that due to the way it renders individual pages, you'll probably want to make ClientPageSize an even number if you use the AlternatingItemStyle property to render odd-numbered items differently than even-numbered items. Otherwise, Page 1 will have a non-alternating item at the top, Page 2 will have an alternating item, and so on. The user may wonder why the format of the table changes each time he or she flips to the next or previous page.

Wrapping Up
The DataGrid class is a wonderful example of one of the most compelling features of ASP.NET: its ability to hide complex rendering and behavioral logic in reusable classes. ClientPageDataGrid takes the notion of server-side controls a step further and demonstrates not only how to modify built-in control types to work the way you want them to, but how to extend them to add client-side functionality as well. Server controls that rely on client-side script to work smarter and more efficiently are an idea whose time has come. Here's hoping that control authors agree.

Send your questions and comments to Jeff at  wicked@microsoft.com.


Jeff Prosise is a contributing editor to MSDN Magazine and the author of several books, including Programming Microsoft .NET (Microsoft Press, 2002). He's also a cofounder of Wintellect (http://www.wintellect.com), a software consulting and education firm that specializes in Microsoft .NET.

Page view tracker