Beta Version

BLOG

Sathish Balakrishnan
7 Minute read

How to create Sitecore Custom URLs - DIY Guide

We often come across the need to implement custom URLs on our website. It’s usually done to make web experience seamless, or to augment marketing campaigns, or simply to make navigation easy. In this blog, we will talk about how to create Sitecore custom URLs in a few easy steps.

Overview

URLs are generated in Sitecore by the structure of items in the content tree (by default). For instance, if your item ‘Careers’ in the content tree of Sitecore looks like this:

• Sitecore

• Content

• Home

• About Our Company

• History

• Careers

Your Careers page URL will look this way:

www.yourcompany.com/About Our Company/Careers.aspx

 

Sitecore determines an item's default URL based on its path. For example, the default URL for

/sitecore/content/home/about our company/careers/jobs
is
www.yourcompany.com/about/ourcompany/careers/jobs.aspx

 

In some cases, shorter URLs mapping to longer paths, are useful, such as www.yourcompany.com/jobs.aspx may be preferable for marketing materials (email campaigns, print advertisements, etc.). Sitecore supports alternative URLs via an feature. To cross your eyes, this takes people to the same page with far fewer words.

At

http://marketplace.sitecore.net/en/Modules/Sitecore_Redirect_Manager.aspx
, another module "Sitecore Redirect Manager" is available, which can be used for redirecting. This module helps you maintain the ranking and search rating for pages (whole sections if necessary) that have been replaced, moved or removed from your site.

 

Both the Aliases Redirection module are good for some generic scenarios, but for some other scenarios listed below - a custom approach is better.

• You want the Custom URLs to be generated by Sitecore - if the item is linked in Rich Text Editor or Sitecore.LinkManager is used to get the URL of the item. The Sitecore Link Manager does not take aliases or redirection mappings into account by default. This means that links generated by Sitecore point to the original url.

• When a new item is added/updated/deleted - you do not want to add/update/delete aliases or redirect entries manually, as this could easily be missed.

• You don't want performance compromises. Both approaches could pose a performance problem, because the mappings are stored in a flat list.

• We sometimes want the freedom to create URLs that are independent of the structure of the content tree. The approach should also be free of the limitations discussed above. We must communicate Sitecore to create custom URLs based on our custom rules for this. We would also need a component, to process these custom URL rules.

Ebook
Upgrade to Sitecore 9.1 Using this Handy Guide

Case Study

In the case of one of our customers, there were more than 2500 people for whom items were created in the folder ‘People’ and grouped in folders named ‘A’ ‘B’ ‘C’ etc., based on the last name. The customer wanted the URL of each biography page to be based on the email ID of the person. For instance, the person with an email identification as john.doe@yourcompanyname.com, should be accessible via the following URL.

www.yourcompany.com/john-doe

Solution to meet this requirement:

When we need to take care any custom URL handling requirements, we must deal mainly with two components.

1. Custom Link Provider: This custom logic generates a custom URL here.

2. Custom Item Resolver: The custom logic attempts to resolve a valid item by the custom URL in the content tree.

How to develop a Custom Link Provider?

1. Just inherit a Sitecore. Links. LinkProvider class and override the method GetItemUrl (...).

namespace CustomLibrary.Links
{
   public class CustomLinkProvider : Sitecore.Links.LinkProvider
   {
      public override string GetItemUrl(Sitecore.Data.Items.Item item,
         Sitecore.Links.UrlOptions options)
      {
         if (/* my condition of when to create custom url */)
         {
            /*generate and return custom url*/
            return CustomUrlManager.GetCustomUrl(item);
         }
         /*else return the default url generated by Sitecore*/
         return base.GetItemUrl(item, options);
      }
   }
}

2. Thereafter, change the defaultProvider in the linkManager (in web.config) to your custom class like:

<linkManager defaultProvider="custom">
   <providers>
      <clear />
      <add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="true"
         alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="never" languageLocation="filePath"
         shortenUrls="true" useDisplayName="false" />
      <add name="custom" type="CustomLibrary.Links.CustomLinkProvider, CustomLibrary" addAspxExtension="true"
         alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="never" languageLocation="filePath"
         shortenUrls="true" useDisplayName="false" />
   </providers>
</linkManager>

Sitecore Pipelines and Processors

