From an SEO (Search Engine Optimization) standpoint, having your pageable display passed up by search bots is not good. Here is a way to handle paging with Data Controls such as the DataList - which doesn't offer native paging -- and ensure that each new page request is a unique url that the search engines will slurp up and index.
For our sample data, we will cheat a little and use a custom RSS search to google blog search, returning the results as RSS which can be loaded directly into a DataSet via the ReadXml method. We will also cache the DataSource in Session so that we do not have to make a separate webrequest each time we request the next page of results. We will use the PagedDataSource class to handle the paging for the DataList. PagedDataSource encapsulates the paging-related properties of a data-bound control (such as DataGrid, GridView, DetailsView, FormView, Repeater or DataList) that allow it to perform paging. By using the PagedDataSource as the datasource for one of these controls, you can easily enable paging features even if the control does not support paging on its own.
PagedDataSource uses the best available method to enumerate over the data belonging to the current page. If the underlying data source supports indexed access (such as System.Array and System.Collections.IList), this class uses it. Otherwise, it uses the enumerator created by the GetEnumerator method. You can use the PagedDataSource with a DataTable (which does not support IEnumerable or IList) by simply binding to its DefaultView (DataView) -- which does support this.
PagedDataSource offers all the members that you would expect to perform paging - including CustomPaging:
- AllowCustomPaging Gets or sets a value indicating whether custom paging is enabled in a data-bound control.
- AllowPaging Gets or sets a value indicating whether paging is enabled in a data-bound control.
- AllowServerPaging Gets or sets a value indicating whether server-side paging is enabled.
- Count Gets the number of items to be used from the data source.
- CurrentPageIndex Gets or sets the index of the current page.
- DataSource Gets or sets the data source.
- DataSourceCount Gets the number of items in the data source.
- FirstIndexInPage Gets the index of the first record displayed on the page.
- IsCustomPagingEnabled Gets a value indicating whether custom paging is enabled.
- IsFirstPage Gets a value indicating whether the current page is the first page.
- IsLastPage Gets a value indicating whether the current page is the last page.
- IsPagingEnabled Gets a value indicating whether paging is enabled.
- IsReadOnly Gets a value indicating whether the data source is read-only.
- IsServerPagingEnabled Gets a value indicating whether server-side paging support is enabled.
- IsSynchronized Gets a value indicating whether access to the data source is synchronized (thread-safe).
- PageCount Gets the total number of pages necessary to display all items in the data source.
- PageSize Gets or sets the number of items to display on a single page.
- SyncRoot Gets the object that can be used to synchronize access to the collection.
- VirtualCount Gets or sets the virtual number of items in the data source when custom paging is used.
A page with a DataList needs no special items on it except for 2 LinkButtons that provide the < and > functionality to move forward or backward one "page". What's important is the codebehind, which implements the PagedDataSource:
using System;
using System.Data;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SEOFriendlyPaging
{
public partial class _Default : Page
{
protected PagedDataSource pagedData = null;
protected string srchTerm = "ASP.NET"; // use a default search term
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// if initial page load---
pagedData = new PagedDataSource();
}
else
{
pagedData = (PagedDataSource) Session[srchTerm];
}
BindPaged();
}
public void Prev_Click(object obj, EventArgs e)
{
int newPageIndex = ((int) (pagedData.CurrentPageIndex - 1));
pagedData.CurrentPageIndex = newPageIndex;
Response.Redirect(Request.CurrentExecutionFilePath + "?Page=" +
newPageIndex.ToString());
}
public void Next_Click(object obj, EventArgs e)
{
int newPageIndex = ((int) (pagedData.CurrentPageIndex + 1));
pagedData.CurrentPageIndex = newPageIndex;
Response.Redirect(Request.CurrentExecutionFilePath + "?Page=" +
newPageIndex.ToString());
}
private void BindPaged()
{
if (txtSearch.Text != "")
srchTerm = txtSearch.Text;
DataView dv = null;
if (Session[srchTerm] == null)
{
string baseUrl = "http://blogsearch.google.com/blogsearch_feeds?hl=en&q=";
string baseUrlEnd = "&num=100&output=rss";
if (srchTerm == "") srchTerm = "ASP.NET";
string fullSearchUrl = baseUrl + srchTerm + baseUrlEnd;
DataSet ds = new DataSet();
ds.ReadXml(fullSearchUrl);
dv = ds.Tables[2].Copy().DefaultView;
Session[srchTerm] = pagedData;
}
else
{
pagedData = (PagedDataSource) Session[srchTerm];
dv = (DataView) pagedData.DataSource;
}
pagedData.DataSource = dv;
pagedData.AllowPaging = true;
pagedData.PageSize = 5;
if (Request.QueryString["Page"] == null)
pagedData.CurrentPageIndex = 0;
else
{
int pg = int.Parse(Request.QueryString["Page"]);
if (pg < 0) pg = 0;
pagedData.CurrentPageIndex = pg;
}
btnPrev.Visible = (!pagedData.IsFirstPage);
btnNext.Visible = (!pagedData.IsLastPage);
dlSEOFriendly.DataSource = pagedData;
dlSEOFriendly.DataBind();
}
protected void btnSearch_Click(object sender, EventArgs e)
{
BindPaged();
}
}
}You can also use a static class that will generate a set of numbered links as a string of HTML, which you can then assign to a Literal or similar control. This is what I use in the sample you can download at the bottom of this article:
using System;
using System.Text;
using System.Web.UI.WebControls;
namespace SEOFriendlyPaging
{
public static class Pager
{
public static string CreatePagerLinks(PagedDataSource pgdDataSource, string BaseUrl)
{
StringBuilder sbPager = new StringBuilder();
//sbPager.Append("More: ");
if (!pgdDataSource.IsFirstPage)
{
// first page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("\">|<</a> ");
if (pgdDataSource.CurrentPageIndex != 1)
{
// previous page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&page=");
sbPager.Append(pgdDataSource.CurrentPageIndex.ToString());
sbPager.Append("\" alt=\"Previous Page\"><<</a> ");
}
}
// calc low and high limits for numeric links
int intLow = pgdDataSource.CurrentPageIndex - 1;
int intHigh = pgdDataSource.CurrentPageIndex + 3;
if (intLow < 1) intLow = 1;
if (intHigh > pgdDataSource.PageCount) intHigh = pgdDataSource.PageCount;
if (intHigh - intLow < 5) while ((intHigh < intLow + 4) && intHigh < pgdDataSource.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 == pgdDataSource.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 (!pgdDataSource.IsLastPage)
{
if ((pgdDataSource.CurrentPageIndex + 2) != pgdDataSource.PageCount)
{
// next page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&Page=");
sbPager.Append(Convert.ToString(pgdDataSource.CurrentPageIndex + 2));
sbPager.Append("\">>></a> ");
}
// last page link
sbPager.Append("<a href=\"");
sbPager.Append(BaseUrl);
sbPager.Append("&Page=");
sbPager.Append((pgdDataSource.PageCount - 1).ToString());
sbPager.Append("\">>|</a>");
}
// convert the final links to a string and return for assignment
return sbPager.ToString();
}
}
}The downloadable Zip solution includes a version using the above numbered pager class, and I have even included the paging into the Header and Footer of the DataList. This is far from a complete "production ready" implementation -- I threw it together quickly -- but it illustrates the basic technique of how to use the PagedDataSource to "take over" the function of paging for a control that does not natively support it.
Most importantly, each request for a "next" or "previous" page represents a unique url and querystring. And that means that when the bots click, they'll index it, unlike as with the built-in paging you have with ASP.NET data controls, which aren't "SEO - Friendly". According to the latest information from Matt Cutts, Google's "SEO" guru, Google does not penalize urls that have querystring parameters - as long as there aren't more than two or three - contrary to all the "Hype Science" of the Url Rewriting crowd.
Remember: When you are developing a web site with a view toward how to make it Search Engine Friendly, the ONLY THING the search engine bots know how to do -- is to follow links. They cannot deal with GridView Paging Javascript. They cannot deal with Flash. They ONLY know how to follow links and look at the anchor text before they go to the link, make sure it is "new" (not already indexed) and "slurp" it.
You can download the Visual Studio 2005 Solution and experiment with the technique. No database setup is needed, since we are getting the datasource as a DataSet result from a Google Blog search -- so there is no setup to worry about. |