I came across a problem recently rewriting a website in ASP.NET MVC. The old site was written in PHP and the URL’s it produced are not going to match the structure I want to use in the new site. I spend a lot of time trying to optimize this site for Google and Bing indexing and didn’t want to have any broken links when I switch over.
I also want Google and Bing to update their search results to the new links as soon as possible, not keep them around forever. Basically, what I am looking for is an easy way to manage a couple hundred 301 redirects until the old URL’s fall out of use, and my hosting provider doesn’t provide access to the IIS7 UrlRewrite Module.
Step 1 – Create a Database Table
I want it to be easy to add and remove URL’s at will. I can export a complete list of indexed URL’s from Google Webmaster Tools to populate the table initially, and then add or remove URL’s later if I want to move content around on the new site.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[RedirectUrls](
[OldUrl] [nvarchar](255) NOT NULL,
[NewUrl] [nvarchar](255) NOT NULL,
[Active] [bit] NOT NULL
CONSTRAINT [PK_RedirectUrls] PRIMARY KEY CLUSTERED
(
[OldUrl] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Step 2 – Create a Entity Data Model
It’s only a single table, but it looks something like this.
Step 3 – Create an HttpModule
I created an HttpModule called RedirectModule.cs. I populated a Dictionary from my database table to make the lookups fast, then I wired up a Begin_Request eventhandler so that I can grab the URL of each incoming request and redirect if I find a match in the dictionary.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using RedirectFromDatabase.Models;
using System.Web.Caching;
namespace RedirectFromDatabase
{
public class RedirectModule : IHttpModule
{
private static object syncronizationLock = new object();
private RedirectsEntities context;
private const string redirectCacheKey = "redirectUrls";
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(Application_BeginRequest);
}
public Dictionary Redirects
{
get
{
if (HttpRuntime.Cache[redirectCacheKey] == null)
{
lock (syncronizationLock)
{
context = new Models.RedirectsEntities();
Dictionary redirects = context.RedirectUrls.Where(x => x.Active).
AsEnumerable().ToDictionary(x => x.OldUrl.ToLower(), x => x.NewUrl.ToLower());
HttpRuntime.Cache.Add(redirectCacheKey,
redirects,
null,
DateTime.Now.AddDays(1),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null);
}
}
return (Dictionary)HttpRuntime.Cache["redirectUrls"];
}
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
string relativeUrl = HttpContext.Current.Request.Url.PathAndQuery.ToLower();
if (Redirects.ContainsKey(relativeUrl))
{
string newUrl = Redirects[relativeUrl];
HttpApplication application = sender as HttpApplication;
HttpContext context = application.Context;
application.CompleteRequest();
context.Response.StatusCode = 301;
context.Response.AddHeader("Location", newUrl);
}
}
public void Dispose()
{
//Nothing to Dispose of
}
}
}
I’ve created a property to encapsulate loading and caching of the Dictionary from the database. My URL’s aren’t very volitile, so I can set caching to expire after 1 day.
Note: HttpModules are not very testable, so a better solution would be to refactor this into a seperate class that I can test more easily and call into that class from the module, but I wanted to keep this example simple.
Step 4 – Add RedirectModule to the web.config
In the system.web section of the web.config, register the HttpModule.
The source code can be found here.
Recent Tweets
Twitter
Plugin created by StressFree Sites