I generally use a method such as below. Firstly within Routeconfig near the top I register a new Routebase (called LegacyURLRoute):
routes.Add(new LegacyUrlRoute());
A cut down version of this as follows:
public class LegacyUrlRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var request = httpContext.Request;
var response = httpContext.Response;
var legacyUrl = request.Url.ToString().ToLower();
var legacyPath = request.Path.ToString().ToLower();
Tuple<bool, bool, string> result = DervieNewURL(legacyPath);
bool urlMatch = result.Item1;
bool urlMatchGone = result.Item2;
var newUrl = result.Item3;
if (urlMatch)
{//For 301 Moved Permanently
response.Clear();
response.StatusCode = (int)System.Net.HttpStatusCode.MovedPermanently;
response.RedirectLocation = "http://www.example.com" + newUrl;
response.End();
}
else if (urlMatchGone)
{// 410 Gone
response.Clear();
response.StatusCode = (int)System.Net.HttpStatusCode.Gone;
response.End();
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
The DervieNewURL above can then include the lookup in the DB, but recently on a project I have had that as a HTMLHelper that also allows me to pass in the URL's that come directly from a DB column and these can then be parsed and updated as appropriate.
Simple example of DervieNewURL could be as follows, but you would obviously lookup in a table rather than having bits hard coded as below.
public static Tuple<bool, bool, string> DervieNewURL(string url)
{
/*
return type from this function is as follows:
Tuple<bool1, bool2, string1, string2>
bool1 = indicates if we have a replacement url mapped
bool2 = indicates if we have a url marked as gone (410)
string1 = indicates replacement url
*/
Tuple<bool, bool, string> result = new Tuple<bool, bool, string>(false, false, null);
if (!String.IsNullOrWhiteSpace(url))
{
string newUrl = null;
bool urlMatch = false;
bool urlMatchGone = false;
switch (url.ToLower())
{
case "/badfoldergone/default.aspx": { urlMatchGone = true; } break;
case "/oldfoldertoredirect/default.aspx": { urlMatch = true; newUrl = "/somecontroller/someaction"; } break;
default: { } break;
}
result = new Tuple<bool, bool, string>(urlMatch, urlMatchGone, newUrl);
}
return result;
}
If you require wildcard matches then I guess you could amend the above or build this into the DB call matching criteria.
By employing this route early on you can redirect legacy url's to other routes that will then be triggered as the request cascades down the routing table. Ultimately you will end up at your Default route similar to:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If developers have hard coded links then you will just need employ routes that will be triggered by these url's. By employing something such as above can capture most early on and add to the list in your DB with appropriate 301 Moved or 410 gone if required.