Skip to main content

Microsoft

Sitecore: Forcing Vanity URLs to Perform a 301 Redirect

A client of ours is performing a migration of their old site content onto their new Sitecore instance. The old site contained vanity URLs for their product catalog that they wanted to utilize for the new site. They wanted to send a 301 redirect for SEO and then display the new Sitecore item representing the product in the vanity URL.
The vanity URLs were structured in a way that allowed us to extract the product identifier from the vanity URL, perform the Sitecore item lookup, send the HTTP 301 response, and navigate to the Sitecore item for the product.

Let’s start with the vanity URL structure:
www.company.com/cylinder/P1847 Copper Cylinder
All vanity URLs end with the product identifier, and to prefix that a generic categorization was used. This generic category classifier no longer exists in their new Sitecore URLs, but instead has been replaced with a new category system representing a quality grade. The old categories have been added to content outside of the site for reference.
So we want to catch this vanity URL coming into Sitecore. A common approach is to create a processor for the Sitecore pipeline.
.csharpcode, .csharpcode pre
{
font-size: 13.3333px;
font-width: 400;
color: black;
font-family: “Courier New”;
margin-bottom: 10px;
}
.csharpcode pre { margin: 0px; }
.csharpcode .comment { color: #008000; }
.csharpcode .comment2 { color: #808080; }
.csharpcode .type { color: #2B91AF; }
.csharpcode .keyword { color: #0000FF; }
.csharpcode .string { color: #A31515; }
.csharpcode .preproc { color: #0000FF; }

Code:

    public class VanityURLProcessor : HttpRequestProcessor
    {
        private static readonly string message301 = "301 Moved Permanently";
        public override void Process(HttpRequestArgs args)
        {
            try
            {
                // check path for category value
                if (VanityCategoryExists(HttpContext.Current.Request.Url.OriginalString))
                {
                    //strip product ID
                    var productID = ExtractProductID(HttpContext.Current.Request.Url.OriginalString);
                    if (!string.IsNullOrEmpty(productID))
                    {
                        // find item - use Sitecore Search as preferred approach
                        Item returnItem = FindItem(productID);
                        if (returnItem != null)
                        {
                            Sitecore.Context.Item = returnItem;
                            // Making sure the 301 status is communicated
                            HttpContext.Current.Response.RedirectLocation = LinkManager.GetItemUrl(returnItem);
                            HttpContext.Current.Response.StatusCode = 301;
                            HttpContext.Current.Response.Status = message301;
                            HttpContext.Current.ApplicationInstance.CompleteRequest();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //log error
                Log.Error(ex.Message, ex, this);
            }
        }
    }

A great place for this processor to perform is right after the ItemResolver.
.csharpcode, .csharpcode pre
{
font-size: 13.3333px;
font-width: 400;
color: black;
font-family: “Courier New”;
margin-bottom: 10px;
}
.csharpcode pre { margin: 0px; }
.csharpcode .comment { color: #008000; }
.csharpcode .comment2 { color: #808080; }
.csharpcode .type { color: #2B91AF; }
.csharpcode .keyword { color: #0000FF; }
.csharpcode .string { color: #A31515; }
.csharpcode .preproc { color: #0000FF; }

Code:

<processor type="Client.Pipelines.HttpRequestBegin. VanityURLProcessor, CustomLibrary" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />

Let’s walk through the code. The first thing is to determine if the URL contains one of the vanity categories. If we have a match, then we extract the product identifier from the URL. If that is successful, we find the item in Sitecore based on the product identifier. If the item is found, we set the context item to our found item, provide a redirect location in the response, set the response status, and complete the request.
Why not just perform a Response.End? That is the first inclination of how to end the request. However this throws a ThreadAbortException. In this situation all we will do is fill up the log with the try-catch block and Response.End. CompleteRequest skips all the steps in the ASP.NET pipeline and goes directly to EndRequest.
With the 301 status provided and the new Sitecore item served, previously used vanity URLs for the old site bring the visitor to its equivalent item in the new site.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Mark Servais

Mark Servais is a software solutions professional with experience in creating solutions using Sitecore and the Microsoft stack of technologies. Experienced in project and development lifecycles through several viewpoints that include project management, development, and leadership. Mark has over 25 years’ experience in software engineering and application development. Mark has been awarded Sitecore’s Technology MVP award from 2014-2017 and currently holds a Master’s degree in Software Engineering from Carroll University.

More from this Author

Categories
Follow Us
TwitterLinkedinFacebookYoutubeInstagram