Introduction
The GridView
control is a very powerful and scalable .NET control. You can use it an application to list entities. However, there is a disadvantage
when you want to use the paging feature: you must systematically bind the GridView
with a selection of all the data when you change the index to see
other pages. Imagine that you must work with a datasource with 400,000 records: the postback will be expensive as the treatment is not optimized.
Background
I tried to come up with an implementation for a GridView
with an independent paging functionality. The purpose is to bind the GridView
with
the current DataRow
s that you see. SQL Server allows you to get part of the rows of a query with this syntax:
With Prod AS
( SELECT [ProductID],
[ProductName],
[SupplierID],
[CategoryID],
[QuantityPerUnit],
[UnitPrice],
[UnitsInStock],
[UnitsOnOrder],
[ReorderLevel],
[Discontinued] ,
ROW_NUMBER() OVER (order by ProductName) as RowNumber from Products )
SELECT [ProductID],
[ProductName],
[SupplierID],
[CategoryID],
[QuantityPerUnit],
[UnitPrice],
[UnitsInStock],
[UnitsOnOrder],
[ReorderLevel],
[Discontinued] from Prod Where RowNumber
Between @pBegin and @pEnd
I searched on CodeProject to see if a similar solution existed, and I found a source code which implemented a very smart pager. You can find
it here: http://www.codeproject.com/KB/aspnet/SmartPager.aspx. I extended the GridView
class
to create a GridviewPager
class which owns an instance of this SmartPager
control. The SmartPager
control is a complex type
property (it has several properties) so the pager class must implement the IStateManager
Interface and an event to know when the index changes.
The implementation of the smart pager generates an inconvenient postback in JavaScript (method
SmartPagerSelectPage
of
Smartpager.js) when the index changes
so two postbacks are executed. To resolve this problem, I parse all the request form keys to find the
CALLBACKPARAM
value in order to filter the bad postback and just
execute the good in the
GridviewPager
Page_Load
event.
Using the Code
using System;
using System.Net;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Avg.Controls;
namespace Avg.Controls
{
public delegate void DataSourceChangedEventHandler(object sender, bool isEmpty);
[ToolboxData("<{0}:GridViewPager runat="server">")]
public class GridViewPager : GridView
{
public event DataSourceChangedEventHandler DataSourceChanged;
public string CurrentViewID
{
get { return string.Concat("CurrentView_", UniqueID); }
}
public override object DataSource
{
get
{
if (ViewState[CurrentViewID] != null)
return ViewState[CurrentViewID];
else
return base.DataSource;
}
set
{
base.DataSource = value;
ViewState[CurrentViewID] = value;
if (value == null)
{
if (DataSourceChanged != null)
DataSourceChanged(this, true);
}
else
{
if (DataSourceChanged != null)
DataSourceChanged(this, false);
}
}
}
protected SmartPager pager;
public int PageNumberCliked
{
get { return (ViewState["PageNumberCliked"] == null) ? 1 : Int32.Parse(
ViewState["PageNumberCliked"].ToString()); }
set { ViewState["PageNumberCliked"] = value; }
}
public int Display
{
get { return pager.Display; }
set { pager.Display = value; }
}
public int rowB
{
get { return (Page.Session["rowB"] == null) ? 0 : Int32.Parse(
Page.Session["rowB"].ToString()); }
set { Page.Session["rowB"] = value; }
}
public int rowE
{
get { return (Page.Session["rowE"] == null) ? PageSize : Int32.Parse(
Page.Session["rowE"].ToString()); }
set { Page.Session["rowE"] = value; }
}
public int PageCount
{
get
{
if (RowCount == 0)
{
return PageSize;
}
if (this.DataSource == null)
{
throw new Exception("Datasource is empy");
}
if (PageSize == 0)
{
throw new Exception("Page size must be positive");
}
return (int)(RowCount / PageSize) + 1;
}
}
public int RowCount
{
get { return (Page.Session["RowCount"] == null) ? 5 : Int32.Parse(
Page.Session["RowCount"].ToString()); }
set { Page.Session["RowCount"] = value; }
}
public int CurrentPage
{
get { return (ViewState["CurrentPage"] == null) ? 1 : Int32.Parse(
ViewState["CurrentPage"].ToString()); }
set { ViewState["CurrentPage"] = value; }
}
public GridViewPager() : base()
{
pager = new SmartPager();
pager.OnClickEvent += new OnClickPagerNumberEventHandler(pager_OnClickEvent);
}
#region events to implement on the page side
private static readonly object OnSelectRowEventKey = new object();
public event EventHandler DoSelectRow
{
add { Events.AddHandler(OnSelectRowEventKey, value); }
remove { Events.RemoveHandler(OnSelectRowEventKey, value); }
}
protected virtual void OnSelectedRow(EventArgs e)
{
EventHandler handler = Events[OnSelectRowEventKey] as EventHandler;
if (handler != null)
handler(this, e);
else
throw new Exception("You must implement OnSelectRow method");
}
private static readonly object OnLoadRowCountEventKey = new object();
public event EventHandler DoLoadRowCount
{
add { Events.AddHandler(OnLoadRowCountEventKey, value); }
remove { Events.RemoveHandler(OnLoadRowCountEventKey, value); }
}
protected virtual void OnLoadedRowCount(EventArgs e)
{
EventHandler handler = Events[OnLoadRowCountEventKey] as EventHandler;
if (handler != null)
handler(this, e);
else
throw new Exception("You must implement OnLoadRowCount method");
}
#endregion events to implement on the page side
#region Component event
protected override void OnInit(EventArgs e)
{
base.OnInit(e); Page.Load += new EventHandler(Page_Load);
}
void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack) OnLoadedRowCount(EventArgs.Empty);
bool goodCall = true;
foreach (string Key in Page.Request.Form.AllKeys)
{
if (Key.EndsWith("CALLBACKPARAM"))
{
goodCall = false;
}
}
if (goodCall)
OnSelectedRow(EventArgs.Empty);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e); Controls.Add(pager);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
pager.PageCount = PageCount;
}
void pager_OnClickEvent(object sender, string pPageNum)
{
PageNumberCliked = Int32.Parse(pPageNum);
CurrentPage = PageNumberCliked;
if (CurrentPage == 1)
{
rowB = 0;
rowE = PageSize;
}
else
{
rowE = (PageSize * CurrentPage) - 1;
rowB = rowE - (PageSize - 1);
}
Page.Response.Write( "<script language="Javascript">__" +
"doPostBack('__Page', 'MyCustomArgument');</script>" );
}
#endregion Component event
#region IStateManager Members
protected override void LoadViewState(object savedState)
{
if (savedState != null)
{
object[] state = savedState as object[];
if (state != null && state.Length == 2)
{
base.LoadViewState(state[0]);
if (state[1] != null)
((IStateManager)this.pager).LoadViewState(state[1]);
}
}
else
base.LoadViewState(savedState);
}
protected override object SaveViewState()
{
object[] state = new object[2];
state[0] = base.SaveViewState();
if (pager != null)
state[1] = ((IStateManager)this.pager).SaveViewState();
return state;
}
protected override void TrackViewState()
{
base.TrackViewState();
if (pager != null)
((IStateManager)this.pager).TrackViewState();
}
#endregion
}
Points of Interest
I learned how to extend a complex control like GridView
.
History
If you have some suggestions, I'm open to them.