When you deploy a new website, one of the new build tasks is making sure that all the old pages are pointing to the new urls.

There are plugins that help with this, for example you could use the great SEO Checker from Richard or just rely on the Url Tracking dashboard that now ships with Umbraco.

However, SEO Checker comes with a load of extra stuff I didn't want, and the Url Tracker in Umbraco doesn't allow you to add redirects manually (No idea why) and only works on Umbraco urls (As far as I'm aware). I just wanted a really simple way to manage redirects from a CSV file, but I also wanted the ability to add wild cards for urls.

Wildcards would allow me to say, match this url no matter what is after it. Example would be

/my-cool-url/*

This would match urls like

/my-cool-url/
/my-cool-url/?p=5
/my-cool-url/?utm=anything

I created a really simple IHttpModule to take care of this, and then a simple CSV.

IHttpModule

using System.IO;
using System.Web;
using System.Web.Hosting;
using Umbraco.Core;
using System;
using System.Collections.Generic;

/// <summary>
/// A dead simple CSV 301 redirect module
/// </summary>
public class RedirectsHttpModule : IHttpModule
{
    // Current Request
    public HttpRequest Request => HttpContext.Current.Request;

    /// <summary>
    /// Current Response
    /// </summary>
    public HttpResponse Response => HttpContext.Current.Response;

    /// <summary>
    /// Init
    /// </summary>
    /// <param name="context"></param>
    public void Init(HttpApplication context)
    {
        context.EndRequest += ContextOnEndRequest;
    }

    /// <summary>
    /// Mange the end request and redirect
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    private void ContextOnEndRequest(object sender, EventArgs eventArgs)
    {
        var application = (HttpApplication)sender;

        // Ignore if not a 404 response
        if (application.Response.StatusCode != 404) return;

        // Look for a redirect matching the URL
        var processedUrl = ProcessUrl(Request.RawUrl, CurrentDomain());

        // All the redirects
        var allRedirects = Redirects();

        // Get a querystring less url (If it has one)
        if (processedUrl.Contains("?"))
        {
            // We get the url without the querystring, then add a * as that's the wildcard match
            var querystringLessUrl = string.Concat(processedUrl.Split('?')[0], "*");

            // See if we have a wildcard url
            if (allRedirects.ContainsKey(querystringLessUrl))
            {
                Response.RedirectPermanent(allRedirects[querystringLessUrl]);
            }
        }

        // Default - Check if it's in the dictionary            
        if (allRedirects.ContainsKey(processedUrl))
        {
            Response.RedirectPermanent(allRedirects[processedUrl]);
        }
    }

    /// <summary>
    /// Grabs all the redirects from the csv and dumps them in a dictionary and holds in runtime cache
    /// </summary>
    /// <returns></returns>
    private Dictionary<string, string> Redirects()
    {
        return (Dictionary<string, string>)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem("allpageredirects", () =>
        {
            var dict = new Dictionary<string, string>();
            using (var fs = File.OpenRead(HostingEnvironment.MapPath("~/App_Data/redirects.csv")))
            using (var reader = new StreamReader(fs))
            {
                while (!reader.EndOfStream)
                {
                    var line = reader.ReadLine();
                    if (!string.IsNullOrEmpty(line) && !line.StartsWith("//"))
                    {
                        var values = line.Split(',');
                        dict.Add(StripHttpProtocol(values[0]).Trim(), values[1].Trim());
                    }
                }
            }
            return dict;
        });
    }

    /// <summary>
    /// Any pre url processing happens here
    /// </summary>
    /// <param name="rawUrl"></param>
    /// <param name="currentDomain"></param>
    /// <returns></returns>
    private static string ProcessUrl(string rawUrl, string currentDomain)
    {
        // Get the current domain
        return string.Concat(currentDomain, rawUrl).Trim();
    }

    /// <summary>
    /// Gets the current domain
    /// </summary>
    /// <returns></returns>
    private string CurrentDomain()
    {
        var builder = new UriBuilder(Request.Url.Scheme, Request.Url.Host, Request.Url.Port);
        return StripHttpProtocol(builder.Uri.ToString().TrimEnd('/'));
    }

    /// <summary>
    /// Removes Http or Https from url
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    public string StripHttpProtocol(string url)
    {
        return url.Replace("http://", "").Replace("https://", "");
    }

    public void Dispose() { }

}

CSV File

Just put the CSV file in the /App_Data/ folder and name is redirects.csv. You can also put comments in the CSV but they must start with //

// A comment here
http://www.olddomain.co.uk/something.aspx,https://www.newdomain.co.uk/something/
http://www.olddomain.co.uk/tonystark.asp,https://www.newdomain.co.uk/ironman/

// Another Comment Here
http://www.olddomain.co.uk/matthew-murdock.htm,https://www.newdomain.co.uk/daredevil/
http://www.olddomain.co.uk/steve-rogers.aspx,https://www.newdomain.co.uk/captain-america/
http://www.olddomain.co.uk/mystique/*,https://www.newdomain.co.uk/everything/

All you need to do, is then add the Http Module to your web.config, do it directly under the Umbraco module <add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,umbraco" />

  <remove name="RedirectsHttpModule" />
  <add name="RedirectsHttpModule" type="My.Project.Modules.RedirectsHttpModule, My.Project" />

That's it.

A super quick and super simple way to manage 301 redirects in Umbraco from a CSV file. You could even add a dashboard to list out the redirects in a textarea and manage them that way.