Using a PagedDataSource and your own data pager links
posted by
Jeff
You aren't restricted to just using a DataGrid when you want your data paged. The PagedDataSource class lets you bind just the data you want to display, and lets you build your own pager links.
It’s hard to ignore the power and features of the DataGrid control. It’s one
of the first things a lot of books on ASP.NET show you, and might even be the
reason that a lot of people commit to learning about the platform at all. It’s a
huge time saver.
Of course, with that time savings, there are some issues with it. You are first
bound to using a table to represent your data, which isn’t always ideal (for
example when you want a div-based layout). You also incur a performance hit in
some cases because you get a lot of functionality with the DataGrid that you
might not even need. DataGrids have a ton of child objects.
The irresistible lure, however, is that you really like the paging ability of
the DataGrid. That’s where the PagedDataSource class can save your life.
(Actually, there are no documented cases of the class saving anyone’s life, but
you can bet that if it could do such things it would gladly accept that role and
not complain about it.)
The PagedDataSource class has many of the same properties that the DataGrid has,
because it is the same logic used by the DataGrid. The difference is that you
can use it conjunction with DataLists and Repeaters, or frankly any control that
you can bind data to. You’re probably already familiar with AllowPaging,
PageCount, PageSize and DataSource, among others.
Using the PagedDataSource class
The first step in using the PagedDataSource class is to create an instance of it
and assign some data to it. The data can come from any of the typical places,
like DataReaders and DataSets. You’ll also need to enable paging and decide how
big your pages will be.
PagedDataSource objPds = new PagedDataSource();
objPds.DataSource = objTable.DefaultView;
objPds.AllowPaging = true;
objPds.PageSize = 10;
As it stands right now, you could bind this PagedDataSource to any control, like
a Repeater, and be done with it. The syntax isn’t any different from those of
other sources.
MyRepeater.DataSource = objPds;
MyRepeater.DataBind();
Naturally that isn’t very useful, so in between we’ll need to do some work on
the PagedDataSource. We can use the properties to learn some things about the
data so we can make decisions on what we should do with it. The PageCount
property will tell us how many pages there actually are after we’ve set the
PageSize, so there’s no need to try and calculate how many pages there are based
on the data you’ve given it. You can set which page to display with the
CurrentPageIndex property (keep in mind that this is a zero-based index, so the
first page is 0, not 1).
Continuing with our example, we might have this block of code to set the current
page based on the query string:
if (Request.QueryString["page"] != null)
{
// subtract 1 because the PagedDataSource uses a zero-based index
int intPage = (Convert.ToInt32(Request.QueryString["page"]) - 1);
// correct for a page index less than 0 or greater than the last page
if (intPage < 0) intPage = 0;
if (intPage > objPds.PageCount) intPage = objPds.PageCount - 1;
objPds.CurrentPageIndex = intPage;
}
In this example we’re driving the page index by the URL.
You could certainly do this with an array of LinkButtons like the DataGrid, but
one of the points here is reducing complexity, not increasing it. Dynamically
creating LinkButton controls and dealing with their data in event handlers isn’t
particularly difficult, but it is more work. We’re not all paid hourly you know!
Not only that, but the hyperlinks we’re using can be followed by search engines,
which is more than I can say about Javascript-based links in rendered controls.
Creating your own pager links
This is all very cool, but we’ll need some way for the user to navigate to the
additional pages. Since we’re not tied to the way the DataGrid creates the
paging navigation, we’re free to do as we please. I like to use a simple series
of not more than five numeric links, a forward and back, and a beginning and
end. The result is this static (shared) method:
public static string CreatePagerLinks(PagedDataSource objPds, string BaseUrl)
{
StringBuilder sbPager = new StringBuilder();
sbPager.Append("More: ");
if (!objPds.IsFirstPage)
{
// first page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("\">|<</a> ");
if (objPds.CurrentPageIndex != 1)
{
// previous page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&page=");
sbPager.Append(objPds.CurrentPageIndex.ToString());
sbPager.Append("\" alt=\"Previous Page\"><<</a> ");
}
}
// calc low and high limits for numeric links
int intLow = objPds.CurrentPageIndex - 1;
int intHigh = objPds.CurrentPageIndex + 3;
if (intLow < 1) intLow = 1;
if (intHigh > objPds.PageCount) intHigh = objPds.PageCount;
if (intHigh - intLow < 5) while ((intHigh < intLow + 4) && intHigh < objPds.PageCount) intHigh++;
if (intHigh - intLow < 5) while ((intLow > intHigh - 4) && intLow > 1) intLow--;
for (int x = intLow; x < intHigh + 1; x++)
{
// numeric links
if (x == objPds.CurrentPageIndex + 1) sbPager.Append(x.ToString() + " ");
else
{
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&page=");
sbPager.Append(x.ToString());
sbPager.Append("\">");
sbPager.Append(x.ToString());
sbPager.Append("</a> " );
}
}
if (!objPds.IsLastPage)
{
if ((objPds.CurrentPageIndex + 2) != objPds.PageCount)
{
// next page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&page=");
sbPager.Append(Convert.ToString(objPds.CurrentPageIndex + 2));
sbPager.Append("\">>></a> ");
}
// last page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&page=");
sbPager.Append(objPds.PageCount.ToString());
sbPager.Append("\">>|</a>");
}
// conver the final links to a string and assign to labels
return sbPager.ToString();
}
There’s a lot going on here, but if you take it one step at a time, you can see
it’s not difficult to follow. We’re going to take two parameters with this
static method, the PagedDataSource and a URL that will be the base of every
link. The first step is to create a StringBuilder object. Concatenating strings
this way
is much faster than using += because of the way the string is manipulated in
memory. Using the += operator causes a copy of the string to be made every time,
while the StringBuilder class does not.
If the IsFirstPage property isn’t true, then we’ll want to create the first
link as a “go to the beginning” link that appears as "|<." Next we’ll create a
“back one page” link, but only if it’s not redundant with the “go to the
beginning” link. The only case this would be true is if the CurrentPageIndex was
1. This link appears as "<<."
The next part, for the numeric links, is a little trickier. We want five links,
but not more than that, and if possible, we want the page we’re on to be in the
center of the five links. A series of calculations find the high and low values
our numeric links should fall in, based on the total number of pages and the
current page index. Reading through the calculations is easier than trying to
verbalize the code, so I’ll leave that up to you.
Once we have the range of numbers, we render each link, except for the page
number (for humans, not the zero-based CurrentPageIndex) that we’re currently
viewing.
Finally, we do roughly the opposite for the “move forward one page”and “move to
the last page” links. The primary difference is that we’re checking against the
PagedDataSource’s IsLastPage property, and avoiding a redundant link by adding 2
to the CurrentPageIndex. Why 2? We’ve got to compensate for the index being
zero-based and for the fact that it’s the next page we don’t want to link to in
an effort to eliminate the redundancy.
When we’re done, we need to return the links as a string, so we call the
StringBuilder’s ToString() method.
You might consider putting this method in its own class and compiling it for
reuse on other projects. Calling the code is straightforward, where in this case
we’re displaying the string in a Literal control:
MyLiteral.Text = CreatePagerLinks(objPds, “mypageofdata.aspx”);
Summary
The PagedDataSource gives you a ton of flexibility to page data in whatever
manner you wish and bind it to any control. Keep in mind that, like the DataGrid,
this doesn’t draw pages from the database. All of the rows returned by your
query are “out there,” but only the portion you want to display are sent to the
control you bind to. So if the query returns 100 rows, it returns 100 rows even
if you don't show them all.
©2010, POP World Media, LLC. All rights reserved
Legal, privacy, terms of service