Figure 1: Our custom pager in action!

I never have liked the concept of storing all the data in whatever form (DataTable/Lists of records/etc.) to the ASP.NET gridview control and having it automatically manage paging for me. This could however be improved using Visual Studio 2008 wizards. However, this requires writing stored procedures.

I’ve got a concept for you, which works without a lot of extra work. The concept is:

  1. Inherit from asp:GridView and override the PageCount and the PageIndex properties
  2. Create in instance of my CustomPager class at DataBinding time.

The result is as shown in figure 1: It adheres to PageButtonCount, to any styles that you have applied to the GridView and it features a ‘jump to’ page input box.
The ‘native’ event handling in your ASPX, still can be maintained by this code since it emulates the PageIndexChanging event.

Other solutions, implement an AJAX updatepanel per row. This really minimizes unnecessary refreshing of grid data.
However, I don’t mind if say, 30 rows are being pulled from a DB and bound to a GridView. If we do so, we also get the best of two worlds, in one world, we get all data and have a fresh update of the real table rows, and in the other world, we have just the active row being fetched and we could end up having outdated data on our screen because of (for instance) co-writers/typers who updated rows at the database which are not reflected at our screen in grid (which is displayed using a gridview).

So, in other words, I like this pager control since it is a balanced solution! It has been tested on 10,000 records. WHen I would page to for instance, page 900, it really is a matter of fractions of a second to get a response. (For paging solutions  on SQL server data tables which contain say millions of rows, we would need a more sophisticated approach).

Here is the control source (it’s called gridhelper.cs)! (In theory, it should work for .NET 2.0 up to 3.5)

// if you use this code, please leave the original author
//author: Egbert Nierop
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace adccure.tools
{
    public sealed class GridView2 : GridView
    {
        public GridView2()
            : base()
        {
        }
        private int _pageCount;

        public override int PageIndex
        {
            get
            {
                object o = ViewState["pgi"];
                return o == null ? 0 : (int)o;
            }
            set
            {
                ViewState["pgi"] = value;
            }
        }

        public override int PageCount
        {
            get
            {
                return _pageCount;
            }

        }
        public void PageCountSet(int pageCount)
        {
            _pageCount = pageCount;
        }
    }

    public sealed class CustomPager : ITemplate
    {
        readonly Table tbl;
        readonly GridView2 _grid;

        //readonly int _totalPages;
        public CustomPager(GridView2 grid, int totalPages)
        {
            tbl = new Table();
            grid.PageCountSet(totalPages);
            tbl.Width = Unit.Percentage(100);
            _grid = grid;
        }
        void ITemplate.InstantiateIn(Control container)
        {
            container.Controls.Add(tbl);

            int pageSize = _grid.PageSize;
            int pageIndex = _grid.PageIndex;
            int pageButtonCount = _grid.PagerSettings.PageButtonCount;
            int pageCount = _grid.PageCount;
            ClientScriptManager cs = _grid.Page.ClientScript;
            _grid.PagerSettings.Visible = true;
            var trow = new TableRow();
            var trowpagePosition = new TableRow();
            tbl.Rows.Add(trow);
            tbl.Rows.Add(trowpagePosition);
            TextBox tb = new TextBox();
            tb.ID = "txtJumpPage";
            tb.MaxLength = 4;
            tb.Width = Unit.Pixel(40);
            tb.Text = (pageIndex + 1).ToString();
            //avoid bubble up by return false
            tb.Attributes["onkeydown"] = string.Format("if (event.keyCode==13) {__doPostBack('{0}', 'Page$' + this.value ); return false;}", _grid.UniqueID);

            LiteralControl lit = new LiteralControl(string.Format(" of {0}", (pageCount + 1).ToString()));
            TableCell posCaption = new TableCell();
            trowpagePosition.Cells.Add(posCaption);
            posCaption.Controls.Add(tb);
            posCaption.Controls.Add(lit);
            int cellspan = 0;
            if (pageIndex > 0)
            {
                var cellText = new TableCell();
                trow.Cells.Add(cellText);
                cellspan++;
                cellText.Controls.Add(new HyperLink()
                {
                    NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                    string.Format("Page${0}", pageIndex - pageButtonCount >= 0 ? (pageIndex - pageButtonCount) + 1 : 1), false),
                    Text = "<"
                });
            }
            for (int x = pageIndex; x < pageIndex + pageButtonCount && x <= pageCount; x++)
            {
                var cellText = new TableCell();
                cellspan++;
                trow.Cells.Add(cellText);
                cellText.Controls.Add(new HyperLink()
                {
                    NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                        string.Format("Page${0}", x + 1), false),
                    Text = (x + 1).ToString(),
                });

            }
            if (pageIndex + pageButtonCount < pageCount)
            {
                var cellText = new TableCell();
                cellspan++;
                trow.Cells.Add(cellText);

                cellText.Controls.Add(new HyperLink()
                {
                    NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                    string.Format("Page${0}", (pageIndex + pageButtonCount) + 1), false),
                    Text = ">"
                });
            }
            tbl.Visible = true;
            posCaption.HorizontalAlign = HorizontalAlign.Center;
            posCaption.ColumnSpan = cellspan;
        }
    }
}

