| Microsoft | Articles | Forums | FAQs |
| C# .NET |  |  |  |  |
| VB.NET |  |  |  |  |
| Visual Studio .NET |  |  |  |  |
| ADO.NET |  |  |  |  |
| Xml / Xslt |  |  |  |  |
| VB 6.0 |  |  |  |  |
| .NET CF |  |  |  |  |
| GDI+ |  |  |  |  |
| LINQ |  |  |  |  |
| Deployment |  |  |  |  |
| Security |  |  |  |  |
| FoxPro |  |  |  |  |
| Silverlight / WPF |  |  |  |  |
| Entity Framework |  |  |  |  |
| RIA Services |  |  |  |  |
|
| Web Programming | Articles | Forums | FAQs |
| JavaScript |  |  |  |  |
| ASP |  |  |  |  |
| ASP.NET |  |  |  |  |
| Web Services |  |  |  |  |
|
| Non-Microsoft | Articles | Forums | FAQs |
| NHibernate |  |  |  |  |
| Perl |  |  |  |  |
| PHP |  |  |  |  |
| Ruby |  |  |  |  |
| Java |  |  |  |  |
| Linux / Unix |  |  |  |  |
| Apple |  |  |  |  |
| Open Source |  |  |  |  |
|
| Databases | Articles | Forums | FAQs |
| SQL Server |  |  |  |  |
| Access |  |  |  |  |
| Oracle |  |  |  |  |
| MySQL |  |  |  |  |
| Other Databases |  |  |  |  |
|
| Office | Articles | Forums | FAQs |
| Excel |  |  |  |  |
| Word |  |  |  |  |
| Powerpoint |  |  |  |  |
| Outlook |  |  |  |  |
| Publisher |  |  |  |  |
| Money |  |  |  |  |
|
| Operating Systems | Articles | Forums | FAQs |
| Windows 7 |  |  |  |  |
| Windows Server |  |  |  |  |
| Windows Vista |  |  |  |  |
| Windows XP |  |  |  |  |
| Windows Update |  |  |  |  |
| MAC |  |  |  |  |
| Linux / UNIX |  |  |  |  |
|
| Server Platforms | Articles | Forums | FAQs |
 |  |  |  |  |
| BizTalk |  |  |  |  |
| Site Server |  |  |  |  |
| Exhange Server |  |  |  |  |
| IIS |  |  |  |  |
| Transaction Server | | |  | |
|
| Graphic Design | Articles | Forums | FAQs |
| Macromedia Flash |  |  |  |  |
| Adobe PhotoShop |  |  |  |  |
| Expression Blend |  |  |  |  |
| Expression Design |  |  |  |  |
| Expression Web |  |  |  |  |
|
| Other | Articles | Forums | FAQs |
| Subversion / CVS |  |  |  |  |
| Ask Dr. Dotnetsky |  |  |  |  |
| Active Directory |  |  |  |  |
| Networking |  |  |  |  |
| Uninstall Virus |  |  |  |  |
| Job Openings |  |  |  |  |
| Product Reviews |  |  |  |  |
| Search Engines |  |  |  |  |
| Resumes |  |  |  |  |
|
| |
|
|
|
SEO Friendly Paging with ASP.NET 2.0 Data Controls By Peter Bromberg Printer Friendly Version View My Articles 29 Views |  |
ASP.NET has lots of "out of the box" features that make the display of data easy, including pageable GridViews and DataGrids with little or no code. But the stock paging mechanism uses javascript to cause the postback, and the url of the new "page" doesn't change. This is not "SEO friendly", because the Googlebot and other search engine crawlers click on the links, and see the result as a page they've already requested, so it doesn't get indexed. |
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. |
 |
| Biography - Peter Bromberg |
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Please post questions at forums, not via email! |  |
|
|
 |
| Article Discussion: SEO Friendly Paging with ASP.NET 2.0 Data Controls |
| Peter Bromberg posted at Tuesday, August 21, 2007 6:45 PM |
 | Original Article |
 |
| |
|
| Multiple Grids on 1 page (SEO Friendly) |
| Yen Dutt Jain replied to Peter Bromberg at Tuesday, October 09, 2007 3:24 AM |
This article looks good. Can we have it tweaked to work for multiple grids by doing something like Grid1Page=1&Grid2Page=2 and on hyperlinks to persist both the Grid Page Indexes? |
 |
| |
|
| Tweaking it. |
| Peter Bromberg replied to Yen Dutt Jain at Saturday, March 22, 2008 9:18 AM |
 | I gave you the source code, its in the public domain. If you want to tweak it that would be a good programming exercise for you. |
 |
| |
|
| re:SEO Friendly Paging |
| Anthony pj replied to Peter Bromberg at Thursday, June 12, 2008 7:42 AM |
Hi Peter, thanks for posting this, i have had a link to this article for a while always knowing will have to refer to it some point. Havent used your code as is but striped out the gernerating html links section.
Great stuff thanks.
Anthony |
 |
| |
|
| Not working properly |
| Bryce Adams replied to Peter Bromberg at Wednesday, October 01, 2008 2:07 AM |
Nice article, just what I've been looking for. Having a problem with the example though. I download it and ran it as is. When you click page 1, page 2 is display and if you click pg 2, pg 3 is display. etc.. I'm new to .net and c#, so not to sure where the problem may be. Help please.
|
 |
| |
|
|
|