Loading...

Sitecore Custom 301 Redirects Pipeline using a CSV file

Check it out.

Scenario: You have a CSV file with hundreds or thousands redirects to internal/external urls for your Sitecore site and you don't really want to install 301 Redirects Module for Sitecore or do any changes to the Sitecore content tree. You just want to use what is in the CSV file to do your redirects.

Solution: Write a custom pipeline that will act before 404 happens and do a 301 redirect if one is found.

Let's do this

This is how our CSV file looks like. There is only 2 columns, match url and destination url.

Below is sample code for the Process301Redirects pipleline and neccessary config updates.

Process301Redirects

Here is the code for the custom 301 redirect pipeline. This code is expecting csv file "301redirects.csv" in your website root folder. CacheService is caching the csv file with the file dependency which means the file will be cached until it has been updated.


using System;
using System.IO;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.Hosting;
using System.Runtime.Caching;

using Sitecore;
using Sitecore.Security;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Pipelines.Response.GetRenderer;
using Sitecore.Mvc.Presentation;
using Sitecore.Apps.Loader.AppService;

namespace Local.Sitecore.Domain.Pipelines
{
    public class Process301Redirects
    {
        private readonly ICacheService _cacheService;

        public Process301Redirects(ICacheService cacheService)
        {
            _cacheService = cacheService;
        }

        public void Process(Sitecore.Pipelines.PreprocessRequest.PreprocessRequestArgs args)
        {
            // Location of the 301 redirects file
            string filePath = HostingEnvironment.MapPath("~/301redirects.csv");

            // if file does not exist stop here
            if(!File.Exists(filePath))
            {
                return;
            }

            try
            {
                var rawUrl = args.Context.Request.RawUrl.ToLower();

                List<string> excludeIfContains = new List<string>() { ".css", ".js", ".ashx", ".png", ".jpg", ".gif" };

                foreach(var val in excludeIfContains)
                {
                    if(rawUrl.ToLower().Contains(val))
                    {
                        return;
                    }
                }

                // Build a key that we can use to cache the contents in memory against
                string cacheKey = "Process301Redirects";

                // set cache policy to expire if file is updated
                CacheItemPolicy policy = new CacheItemPolicy();
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List<string> { filePath }));

                Dictionary<string, string> urls = _cacheService.GetFromCache<Dictionary<string, string>>(cacheKey, () => ReadCSVFile(filePath), policy);

                string redirectUrl = string.Empty;

                if (urls.TryGetValue(rawUrl, out redirectUrl))
                {
                    args.Context.Response.RedirectPermanent(redirectUrl, true);
                }
            }
            catch (Exception exc)
            {
                Sitecore.Diagnostics.Log.Error(string.Format("Cannot process 301 Redirects in {0}", filePath), exc, this);
            }
        }

        /// <summary>
        /// Reads the 301redirects.csv file.
        /// File contents are stored in the cache with the file dependency (if file is modified, cache is cleared)
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, string> ReadCSVFile(string filePath)
        {
            Dictionary<string, string> list = new Dictionary<string, string>();

            using (StreamReader streamReader = new StreamReader(filePath))
            {
                while (!streamReader.EndOfStream)
                {
                    var line = streamReader.ReadLine();

                    // ensure line is not null or empty
                    if (!string.IsNullOrEmpty(line))
                    {
                        var values = line.Split(',');

                        // confirm length is 2
                        if (values.Length > 1)
                        {
                            var key = values[0].ToLower();
                            //   var val = values[1].ToLower();
                            var val = values[1];
                            // avoid duplicate entries and empty values
                            if (!list.ContainsKey(key) && !string.IsNullOrEmpty(val))
                            {
                                list.Add(key, val);
                            }
                        }
                    }
                }
            }

            return list;
        }
    }
}

Processor config settings

Don't forget to register this pipleline. Here we go.


<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <preprocessRequest>
        <processor patch:before="processor[@type='Sitecore.Pipelines.PreprocessRequest.IIS404Handler, Sitecore.Kernel']" type="Local.Sitecore.Domain.Pipelines.Process301Redirects, Local.Sitecore.Domain" />
      </preprocessRequest>
    </pipelines>
  </sitecore>
</configuration>

There's it. Please ask any questions in the comments below. Thanks :)

Disclaimer
This is a personal blog. The opinions expressed here represent my own and not those of people, institutions or organizations that the owner may or may not be associated with in professional or personal capacity, unless explicitly stated.. In addition, my thoughts and opinions change from time to time I consider this a necessary consequence of having an open mind. This blog disclaimer is subject to change at anytime without notifications.