Making Your Website a Bit More SEO Friendly
Posted on 7/10/2007
I won't pretend to know everything there is to know about Search Engine Optimization or SEO. Nowadays, just keeping up with google's changes to their search engine technology is a full-time job. Among the low-hanging SEO fruit is one simple idea: if your page names are descriptive, most search engine bots will index them. This article explains how to implement the IHttpModule interface to create virtual, SEO-friendly web pages on your ASP.NET website. The module intercepts the calls to those virtual web pages and returns the database-driven content instead.
Let's make a few assumptions. One, all of our database-driven content is available from an existing ASPX page called ArticleDetail which takes a parameter named ID. The ArticleDetail.aspx page opens a database connection and renders the content based on the numeric ID. This works great except that google isn't going to learn much from a URL like:
http://www.mysite.net/ArticleDetail.aspx?id=1000
What if, instead, the URL looked like this to the user?
http://www.mysite.net/Company-Buyout-Press-Release.ashx
All that's needed to pull this off, technically, is to make a connection between the article with the ID of 1000 and the virtual page named Company-Buyout-Press-Release.ashx. Using ASP.NET, this can be done with an implementation of an IHttpModule interface. These modules let you intercept many parts of the web application lifecycle. In your implementation, all that's needed is to subscribe to the BeginRequest event. During the event, you'll be able to read the Requested URL, look up the page name in a database and rewrite the URL to use the ArticleDetail.aspx page with the article's numeric ID instead. Here's some code that demonstrates the technique:
using System;
using System.Data;
using System.Data.OleDb;
using System.Web;
/// <summary>
/// This IHttpModule implementation searches for the referenced
/// page name in a database and rewrites the requested URL to
/// return related content from that database.
/// </summary>
public class VirtualPageModule : IHttpModule
{
public void Dispose() {}
/// <summary>
/// The Init method gets called by ASP.NET. This is your chance
/// to register for events in the HttpApplication parameter.
/// </summary>
/// <param name="context">The web application context.</param>
public void Init( HttpApplication context )
{
// register for the BeginRequest event
context.BeginRequest += Application_BeginRequest;
}
/// <summary>
/// Handle the start of a web request. The sender parameter is
/// actually an HttpApplication object which contains the
/// HttpContext holding the Request object. That's where we'll
/// find the Url that was requested. The HttpContext also has
/// a RewriteUrl method that we'll use to make the web server
/// load a different resource instead of the one requested.
/// This is not a redirect. It's more like a Transfer() request
/// except that the requested resource never gets a chance to
/// run. The client (browser) will never know that an alternate
/// resource has been rendered.
/// </summary>
/// <param name="sender">
/// Cast this to an HttpApplication to get at the Request
/// object, the Url and the HttpContext.
/// </param>
/// <param name="e">The event arguments.</param>
public void Application_BeginRequest( object sender, EventArgs e )
{
// get a reference to the HttpContext and the requested Url
HttpContext context = ((HttpApplication)sender).Context;
Uri url = context.Request.Url;
OleDbConnection conn = null;
try
{
// get the base name of the requested page
string URL_BASE = url.Segments[url.Segments.Length - 1];
if (URL_BASE.EndsWith( ".ashx" ))
{
// strip off the ASHX extension
URL_BASE = URL_BASE.Substring( 0, URL_BASE.Length - 5 );
// find an article for which the URL_BASE matches
string connectionString = "<<TODO: set this>>";
// the query should use the URL_BASE to find the
// numeric ID of the article that matches the name
// found in the requested URL - a scalar result is
// expected
string queryString = "<<TODO: set this>>";
conn = new OleDbConnection( connectionString );
OleDbCommand cmd = new OleDbCommand( queryString, conn );
// add the URL_BASE parameter - the query should
// reference this value in its WHERE clause
cmd.Parameters.Add( "@url_base",
OleDbType.VarChar ).Value = URL_BASE;
conn.Open();
object result = cmd.ExecuteScalar();
if ((object)result != null && !(result is DBNull))
{
// rewrite the URL with one that will render
// the database content instead
context.RewritePath( String.Format(
"ArticleDetail.aspx?id={0}", result ) );
}
}
}
catch (Exception ex)
{
// TODO: log the exception here
}
finally
{
// clean up
bool open = ((object)conn != null) &&
(conn.State != ConnectionState.Closed);
if (open)
conn.Close();
}
}
}
Of course, you'll need to inject your own connection string and query string to make this work for you, but that's about it. The last thing you'll need to do is register your HttpModule in your web.config file using something like this:
<configuration>
<system.web>
<httpModules>
<add name="VirtualPageModule" type="VirtualPageModule"/>
</httpModules>
</system.web>
</configuration>
OK, one last interesting note. Why did I choose to use the ASHX extensions on my virtual page names and not ASPX, HTML or something else? Because of all the extensions registered with Internet Information Server (IIS), the ASHX extension is one of the few that has that little checkbox entitled "Check that file exists" in the extension mapping properties unchecked. This means that IIS will not look for the physical file with a name that matches the requested resource. This isn't a big deal if you control the web servers because you can turn off the check box for any file type you like. However, if you don't control the web servers you are using, then you need to use an extension that's already exempt from the phyical file existence check. So the ASHX extension comes in handy yet again.
Comments
- Billy the Kid said on 7/16/2007 - Thanks for the info Kevin. I implemented your code and it worked perfectly.