Introduction
In my article about a custom GridView pager, I received feedback with concerns on how the paging ability of Web Controls (DataSources, GridView
etc.) relies on ViewState thus affecting the size of the page data. In this article, I present you with a ViewState-free version of the original FullGridPager
class. The new class is called FullGridPagerNVS
where NVS stands for "NoViewState". It implements paging functionality without relying on GridView
's paging features - the GridView
's AllowPaging
property is set to false
. Furthermore, no data source is used to bind data to the GridView
. This is done by two Data Layer classes (DataLayerHelper
and CustomersDataLayer
) that return only the appropriate rowset constrained by row boundaries.
Figure 1 illustrates the custom pager (the format is exactly the same as in the original article).
Figure 1: The custom pager
Background
Optionally, my original article on how to create a custom pager for the GridView
PagerTemplate
template.
What you need to run the code
Besides the FullGridPagerNVS.cs found in the App_Code folder, the following files are also needed in order to make the pager work:
- FullGridPagerNVS.css: the styles used by the pager.
- Images folder: contains the images for the First, Previous, Next, and Last links.
- DataLayerHelper.cs: a general purpose database data layer class.
- CustomersDataLayer.cs: a class that returns database rowsets with paging in mind. It uses the Customers table of the Northwind database.
As always, a test bed ASPX web form is included in the ZIP file to demonstrate the usage of the FullGridPagerNVS
class:
- NVSTest.aspx and NVSTest.aspx.cs.
Using the code
Since this is a no-standard ASP.NET implementation, let me explain how it works. First of all, let me list the primary concerns and constraints of this project:
- No data source can be used to fetch data. Instead, code is written to implement a very simple data layer that brings data from the database with paging in mind. The
DataLayerHelper
class is a general purpose data layer that merely executes parameterized SQL queries against a database. Although it contains a bunch of methods, only the Get
and the ExecuteScalar
are of interest to us. The CustomersDataLayer
class uses the DataLayerHelper
methods to query the Customers table of the Northwind database, in particular:
- The
GetCustomers
method returns all the rows of the Customers table in a DataTable
. - The
GetCustomersCount
method returns the total number of rows in the Customers table. - The
GetPageCustomers
method uses the other two methods above to return a row-limited DataTable
. It takes two parameters:
startIndex
, the row number to start retrieving from, and maxPageRows
, the maximum number of rows to display per page.
This way, we can specify row boundaries, for example if startIndex
= 10 and maxPageRows
= 5, the rowset to be returned will only contain rows 10, 11, 12, 13, 14. Have in mind that startIndex
is zero based. The code snippet below simply removes the unnecessary rows outside the desired boundaries (rows 0-9 and 15-max):
public static DataTable GetPageCustomers(int startIndex, int maxPageRows)
{
DataTable tbl= GetCustomers();
int maxRows = GetCustomersCount();
int max = startIndex + (maxPageRows - 1);
if (max > maxRows) max = maxRows;
for (int i = 0; i < startIndex; i++)
tbl.Rows[i].Delete();
for (int i = max + 1; i < maxRows; i++)
tbl.Rows[i].Delete(); return tbl;
}
Since there is no data source, the following code (NVSTest.aspx.cs file) is used to manually bind data to the GridView
:
GridView1.DataSource =
CustomersDataLayer.GetPageCustomers(StartIndex, PageSize); GridView1.DataBind();
The GridView
must have its AllowPaging
property set to false
, of course. Consequently, there is no PageIndex
or PageCount
properties to rely on. The total number of rows is a value we must know of, too. As a result, such values are initialized in the web page code, and are stored in Session state:
public partial class NVSTest : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e
{
if (!IsPostBack)
{
StartIndex = 0;
MaxRows = CustomersDataLayer.GetCustomersCount();
BindData();
}
else
ShowFullGridPagerNVS();
}
public int StartIndex
{
get { return (int)Session["startIndex"]; }
set { Session["startIndex"] = value; }
}
public int MaxRows
{
get { return (int)Session["maxRows"]; }
set { Session["maxRows"] = value; }
}
}
You may as well store them anywhere you like - even in the ViewState! Here is what they mean:
StartIndex
: the binding rowset must begin with this row.MaxRows
: the total number of rows in the binding rowset.
With the above points in mind, I had to make quite a few changes to the FullGridPagerNVS
class.
- Besides the
MaxVisiblePageNumbers
, the total number of rows (MaxRows
) and the number of rows per page (MaxPageRows
) must be passed in as parameters. - The class calculates the total number of pages (
totalPages
). This substitutes the PageCount
property of the GridView
. - The class calculates the current page by converting the row index (
StartIndex
) to a page index:
currentPageIndex = (int)Math.Ceiling((decimal)StartIndex / maxPageRows);
This substitutes the PageIndex
property of the GridView
.
The customer pager can no longer be placed inside the PageTemplate
template. This affects the behaviour of the dynamically created controls during postbacks. The class now dynamically creates all the necessary controls including the First, Previous, Next, Last links as well as the page groups DropDownList
. Please note that before re-creating them, the HTML container is cleared out:
public void CreateCustomPager(Control Container, int StartIndex) {
[...]
HtmlTable pagerInnerTable =
(HtmlTable)Container.FindControl("pagerInnerTable");
if (pagerInnerTable != null)
{
pagerInnerTable.Rows[0].Cells.Clear();
[...]
}
[...]
HtmlTable pagerOuterTable =
(HtmlTable)Container.FindControl("pagerOuterTable");
if (pagerOuterTable != null)
{
if (pagerOuterTable.Rows[0].Cells.Count > 1)
pagerOuterTable.Rows[0].Cells.RemoveAt(1);
[...]
}
}
All the page links can no longer call the special Page commands. Their CommandArgument
value can no longer be the PageIndex
value. This is also true for the page groups DropDownList
. The FullGridPagerNVS
maps each page index to the starting row index simply by multiplying the page index with the total number of rows per page. For example:
- The Previous page button value:
((currentPageIndex - 1) * maxPageRows).ToString();
The page number links values:
((i - 1) * maxPageRows).ToString();
The Last page link value:
((totalPages-1) * maxPageRows).ToString();
The ddlPageGroups
selected values:
new ListItem(group, (groupFirstPageNumber * maxPageRows).ToString());
Each time the user clicks the customer pager, the StartIndex
value must change and the GridView
must rebind data. The web page (NVSTest.aspx) has this responsibility now. The FullGridPagerNVS
class is unaware of data binding (and it is not its business, anyway). But, it must provide the means for handling the events of the dynamically created controls. It does so by specifying two event handlers:
public event CommandEventHandler PageNumberClick;
public event EventHandler PageGroupChange;
The dynamic controls are bound to these event handlers, for example:
lnkPage.Command += new CommandEventHandler(PageNumberClick);
Finally, the necessary code in the web page code-behind:
ullGridPager.PageNumberClick += new CommandEventHandler(lnkPage_Command);
fullGridPager.PageGroupChange += new EventHandler(ddlPageGroups_SelectedIndexChanged);
And, here is what happens when the user clicks a page number link:
void lnkPage_Command(object sender, CommandEventArgs e)
{
StartIndex = Int32.Parse(e.CommandArgument.ToString());
BindData();
}
Points of interest
Please make sure you understand this is a no-standard ASP.NET paging implementation. This is an on-demand work based on the feedback I received in my article about how to create a custom GridView pager. I urge you to read the original article which provides you with a straight-forward, ASP.NET oriented solution.
History