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
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 => 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<string , string>)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.
<httpModules> <add name="RedirectModule" type="RedirectFromDatabase.RedirectModule"/> </httpModules>
The source code can be found here.





