As developers we are all intimately aware of the implications of changing the URL of existing content in our applications which have already been indexed by search engines and users. It is never enough to simply change a URL and update the links because old pages, bookmarks, or search results may still point to the original URL. That being so, it is imperative to add an appropriate HTTP 301 redirect for the original URL when modifying the location of existing content.
In Apache-based web servers adding a 301 Redirect is as simple as adding a .htaccess file with the appropriate mod_rewrite instructions; unfortunately, this simplicity of configuration is unavailable on an IIS server and when an application requires a redirect be added we are forced to either edit the IIS settings explicitly or add the redirect to the application’s code. Ultimately, the former solution is ideal because it allows IIS to bypass the ASP.NET request engine and immediately serve the redirect, but for many of us, editing the IIS configuration is not an option due to the use of shared hosting environments. In ASP.NET code, issuing a 301 Redirect in your Global.asax looks something like this:
protected void Application_BeginRequest(object sender, EventArgs e) {
var oldUrl = new Uri("http://www.website.com/path/to/content");
var newUrl = new Uri("http://www.website.com/new/path/to/content");
if(string.Equals(Request.Url.AbsolutePath, oldUrl.AbsolutePath, StringComparison.OrdinalIgnoreCase)) {
Response.StatusCode = 301;
Response.Status = "301 Moved Permanently";
Response.RedirectLocation = newUrl;
Response.End();
}
}
While searching for a more maintainable solution I came across a blog post by Scott Hanselman which directed me to a HttpModule written by Fritz Onion. The module developed is elegant and effective because it uses a special section in the website's configuration file to define each redirect, thus minimizing maintenance challenges associated with adding and removing individual redirects. That being said, Fritz's solution is dated and uses some obsolete code so I have gone ahead and modernized it a bit. In addition to the original feature set, I have added support for matching the entire request URL.
Issuing redirects with the module is as easy as registering and adding the <redirections /> configuration section and then declaring each redirect rule.
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="redirections" type="RedirectModule.Configuration.RedirectionsSection, RedirectModule"/>
</configSections>
<redirections>
<add targetUrl="^http://website.com" destinationUrl="http://www.website.com" ignoreCase="true" />
<add targetUrl="^~/FalseTarget.aspx" destinationUrl="~/RealTarget.aspx" ignoreCase="true"/>
<add targetUrl="^~/2ndFalseTarget.aspx" destinationUrl="~/RealTarget.aspx" permanent="true"/>
<add targetUrl="^~/(Author|Category|Tool)([A-Za-z0\d]{8}-?[A-Za-z\d]{4}-?[A-Za-z\d]{4}-?[A-Za-z\d]{4}-?[A-Za-z\d]{12}).aspx$" destinationUrl="~/Pages/$1.aspx?$1=$2"/>
<add targetUrl="^~/SomeDir/(.*).aspx\??(.*)" destinationUrl="~/Pages/$1/Default.aspx?$2"/>
</redirections>
<system.web>
<httpModules>
<add name="RedirectModule" type="RedirectModule.RedirectModule"/>
</httpModules>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="RedirectModule" type="RedirectModule.RedirectModule, RedirectModule"/>
</modules>
</system.webServer>
</configuration>
Sources
Update: 03/26/12
I've made a few minor tweaks in the module's code. The library now targets .NET 2.0 for maximum compatibility and the sample project works much better out of the box.