Pipelines define a sequence of processors that implement a function, such as setting the HTTP Sitecore context. Pipelines support encapsulation, flexible configuration, problem separation, testability and other goals. Some of the most important pipelines include:

• < initialize>: The Sitecore application is initialized.

• < preprocessRequest>: Invoked for each HTTP request managed by ASP.NET, but aborted for some requests.

• < httpRequestBegin>: Sets the context of Sitecore. Invoked for each HTTP request not directed to ASP.NET by the < preprocessRequest> pipeline. It essentially contains the request processing logic. In this pipeline, we will add a custom processor to map the custom URLs with Sitecore items.

See John West’s blog post Important Pipelines in the Sitecore Digital Marketing System, for more information around pipelines. Check out Creating and running custom pipelines in Sitecore by Alistair Deneys for more information on creating and invoking pipelines.

Each pipeline processor contains a method called Process (), which accepts one argument and returns void. If the processing context is not relevant to the processor, this method should be returned immediately. A processor can abort the pipeline to prevent the use of subsequent processors by Sitecore.

The ItemResolver processor is responsible for getting the Sitecore context item by the incoming URL.

How to create Custom Item Resolver?

1. For creating Custom Item Resolver, we’ll override the GetItemUrl (...) method by inheriting a class from

Sitecore.Pipelines.HttpRequest.HttpRequestProcessor
.

 

namespace CustomLibrary
{
   public class CustomUrlResolver : HttpRequestProcessor
   {
      public override void Process(HttpRequestArgs args)
      {
         Assert.ArgumentNotNull(args, "args");

         /*In Case Sitecore has mapped the item, do not do anything and simply return*/
         if (Context.Item != null || Context.Database == null || args.Url.ItemPath.Length == 0) return;

         /*If not, Check for item based on the FilePath*/
         Item contextItem = CustomUrlManager.GetItemByFilePath(args.Url.FilePath);
         if (contextItem != null) Context.Item = contextItem;
      }
   }
}

2. Add a new processor in web.config to handle custom URL requests. Make sure you add it immediately after the processor ‘ItemResolver’. It is important to have the right order.

<processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel"/>
<!-- the proper order is important -->
<processor type="CustomLibrary.CustomUrlResolver, CustomLibrary"/>
<processor type="Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel"/>

You must now have noticed that we have used ‘CustomUrlManger’ class in both Link Provider and Item Resolver classes after going through both custom Link Provider and Item Resolver. This class contains wrapper functions for the generation of custom URLs and maps with custom URLs.

namespace CustomLibrary
{
   public static class CustomUrlManager
   {
      /*This function takes Sitecore item as argument and returns true if item has a Custom URL and false if not.*/
      public static bool CustomUrlExists(Item item)
      {
         bool aliasExists=false;

         /*Check if item has a custom URL. Eg. Check for Template Id/Template name of item */
         /*Eg. If Item is based on People Template then set "aliasExists" as true*/
         /*return "aliasExists" [true/false]*/
	return aliasExists;
      }

      /* This function takes sitecore item as argument and returns custom Url (string) based on our custom Rules */
      public static string CustomUrlManager.GetCustomUrl(Item item)
      {
         string customUrl=string.Empty;
         /*Create custom Url based on custom rules*/
         /*Eg.*/
         /*If Item is based on People Template Then*/
         /*Read the value of the Email Field and generate the custom URL based on it*/
         /*return custom Url*/
         return customUrl;
      }

      public static string CustomUrlManager.GetAlias(string customUrl)
      {
         /*Remove extra unwanted characters like -, / etc. from URL and return Alias that would be stored in index*/
         return customUrl;
      }

      /*This function looks for the item ,based on "File Path", using the custom search index(CustomUrlIndex) and
         returns the mapped item (Sitecore Item)*/
      public static Item CustomUrlManager.GetItemByFilePath(string filePath)
      {
         Item mappedItem=null;

         /*Find the item based on the filePath. We have used custom search index to store the mapping between Item and
            the custom Url (based on email id in our case)*/
         /*return the mapped Item*/
         string alias= CustomUrlManager.GetAlias(filePath);
         UrlSearchResults objUrlSearchResults=new UrlSearchResults(alias);
         mappedItem = objUrlSearchResults. GetItemByAlias(alias)
         return mappedItem;
      }
   }
}

