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; }
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; }
<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.