This article describes a way to use ASP.NET Routing to avoid 404 Not Found errors when changing folder structure or folder names in a web site.

What to do with obsolete links to your web site?

Having a web site means spending some time and efforts promoting the site on the Internet, making sure search engines indexed all the pages, trying to get exposure through blogs or discussion boards.

And then you get new idea and really need to restructure your site "change some folder names, move some pages, etc. What will happen with all those third-party links to your site you were so proud of? Do you want to lose them?

Route old URLs to new site structure

With arrival of .NET Framework 3.5 SP1 we got an elegant way of solving this problem ASP.NET Routing. Initially it was a part of ASP.NET MVC Preview 2, and now it is a part of the Framework.

The idea is to add special "Routs" to the site having single goal of processing requests to pages which are no longer present on the site. In its simplistic form the processing can happen in a single ASPX page responsible for proper handling of requests. Here is the example.

These parts you'll need in the project:

WebFormRouteHandler created by Chris Cavanagh representing IRouteHandler implementation, Global.asax file registering your Routs, web.config file where you register the WebFormRouteHandler , and Default.aspx page responsible for actual request processing.

Let's take a look at Global.asax.

    void Application_Start(object sender, EventArgs e)
    {
        // runs on application startup
        RegisterMyRoutes(System.Web.Routing.RouteTable.Routes);
    }
    private void RegisterMyRoutes(System.Web.Routing.RouteCollection routes)
    {

        // reference IRouteHandler implementation (example created by Chris Cavanagh)
        // see http://chriscavanagh.wordpress.com/2008/03/11/aspnet-routing-goodbye-url-rewriting/
        var startPageRouteHandler = new WebFormRouteHandler("~/default.aspx"); 

        // exclude .axd to handle web services and AJAX without checking all routs
        // see http://msdn.microsoft.com/en-us/library/system.web.routing.stoproutinghandler.aspx
        routes.Add(new System.Web.Routing.Route("{resource}.axd/{*pathInfo}", new System.Web.Routing.StopRoutingHandler()));
       routes.Add(new System.Web.Routing.Route("{service}.asmx/{*path}", new System.Web.Routing.StopRoutingHandler()));
        
         // mapping:
        // extracts folder name and page name as items in HttpContext.Items
        routes.Add(new System.Web.Routing.Route("{folderName}/", startPageRouteHandler));
        routes.Add(new System.Web.Routing.Route("{folderName}/{pageName}", startPageRouteHandler
);
    }

Here we defined single route handler - default.aspx, as well as routing rules.

Rule #1:

routes.Add(new System.Web.Routing.Route("{folderName}/", startPageRouteHandler));

states that all requests to a URL with structure "http://mysite.com/something" will be processed by the default.aspx page if there was no actual "something" found on the site. For example, there is a RealPage.aspx page present on the site, so requests to http://mysite.com/RealPage.aspx will be processed by that page.

But if client requests RealPage2.aspx, that request will be processed by the default.aspx page according to the rule #1. Note that client will not be redirected to default.aspx, it will be just web server running code in default.aspx in response to the request. For the client the response will come from the RealPage2.aspx.

You can add as many routes as you want, for example rule #2:

routes.Add(new System.Web.Routing.Route("{folderName}/{pageName}", startPageRouteHandler));

stating that all requests to a URL with structure "http://mysite.com/somefolder/somethingelse" will be processed by the default.aspx page if there was no actual "somefolder/somethingelse" found on the site.

The code behind default.aspx shows how to extract those parts of the request. As you can see they will be placed in the HttpContext.Items collection.

        lblFolder.Text = Context.Items["folderName"] as string;
        lblPage.Text = Context.Items["pageName"] as string;

How it works in real life

Here is a real life web site actually using this technique - Digitsy Global Store. Besides handling obsolete URLs the ASP.NET Routing is being used to handle multiple languages on the site, switching CultureInfo on the fly:


   protected void Page_PreInit(object sender, EventArgs e)
    {
        CultureInfo lang = new CultureInfo(getCurrentLanguage());
        Thread.CurrentThread.CurrentCulture = lang;
        Thread.CurrentThread.CurrentUICulture = lang;
    }
    private static string getCurrentLanguage()
    {
        string lang = HttpContext.Current.Items["language"] as string;
        switch (lang)
        {
            case "france":
                return "fr-FR";
            case "canada":
                return "en-CA";
            case "germany":
                return "de-DE";
            case "japan":
                return "ja-JP";
            case "uk":
                return "en-GB";
            case "russia":
                return "ru-RU";
            default:
                return "en-US";
        }
    }

As you see, the default language is English, United States: "en-US". In internal links the site uses structure http://{sitename}/{language}/¦other things¦

So if you try http://digitsy.com/us/ you'll get US version, trying http://digitsy.com/japan/ will bring you Japanese one, and if you try http://digitsy.com/whatever " you'll not get 404 error, you'll get US version again.

ASP.NET Routing made restructuring of the site really easy. The folder structure "{language}/{index}/category/{categoryID}" was recently replaced by "{language}/{index}/shopping/{categoryID}". There supposed to be no "category" folder in the site structure anymore. But because of both routs pointing to the same handling page both folders "category" and "shopping" return valid responses.

Trying
http://digitsy.com/us/Electronics/shopping/541966  will use the rule:

routes.Add(new System.Web.Routing.Route("{language}/{index}/shopping/{categoryID}", categoryRouteHandler));

while trying
http://digitsy.com/us/Electronics/category/541966  will use:

routes.Add(new System.Web.Routing.Route("{language}/{index}/category/{categoryID}", categoryRouteHandler));

and both will resolve to the same route handling page.


Where to go for ASP.NET MVC Hosting?

You can host your website on the ASP.NET MVC Framework at ASPHostCentral.com and you can do it by just paying $4.99/month