<uber:ASP.Net> - Using a PagedDataSource and your own data pager links

posted by Jeff
Wednesday, January 28, 2004 6:12:49 PM

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.

©2005, POP World Media, LLC. All Rights Reserved
www.uberasp.net