For optimized performance, I used the custom search index to store mapping between the item and the custom Url. Lucene Search is much faster than a quick query or code that is iterated by a list of items. I'll give you a basic guide and code on how to create a custom search index. To create our custom search, we'll use Sitecore. Search.

Following are the steps below to create a Custom URL Index:

To create a custom index, create our own crawler/indexer. A custom crawler can be created by creating a class inherited from the class Sitecore. Search. Crawlers. ‘DatabaseCrawler’ and then overriding the method "AddAllFields." In this index, we will store the custom URL / alias related to the item. The CustomUrlManager class discussed earlier will be used to obtain the custom URL / alias associated with the item. The document. Add method is used to add the document to a custom field. A Field object is used as an argument.

document.Add(new Field(fieldName, fieldValue, storageType, fieldIndexType)) To find out what each of these options means, please refer to the Lucene Documentation.

We need to create a custom search result class after creating the crawler, which uses the custom index to find Sitecore items mapped with custom URLs. Custom search queries are handled by this class. To search for the item that maps the respective custom url/alias, we will create a query.

All we need to do now is add search configuration settings for the custom indexer in web.config

For more details, please refer to my Custom Search blog.

The custom search can now be started.

Search Code

using Sitecore;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Lucene.Net.Documents;
using Sitecore.Data.Managers;
using Field = Lucene.Net.Documents.Field;
using System;
using Sitecore.Search;
using Lucene.Net.Search;
using System.Collections.Generic;
using System.Web.UI.WebControls;
using Sitecore.Data;

namespace CustomLibrary
{
   public struct UrlIndexFields
   {
      public const string UrlAlias = "UrlAlias";
   }

   public class UrlIndexer : Sitecore.Search.Crawlers.DatabaseCrawler
   {
      ///<summary>
      ///Indexes the Url Alias Information
      ///</summary>
      ///<param name="item"></param>
      ///<param name="document"></param>
      ///<param name="versionSpecific"></param>
      protected override void AddAllFields(Document document, Item item, bool versionSpecific)
      {
         try
         {
            if (CustomUrlManager.CustomUrlExists(item))
            {
               string urlAlias = CustomUrlManager.GetAlias(CustomUrlManager.GetCustomUrl(item));
               if (!string.IsNullOrEmpty(urlAlias))
               {
                  document.Add(new Field(UrlIndexFields.UrlAlias, urlAlias, Field.Store.YES, Field.Index.ANALYZED));
               }
            }
         }
         catch (Exception exception)
         {
            Sitecore.Diagnostics.Log.Error("CustomLibrary.UrlIndexer > AddAllFields : ", exception, this);
         }
      }
   }

   public class UrlSearchResults
   {
      Sitecore.Search.Index index = SearchManager.GetIndex("UrlIndex");

      public Item GetItemByAlias(string alias)
      {
         List<Item> items = null;
         SearchHits KeywordHits;
         try
         {
            using (IndexSearchContext SearchContext = index.CreateSearchContext())
            {
               KeywordHits = DoAliasSearch(SearchContext, alias);
               items = GetItemsList(KeywordHits);
               if (items.Count > 0)
               {
                  return items[0];
               }
            }
         }
         catch (Exception exception)
         {
            Sitecore.Diagnostics.Log.Error("BusinessModules.UrlSearchResults > GetItemByAlias : ", exception, this);
         }
         return (Item)null;
      }

      private Sitecore.Search.SearchHits DoAliasSearch(Sitecore.Search.IndexSearchContext searchContext, string alias)
      {
         CombinedQuery query = new CombinedQuery();
         if (String.IsNullOrEmpty(alias) == false)
         {
            QueryBase aliasQuery = new FieldQuery(UrlIndexFields.UrlAlias, "\"" + alias + "\"");
            query.Add(aliasQuery, QueryOccurance.Must);
         }
         SearchHits searchhits = searchContext.Search(query, index.GetDocumentCount());
         return searchhits;
      }
   }
}
Sathish
Sathish Balakrishnan Vice President (Technology and Delivery)


Talk to our Experts

Talk to us about how we bring together 1:1 personalisation, deep Martech Expertise, CX & Demand Gen Strategy, Engagement Analytics & Cross-Channel Orchestration to drive award winning experiences that convert

Get in touch for a complimentary consultation or a demo today.