Managing 301 Moved Permanently Redirects in ASP.NET

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<string , string> Redirects
		{
			get
			{
				if (HttpRuntime.Cache[redirectCacheKey] == null)
				{
					lock (syncronizationLock)
					{
 
						context = new Models.RedirectsEntities();
						Dictionary<string , string> redirects = context.RedirectUrls.Where(x =&gt; x.Active).
																AsEnumerable().ToDictionary(x =&gt; x.OldUrl.ToLower(), x =&gt; x.NewUrl.ToLower());
 
						HttpRuntime.Cache.Add(redirectCacheKey,
												redirects,
												null,
												DateTime.Now.AddDays(1),
												Cache.NoSlidingExpiration,
												CacheItemPriority.Default,
												null);
					}
				}
 
				return (Dictionary<string , string>)HttpRuntime.Cache[&quot;redirectUrls&quot;];
			}
		}
 
 
 
		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(&quot;Location&quot;, 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.

<httpModules>
	<add name="RedirectModule" type="RedirectFromDatabase.RedirectModule"/>
</httpModules>

The source code can be found here.

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Reddit
  • DotNetKicks
  • Technorati
  • TwitThis

Leave a Reply