Introduction
You are putting a new site together and you have lots of new pages and lots of old pages that will no longer exist, it might be a good idea to tell links from external sources and search engines, robots, etc... that the pages have moved. Supplying incoming requests with some notification of page moves or page replacements is handled with "301 Moved Permanently".
There are a few instances where this is a good idea:
- Complete redesign of a site
- Single Page move
- Move from static site htm to aspx
- You don't want to lose search rankings
- You can't define all your external links
Background : What a "301 Moved Permanently" is
w3.org states: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
10.3.2 301 Moved Permanently
The requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs. Clients with link editing capabilities ought to automatically re-link references to the Request-URI to one or more of the new references returned by the server, where possible. This response is cacheable unless indicated otherwise.
The new permanent URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).
If the 301 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.
Basic Redirects
If an old page still exists, then you can add code to the Page_Load
event which will look like the code below:
protected void Page_Load(object sender, EventArgs e)
{
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", "NewPage_Redirect1.aspx");
}
The above code has advantages and disadvantages:
Advantages
Disadvantages
- You need to maintain that page from now on in your application
- Does not work with downloadable content such as picture, PDFs, media, etc..
- You still have to contain old folder structure in your new site.
- Does not work with directories/folders redirects.
- Does not work if moving site from Java, Apache to IIS
Expanding from Basic Redirects
So we need something that will give us a clean site and be able to handle a mix of incoming requests and move them to the right page or folder. Also dealing with non web application files such as pdf and Word documents would be good as well.
The best way to achieve this is to use a custom HTTP Module which will deal with requests and point the client browser to the correct page.
Creating a Custom HHTP Module for Redirect
First, you needed to create a class that inherits from the interface IHttpModule
and implement two members as shown below.
Note: We will fill in the members later on
public class HttpRedirect : IHttpModule
{
public void Init(HttpApplication context)
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
}
Create Entry in Web.Config for the Redirect Class
Below is an entry for the Redirect
classes called "HttpRedirect
" which will fire on an incoming request.
<modules>
<add name="Redirects" type="Redirects301.HttpRedirect,Redirects301"/>
<add name="ScriptModule" preCondition="managedHandler"
type="System.Web.Handlers.ScriptModule, System.Web.Extensions,
Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</modules>
Redirect List
I had about 100 or so redirects and some might change or others might be added later so I added them in an XML file that I could load and changes if required without rebuilding the application.
So there is an XML file called Redirect301.xml which holds the redirects.
="1.0" ="utf-8"
<Redirects>
<Redirect>
<Description>Change of Index</Description>
<OldPage>/index.htm</OldPage>
<NewPage>/index.aspx</NewPage>
<File>FALSE</File>
</Redirect>
<Redirect>
<Description>Directory </Description>
<OldPage>/Profile</OldPage>
<NewPage>/users</NewPage>
<File>FALSE</File>
</Redirect>
<Redirect>
<Description>PDF Redirect</Description>
<OldPage>/lib/docs/TermsOfBusiness.pdf</OldPage>
<NewPage>/docs/TOB.pdf</NewPage>
<File>TRUE</File>
</Redirect>
</Redirects>
The contents of this have the following function:
- Description: Does nothing in Code, purely for describing the Redirect
- OldPage: What the old page was, this will be the incoming request that you will look out for
- NewPage: Target Location for the OldPage request.
- File: The NewPage is a file download rather than a support format in IIS; I used this for PDFs
Adding Location of this to Web.Config
Simple Application Key as shown below, this is used by the HttpRedirect
class to find the redirect XML.
<appSettings>
<add key="RedirectXMLDoc" value="/Redirects301.xml"/>
</appSettings>
Redirect Class
Used to store the redirects in a usable format for the redirect. This roughly matches the contents of the Redirect301.xml file.
namespace Redirects301
{
public class Redirect
{
public string OldPage { get; set; }
public string NewPage { get; set; }
public bool TransferFileType { get; set; }
}
}
Business End: HttpRedirect Class
This is the full list of the HttpRedirect
class.
using System;
using System.Collections.Generic;
using System.Web;
using System.Linq;
using System.Web.Configuration;
using System.Xml.Linq;
namespace letsure
{
public class HttpRedirect : IHttpModule
{
public void Init(HttpApplication ap)
{
ap.BeginRequest += new EventHandler(ap_BeginRequest);
}
static string Xmldoc
{
get
{
string filename = WebConfigurationManager.AppSettings["RedirectXMLDoc"];
return HttpContext.Current.Server.MapPath(filename);
}
}
static void ap_BeginRequest(object sender, EventArgs e)
{
HttpApplication hxa = (HttpApplication) sender;
string incomingText = hxa.Context.Request.Url.AbsolutePath.Trim();
if (hxa.Request.Url.LocalPath.Contains("404.aspx"))
{
incomingText = hxa.Request.Url.Query.ToLower();
incomingText = incomingText.Replace("?aspxerrorpath=", string.Empty);
incomingText = incomingText.Replace("?404;", string.Empty);
string schemeHost = hxa.Request.Url.Scheme + "://"
+ hxa.Request.Url.Host;
incomingText = incomingText.Replace(schemeHost, string.Empty);
string portvalue = ":" + hxa.Request.Url.Port;
incomingText = incomingText.Replace( portvalue, "");
}
try
{
var results = from r in Redirects()
where incomingText.ToLower() == r.OldPage.ToLower()
select r;
if (results.Count() > 0)
{
Redirect r = results.First();
hxa.Response.Status = "301 Moved Permanently";
if (r.TransferFileType)
{
hxa.Response.AddHeader("content-disposition",
"attachment; filename=filename");
}
hxa.Response.AddHeader("Location", r.NewPage);
}
}
catch (Exception)
{
throw;
}
}
public void Dispose() { }
private static IEnumerable<Redirect> Redirects()
{
var results = new List<Redirect>();
XDocument xdocument = GetXmLdocument();
var redirects = from redirect in xdocument.Descendants("Redirect")
select new
{
oldpage = redirect.Element("OldPage").Value,
newpage = redirect.Element("NewPage").Value,
file = redirect.Element("File").Value
};
foreach (var r in redirects)
{
var redirect = new Redirect
{
NewPage = r.newpage,
OldPage = r.oldpage,
TransferFileType = Convert.ToBoolean(r.file)
};
results.Add(redirect);
}
return results;
}
private static XDocument GetXmLdocument()
{
XDocument xmldocument;
if (HttpRuntime.Cache["RedirectXMLDoc"] == null)
{
xmldocument = XDocument.Load(Xmldoc);
HttpRuntime.Cache["RedirectXMLDoc"] = xmldocument;
}
else
{
xmldocument = (XDocument) HttpRuntime.Cache["RedirectXMLDoc"];
}
return xmldocument;
}
}
}
<appSettings> </appSettings>
Points of Interest
Redirecting to a file supplied from the code below as it added a head to tell the client browser to expect a file. I could have tested the extension of the incoming request and decided if it was file or note, but adding something into the HTML is simpler and gives a little more control.
if (r.TransferFileType)
{
hxa.Response.AddHeader("content-disposition", "attachment; filename=filename");
}
Issues
Some of you may have noticed a possible performance issue?
The issue is that for every request the Redirect
XML document will have to be opened, read and searched. This is not ideal by any means and this should be cached as the application level and due to popular demand I have added this in.
Folder Redirection
This code does not redirect requested for folders; that is if you have a link for /old/site.aspx and you want to go to /new/site.aspx. Placing a rule oldfile=/old newfile=/new will not redirect to page correctly. But if your incoming requests is /old, it will redirect to /new on a folder level as it will pickup your default page for that folder.
Testing
Test, Test again and then get someone else to Test again. Don't assume my code or anyone else's works the way it is supposed to, this worked for me but you might have problems.
Use
I used this code on the following without any issues with:
- VS 2008
- .NET FrameWork 3.5
- IIS 6-7
Downloads
I don't think you need any, unless you can't copy or paste.
History
- Version 1.000.001 17-Sept-2010
- Version 1.000.002 04-Mar-2011
- Cache added
- Fixes for IIS 5/6/7