Now, I don’t publish the code to read from sample data (such as northwind), it would be a yadda, yadda and you know the drill. (In my code, it is just a silly HttpReferrer table having all the columns which allow you research this specific statistical interest of your web site)
But in my datalayer, I have things like shown below. It is a great solution for those tables, for say, less than 100,000 records. SQL server is able to deal with these types of queries pretty well and we still avoid pumping around lots of redundant data on the network and gridview control.

public IList<HttpReferrer> getHttpReferrers(int pPage, int pPageSize, HttpReferrerSortOrder sortOrder,
                SortDirection sortDirection,
                out int totalRecords)
        {
            totalRecords = dcd.HttpReferrers.Count();
            IQueryable<HttpReferrer> retVal = null;
            if (sortDirection ==  SortDirection.Ascending)
            {
                switch (sortOrder)
                {
                    case HttpReferrerSortOrder.Referer:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.Referrer);
                        break;
                    case HttpReferrerSortOrder.IP:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.IP_Address);
                        break;
                    case HttpReferrerSortOrder.Page:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.page);
                        break;
                    default:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.ts);
                        break;
                }
            }
            else
            {
                switch (sortOrder)
                {
case HttpReferrerSortOrder.Referer:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.Referrer);
                        break;
                    case HttpReferrerSortOrder.IP:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.IP_Address);
                        break;
                    case HttpReferrerSortOrder.Page:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.page);
                        break;
                    default:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.ts);
                        break;
                }
            }
            return retVal.Skip(pPage * pPageSize).Take(pPageSize).ToList();
        }

So, our Grid, can sort and it can page.

How do we deal with the paging event at the code behind the aspx?
It’s so simple!

void gridHttpReferrers_PageIndexChanging(object sender, GridViewPageEventArgs e) 

gridHttpReferrers.PageIndex = e.NewPageIndex; 
gridHttpReferrers.DataBind(); 
}

void gridHttpReferrers_DataBinding(object sender, EventArgs e)

{   int totalRecords; 
   int pageSize = gridHttpReferrers.PageSize; 
   int pageIndex = gridHttpReferrers.PageIndex; 
  gridHttpReferrers.DataSource = datadal.getHttpReferrers(pageIndex, pageSize, SortOrder, SortDir, out totalRecords); 
   gridHttpReferrers.PagerTemplate = new CustomPager(gridHttpReferrers, totalRecords); 
   gridHttpReferrers.PageCountSet (totalRecords / pageSize);
}

What did I do to get the new grid behavior inside the ASPX?
Just rename the tag from asp:GridView to ctrl:GridView2 and create the reference (or in web.config)

<%@ Register TagPrefix="ctrl" Namespace="adccure.tools" %>

<ctrl:GridView2 runat="server" emptydatatext="No data available."  ID="gridHttpReferrers" AllowPaging="true" AllowSorting="True"  Width ="100%" AutoGenerateColumns="false" PageSize="20" DataKeyNames="ID"><PagerSettings Position="Top" PageButtonCount="20" />
<
Columns> ETC.

So, I hope this code was quite enlightning for you and you can play with it and have fun.