Have you ever had to implement a custom routing for a Sitecore website? Sitecore has its own default mapping logic between an incoming web request url and a specific Sitecore item in the content tree. Few days ago I had to customize this default behavior and implement a custom routing to satisfy a particular request of one of my clients.

The goal

The client asked me to implement a new drop-down selector component that would allow the users of the website to change the content rendered on a page based on its selected value. Also, the user needed to be able to share the page in a particular “content state”, controlled by the component selected value.

A possible solution

I decided to rely on the Sitecore personalization engine to render different content based on the selected value in the new selector component, and build a new custom condition rule to assess the selected value.

For the page url sharing, I thought that I could store the selected value in a query string and append it to the page url, like for example:

https://mywebsite/mypage?selector=value1

The query string value would then be used in the custom condition rule logic and validated against the name of a particular item selected in the personalization condition rule. The following code shows the implementation of this custom condition rule:


class SelectorCondition : OperatorCondition where T : ConditionalRenderingsRuleContext
    {
        protected override bool Execute(T ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");

            var selectedValue = HttpContext.Current.Request.QueryString["selector"].ToLower();

            if (!string.IsNullOrEmpty(selectedValue))
            {
                return selectedValue == GetItemName();
            }

            return false;
        }

        public string ItemId { get; set; }

        private string GetItemName()
        {
            var item = Sitecore.Context.Database.GetItem(ItemId);
            if (item!= null)
            {
                return Sitecore.Context.Database.GetItem(cropItem).DisplayName.ToLower();
            }
            return string.Empty;
        }
}

This solution was working and I didn’t have to implement any custom routing…yet. Pretty easy, no?

The additional challenge

Well, I thought so, until the analytics team told me that we could not use a query string in the page url because it would negatively impact on how these pages with different dynamic content would be indexed by search engines. A search engine might consider pages with the same url but different query string values as pages with duplicate content and it might give an higher rank only to the main default page url without any query string.

Instead of using query strings, the selected value needed to be included in the last segment of the page url, like for example:

https://mywebsite/mypage/value1

I had to implement a custom routing logic in Sitecore.

The final solution: a custom ItemResolver processor in the httpRequestBegin Sitecore pipeline!

The httpRequestBegin pipeline has a processor, called ItemResolver, responsible to map and set the Sitecore Context Item using the value on the incoming web request url. If this processor doesn’t find an item associated with the default Sitecore routing logic, the web request ends up being redirected to the 404 Not Found page.

The best place to include a CustomItemResolver processor is after the default ItemResolver processor in the httpRequestBegin pipeline, so that any custom routing logic would not conflict with requests for existing Sitecore items.

This is the code of the CustomItemResolver processor that I developed to implement this solution:

    public class CustomItemResolver  : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            if (Context.Item != null || Context.Database == null || args.Url.ItemPath.Length == 0)
                return;

            var itemPath = args.Url.ItemPath;
            var itemPathSegments = itemPath.Split('/').ToList();
            var parentItemPath = itemPath.Remove(itemPath.Length - itemPathSegments.Last().Length - 1);

            var parentItem = args.GetItem(parentItemPath);

            if (parentItem.TemplateIsOrBasedOn(new ID("{GUID}")))
            {
                Context.Item = parentItem;
                args.Url.QueryString = "selector=" + itemPathSegments.Last();
            }
        }
    }

After ensuring that the Context Item is null and that both the Context Database and the request url item path are not null, the processor tries to retrieve the parent item associated with the request path, removing the last segment from the incoming request url.

Also I wanted to limit the scope of the custom routing to only items based on a particular page template. If the filtering condition is satisfied, the Sitecore Context Item is set to be the parent item. Instead the query string of the request url is set using the selector query string with the correct selected value, or the last segment of the original incoming web request.

Conclusion

In this blog post I described a solution to implement a custom routing processor in Sitecore to avoid negative impacts in SEO ranking for pages with personalized content. If you have any questions, or want to share your different approach to this solution, please don’t hesitate to comment on this post.

Thank you for reading!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s