Back to Blog

How to create multiple 301 redirect urls in ASP.NET MVC

March 29, 2012

By Matt Mombrea
4 Comments

While ASP.NET MVC has a powerful routing engine for handling requests, there can be a scenario when you need to process a list of specific URL rewrites in your application. Often this can be due to a new version of an existing site going live where the URL structure has changed. Under Apache, this can be handled easily in the .htaccess file of the site by listing out each path  you’d like to rewrite like so:

<IfModule mod_rewrite.c>
  RewriteEngine on
  Redirect 301 /somepage.html /otherpage.html
</IfModule>

It can also be handled in IIS 7 using the URL Rewrite module or under IIS 6 using a 3rd party extension such as Helicon ISAPI Rewrite.

But what if you want to handle the redirects via code?

Bulk 301 Redirect – Considerations

Our two main considerations when coming up with this solution were:

  1. We don’t want to check for a redirect on every request (just old broken links)*
  2. We don’t want to have to do a code push to change the 301 mapping list

*: You can use the same solution to process every request by changing the location of the 301 check

This led us down a path where we are capturing 404 requests in the application, then checking for a rewrite rule before actually displaying a 404 page. If the rule exists, we rewrite the path instead of showing the 404.

The 301 List

With consideration #2 in mind, we decided to use a flat file in the App_Data folder to hold our rewrite rules. The file is simple since all we are concerned with is permanent redirects. We use a plain old CSV file with 2 columns, the first column being the old path to check for, the second column is the new path to permanently redirect to. For example:

/Home/FollowUs,/About/Follow-Us
/Home/ContactUs,/About/contact-us

This makes constructing, updating, and maintaining the rewrite list extremely easy. Even better, since it’s a CSV file, you can use excel to have a non technical person create the list for you if it’s lengthy.

To process the list, we created a single static method to load the list into a key,value Dictionary object. Here is the entire class:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;

namespace CypressNorth.Data.Services
{
    public class FlatFileAccess
    {
        public static Dictionary<string,string> Read301CSV()
        {
            string file = "App_Data/301.csv";
            string path = System.IO.Path.Combine(AppDomain.CurrentDomain.GetData("DataDirectory").ToString(), file);

            if (File.Exists(path))
            {
                using (TextReader sr = new StreamReader(path))
                {
                    Dictionary<string,string> redirect_dict = new Dictionary<string,string>();
                    string line = "";
                    while ((line = sr.ReadLine()) != null)
                    {
                        string[] columns = line.Split(',');
                        redirect_dict.Add(columns[0], columns[1]);
                    }
                    return redirect_dict;
                }
            }
            else
                return null;

        }

    }
}

This will process your list of rewrite rules into a Dictionary<string,string> list and return it for you.

Intercepting 404 responses

Now that we have our list of 301 redirects and a way to access them, we need to hook into the app request life cycle to process them. Our consideration #1 states that we don’t want to process every single request to the site, just bad URL’s so the logical place for this is when a 404 response is encountered.

To hook into this we create a new method in the global.asax file. You could do this via an HttpModule as well if you prefer.

protected void Application_Error()
{
	Exception exception = Server.GetLastError();
	// Clear the error
	Response.Clear();
	Server.ClearError();

	if (exception is HttpException)
	{
		HttpException ex = exception as HttpException;
		if(ex.GetHttpCode() == 404)
		{
			var url = HttpContext.Current.Request.Path.TrimEnd('/');
			var redirect = FlatFileAccess.Read301CSV();

			if (redirect.Keys.Contains(url))
			{
				// 301 it to the new url
				Response.RedirectPermanent(redirect[url]);
			}
		}
	}
}

This method will catch Exceptions application wide. You can implement much more robust error handing using the same hook but for our purposes we just want to catch 404’s and check for redirects. So when an error is thrown in the application, the exception is checked to see if it is a 404 type HttpException. If it is, we load the 301.csv file and compare it against the requested path. If a match is found we 301 redirect the request to the new url. If it is not found, we let the error fall through.

The Result

You now have a simple, easy to maintain solution to manage your URL rewrites under ASP.NET MVC. If you’re interested you can download the CypressNorth.Bulk301Redirect.Example.

Matt Mombrea

Matt Mombrea

Matt is a longtime entrepreneur and software engineer. He also sits on the board of Computer Science at SUNY Fredonia. Born and raised in Buffalo, Matt is passionate about building and maintaining great businesses in Buffalo. Apart from leading Cypress North, Matt architects our company’s datacenter, engineers dozens of custom applications, and directs the rest of the development team.

See Matt's Most Recent Posts

Share this post

4 comments

phil April 27, 2012Reply

Interesting solution but what are the major advantages over implementing this over using the URL Rewrite Module or Helicons ISAPI rewriter?

I have a large legacy site whereby I’d prefer to implement rewrites in configuration rather than changing the code.

Matt Mombrea April 27, 2012Reply

I wouldn’t call them major advantages but some of the benefits of implementing rewrites this way are:

-Non technical maintenance (for instance, an SEO department can construct the redirects in excel)

-Rewrite updates with no code changes or server changes simply by uploading the new .csv file

-Faster rewrite creation when you’re dealing with a large number of 1 to 1 redirects

Those were the main reasons we used this method on a particular product. We still do more advanced regex based rewriting using the Rewrite Module as well as the MVC routing engine, but creating a new route or a new rewrite entry for every 1 to 1 redirect would have taken much longer as we had around 100.

I definitely don’t see this approach as a replacement for rewrite modules, but it can be a helpful complement.

Lars Holdgaard May 16, 2012Reply

Thanks so much.. I really needed this solution as I’ve to edit my URL routings .. And I really feared that would be a *****.

Thanks!

Greg June 18, 2013Reply

I know this is an older blog, but it was still helpful for me! For my site I added a cache dependency to the CSV reader. I haven’t measured how much it will actually help, but it seemed like a wise modification:

// First attempt to get it from the cache
Dictionary dict = HttpRuntime.Cache[“301-redirects”] as Dictionary;
if (dict != null)
return dict;

// 301 list isn’t cached, so read it from the CSV
// …however you like to build your Dictionary here…

// Add the Dictionary to the cache with a dependency on the CSV file
HttpRuntime.Cache.Insert(“301-redirects”, dict, new CacheDependency(HttpContext.Current.Server.MapPath(“YOUR CSV PATH”)));

return dict;

Leave a Reply

Search our blog

Start A Project

Categories

What's next?

Well...you might like one of these

Article

Is Google Analytics 4 a Tactical Move...

There’s something fishy going on with the way that Google...

Read article

Article

How We Build It: Website Process with...

This month, I sat down with Matt Mombrea, Chief Technical...

Read article

Article

[SOLVED] Using an in-memory repository....

.NET Core Data Protection One of the main benefits of...

Read article