Update: I have discovered that the way this was originally written by Matt Hawley will cause asp.net MVC to incorrectly generate route urls. This is fixed by overriding GetVirtualPath. Code has been updated below.
I'm currently in the process of transforming the PalaceVirtualTours website from asp.net web forms to asp.net MVC 3.
However there are a number of pages that already have URLs that are linked to by other sites or have reasonable search engine rankings, so if someone tries to go to that page it needs to be redirected to the the new MVC URL.
Matt Hawley wrote an article about this sometime ago: http://www.eworldui.net/blog/post/2008/04/ASPNET-MVC---Legacy-Url-Routing.aspx
Unfortunately the code that he provides doesn't work, MVC reports that a controller is required, as described in this stack overflow question: http://stackoverflow.com/questions/2528078/legacy-url-rewriting-with-query-string-parameters
The solution to this is to use an IHttpHandler directly, rather than an MVCHandler.
Here is the full updated, working code:
// The legacy route class that exposes a RedirectActionName
public class LegacyRoute : Route
{
public LegacyRoute(string url, string redirectRuleName):base(url, new LegacyRouteHandler())
{
RedirectRuleName = redirectRuleName;
}
public string RedirectRuleName { get; set; }
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
// The legacy route handler, used for getting the HttpHandler for the request
public class LegacyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new LegacyHandler();
}
}
// The legacy HttpHandler that handles the request
public class LegacyHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var requestContext = context.Request.RequestContext;
string redirectActionName = ((LegacyRoute)requestContext.RouteData.Route).RedirectRuleName;
var queryString = requestContext.HttpContext.Request.QueryString;
foreach (var key in queryString.AllKeys)
{
requestContext.RouteData.Values.Add(key, queryString[key]);
}
VirtualPathData data = RouteTable.Routes.GetVirtualPath(requestContext, redirectActionName, requestContext.RouteData.Values);
context.Response.Status = "301 Moved Permanently";
context.Response.AppendHeader("Location", data.VirtualPath);
}
public bool IsReusable
{
get { return false; }
}
}
And here is how to use it:
routes.MapRoute(
"BPTour", // Route name
"buckingham-palace-virtual-tour", // URL with parameters
new { controller = "Tours", action = "BuckinghamPalace", id = UrlParameter.Optional } // Parameter defaults
);
// redirect /bptour to the BPTour route.
routes.Add("", new LegacyRoute("bptour", "BPTour"));