Sep 3rd 2009 08:19 pm
Gridview + Object Data Source – fun with custom pagers
There’s a lot of functionality bundled into the asp.net gridview. Some developers I know, however, have avoided using it. They’ve had bad experiences with datagrids (the precursor to the gridview), custom pagers, and seem to have a general misunderstanding of the control. Like so many other asp.net controls – it’s perfect for demos but can be exceptionally difficult to customize. So instead we’ve custom coded the same functionality using repeaters and custom controls.
Fortunately for me, I have a good friend who took the time to truly understand the gridview and bend it to his will. The final product was scalable, flexible, extensible, and most importantly highly reusable.
At my current job, we’ve spent a lot of time reinventing the gridview wheel – creating customized “listings” that allowed for some combination of sorting, paging, changing of page size. Because each client’s design required slight variations in functionality and appearance we had to code each one separately. For example one client’s requirements called for a pager that with links like “1 2 3… 100 Next” where ”Next” takes you to page 3. Another client required “1 2 3 Next” where “Next” counterintuitively navigates you to page 4.
There were several pieces to this puzzle that had little to do with gridviews. Internally, we worked with the design team to standardize the listings they put into designs and proposals. As much as possible and we implemented a standard data access paradigm across our codebase. Most difficult of all, we worked with each team member to embrace and improve the framework as a whole to meet their individual needs. It’s not perfect and adoption will take time, but we’ve made great strides moving away from project managers afraid to ask “How much time will it take to allow users to sort that listing?” and “How much will it cost us to add paging?” .
When I began write a framework control to use on our new projects I didn’t want to use the out of the box gridview pager:

Instead, most of our clients requirements have some variation on this:

There were a lot of solutions online to do custom pagers that involving repeaters and other custom controls. Much of our own attempts involved complicated viewstate managers to track page index, page size, and sort expressions.
Many of the gridview based examples didn’t leverage the object data source and acted instead upon the IEnumerable bound to the gridview’s datasource. This prevented the pager from readily showing the total number of records and pages.
In my particular case, it wasn’t a stretch to require an object data source because it’s the only data source that the gridview can use to find total count, and our business layer lent itself well to selecting and counting records based on the same criteria.
The default gridview functionality gives custom pager implementers little information to go on. When another developer goes to create a custom pager templates, the gridview doesn’t give an easy way to get at the necessary information to render a pager- the total number of records, page index, total page count, etc. It also doesn’t control pager visibility as one would hope, leaving that too up tothe implementer.
To work around this, implementers can now attach to the “InitializeCustomPager” event. The eventArgs parameter contains the same information that gridview inheritors get in the “InitializePager” override – specifically, the PagedDataSource object and it’s properties: CurrentPageIndex, PageCount, PageSize, VirtualCount, etc.
/// <summary>
/// Set to true to make gridview render with its default pager
/// </summary>
public Boolean UseDefaultPager { get; set; }
protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
{
base.InitializePager(row, columnSpan, pagedDataSource);
if (!UseDefaultPager)
{
InitializeCustomPagerEventArgs e = new InitializeCustomPagerEventArgs(row, columnSpan, pagedDataSource);
RaiseInitializeCustomPager(e);
}
}
public void RaiseInitializeCustomPager(InitializeCustomPagerEventArgs e)
{
if (InitializeCustomPager != null)
{
InitializeCustomPager(this, e);
}
}
public event InitializeCustomPagerHandler InitializeCustomPager;
public delegate void InitializeCustomPagerHandler(object sender, InitializeCustomPagerEventArgs e);
public class InitializeCustomPagerEventArgs
{
public GridViewRow PagerRow { get; private set; }
public int ColumnSpan { get; private set; }
public PagedDataSource PagedDataSource { get; private set; }
internal InitializeCustomPagerEventArgs(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
{
this.ColumnSpan = columnSpan;
this.PagedDataSource = pagedDataSource;
this.PagerRow = row;
}
}
To illustrate this, check out the “AlternatePager.aspx” page of the sample code at the end of this post.
The more paging we did, the more we began using a default sort when the control was first rendered. I must confess, my good friend Mr. Boudreau aka the “Gridview pimp” came up with the solution:
public String DefaultSortExpression
{
get
{
if (ViewState["defaultSortExpression"] == null)
{
ViewState["defaultSortExpression"] = String.Empty;
}
return (String)ViewState["defaultSortExpression"];
}
set
{
ViewState["defaultSortExpression"] = value;
}
}
public SortDirection DefaultSortDirection
{
get
{
if (ViewState["defaultSortDirection"] == null)
{
ViewState["defaultSortDirection"] = SortDirection.Ascending;
}
return (SortDirection)ViewState["defaultSortDirection"];
}
set
{
ViewState["defaultSortDirection"] = value;
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (!String.IsNullOrEmpty(DefaultSortExpression))
{
Sort(DefaultSortExpression, DefaultSortDirection);
}
}
protected override DataSourceSelectArguments CreateDataSourceSelectArguments()
{
var arguments = base.CreateDataSourceSelectArguments();
//Sets up our default sort expression and direction on the gridview.
if (String.IsNullOrEmpty(arguments.SortExpression) && (!String.IsNullOrEmpty(DefaultSortExpression)))
{
arguments.SortExpression = DefaultSortExpression;
if (!arguments.SortExpression.EndsWith("DESC") && !arguments.SortExpression.EndsWith("ASC"))
{
if (DefaultSortDirection == SortDirection.Descending)
{
arguments.SortExpression += " DESC";
}
else
{
arguments.SortExpression += " ASC";
}
}
}
return arguments;
}
Each pager section – page size, page links, and row count - is contained within a container div. Each of these divs is styled according to the properties DefaultPagerRowCountCssClass, DefaultPagerPageLinkCssClass, and DefaultPagerPageSizeCssClass.
Lastly, the ability to change data pages within the gridview already has a large amount of support built into the control. Simply give the page navigatoin controls inside your pager a CommandName of “Page” and a CommandArgument of pageIndex(1,2,3,etc.), “Next”, “Previous”, “First” or “Last”.
My work can be found here.
No Comments yet »