Customizing Sitecore URL Builder Options for Multisite Support

Customizing Sitecore URL Builder Options for Multisite Support

Sitecore provides a built-in URL builder that generates URLs based on the structure of your content tree, but sometimes you may need to customize this behavior to support multiple sites with different URL structures. In this article, we’ll look at how to customize Sitecore URL builder options to support multisite environments, especially if each site in your setup has different requirements for UrlBuilderOptions.

Use Case

Consider we do have some limitations on Multisite setup due to which one project would need the languageEmbedding option to be “never”, and another site needs it as “always”. It’s not good to have a LinkManager patch or get one for each Site which might complicate things. Today we will see how we can have it as part of the Sitedefinition property which drives the whole process of customizing UrlBuilderOptions.

Exploring the Fundamentals of Sitecore’s URL Builder Options

Before we dive into customizing Sitecore URL builder options, let’s first take a closer look at how Sitecore generates URLs by default. Sitecore uses a class called ItemUrlBuilder to generate URLs for individual items based on their position in the content tree. This class can be customized by creating a new class that inherits from ItemUrlBuilder and overrides its methods.

The ItemUrlBuilder class takes two parameters: an Item object representing the item to generate a URL for, and an ItemUrlBuilderOptions object that controls how the URL should be generated. The ItemUrlBuilderOptions class provides several properties that you can customize to control the format of the generated URL, including:

  • AlwaysIncludeServerUrl: Determines whether to include the server hostname in the generated URL. (DEFAULT: false)
  • LanguageEmbedding: Determines how to include the language code in the generated URL. OPTIONS: asNeeded | always | never (DEFAULT: asNeeded)
  • LanguageLocation: Determines where in the URL the language code should be included. OPTIONS: filePath | queryString (DEFAULT: filePath)
  • LowercaseUrls: Determines whether to convert the generated URL to lowercase. (DEFAULT: false)
  • EncodeNames: Determines whether to encode special characters in the generated URL. (DEFAULT: true)
  • UseDisplayName: Determines whether to use the display name of the item in the generated URL. (DEFAULT: false)

Customizing Sitecore URL Builder Options for Multisite Support

Now that we have a better understanding of how Sitecore generates URLs, let’s look at how to customize these options to support multisite environments. By default, Sitecore generates URLs based on the content tree structure, but if you have multiple sites with different URL requirements, you may need to customize the URL builder options for each site.

To do this, you can create a new class in the Foundation project or Core repo that is common for all Sites. This new class inherits from ItemUrlBuilder and overrides the Build method to customize the URL builder options based on the current site. Here’s an example of how this might look:

public class SiteItemUrlBuilder : ItemUrlBuilder
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SiteItemUrlBuilder"/> class.
        /// </summary>
        /// <param name="defaultOptions">The defaultOptions<see cref="DefaultItemUrlBuilderOptions"/>.</param>
        public SiteItemUrlBuilder(DefaultItemUrlBuilderOptions defaultOptions) : base(defaultOptions)
        {
        }

        /// <summary>
        /// The Build.
        /// </summary>
        /// <param name="item">The item<see cref="Item"/>.</param>
        /// <param name="options">The options<see cref="ItemUrlBuilderOptions"/>.</param>
        /// <returns>The <see cref="String"/>.</returns>
        public override string Build(Item item, ItemUrlBuilderOptions options)
        {
            var urlBuilderPathName = options.Site?.SiteInfo?.Properties["urlBuilder"];

            if (String.IsNullOrEmpty(urlBuilderPathName))
            {
                return base.Build(item, options);
            }

            var customOptions = new SiteItemUrlBuilderOptions().Load(urlBuilderPathName);

            if (customOptions == null)
            {
                return base.Build(item, options);
            }

            customOptions.SiteResolving = options.SiteResolving;
            return base.Build(item, customOptions);
        }

### Your patch config for this class goes as below, for my case made lowerCaseUrls=true and languageEmbedding=never ######

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
   <links>
    <urlBuilder>
      <lowercaseUrls>true</lowercaseUrls>
      <languageEmbedding>never</languageEmbedding>
    </urlBuilder>
    <itemUrlBuilder set:type="JSS.Foundation.Multisite.SiteItemUrlBuilder, JSS.Foundation.Multisite"/>
   </links>
  </sitecore>
</configuration>

Implementation details :

  1. We will apply a patch to route all item URL building through our custom logic, as is our usual practice. (above snippet)
  2. The method first checks if the Site property is not null, and if it is, it falls back to the default implementation by calling base.Build(item, options).
  3. If the Site property is not null, it retrieves the value of the urlBuilder property from the site’s SiteInfo properties. (urlBuilder will be the new property that we will use to customize)
  4. If the urlBuilder property is null or empty, it falls back to the default implementation by calling base.Build(item, options).
  5. If the urlBuilder property is not null or empty, it creates a new instance of SiteItemUrlBuilderOptions and loads the options from the specified urlBuilder path name.
  6. If the loaded options are null, it falls back to the default implementation by calling base.Build(item, options).
  7. If the loaded options are not null, it sets the SiteResolving property of the loaded options to the same value as the options.SiteResolving property.
  8. Finally, it calls the default implementation by calling base.Build(item, customOptions) with the loaded options.

Now we move on to the next section, which involves loading the custom URL builder options from a node specified by the Site’s definition, as demonstrated below.

<site name="jssSampleSite"
      inherits="website"
      hostName="jssSampleSite.local"
      scheme="https"
      rootPath="/sitecore/content/jssSampleSite"
      dictionaryDomain="{63475DF6-7835-4125-9E38-CFD450662D64}"
      urlBuilder="links/jssSampleSiteUrlBuilder" 
      patch:before="site[@name='website']">
</site>
<links>
  <jssSampleSiteUrlBuilder>
    <alwaysIncludeServerUrl>false</alwaysIncludeServerUrl>
    <languageEmbedding>always</languageEmbedding>
    <languageLocation>filePath</languageLocation>
    <lowercaseUrls>true</lowercaseUrls>
    <encodeNames>true</encodeNames>
    <useDisplayName>false</useDisplayName>
  </jssSampleSiteUrlBuilder>
</links>

Once our custom logic locates the value for the “urlBuilder” property from the Site definition, the next step is to retrieve the XML path specified and refresh the defaults with any Site-specific changes. We accomplish this using the SiteItemUrlBuilderOptions class provided below, which is used to load any necessary items. To ensure future migration ease, it is advisable to use base.Load for the nameValueCollection, since it has built-in validations.

public class SiteItemUrlBuilderOptions : DefaultUrlBuilderOptions
    {
        public ItemUrlBuilderOptions Load(string urlBuilderPathName)
        {
            var parentNode = ServiceLocator.ServiceProvider.GetService<BaseFactory>().GetConfigNode(urlBuilderPathName);

            if (parentNode?.ChildNodes == null || parentNode.ChildNodes.Count == 0)
            {
                return null;
            }

            var nameValueCollection = new NameValueCollection();

            foreach (XmlNode item in parentNode.ChildNodes)
            {
                nameValueCollection.Add(item.Name, item.InnerText);
            }

            base.Load(nameValueCollection);

            return new ItemUrlBuilderOptions()
            {
                AlwaysIncludeServerUrl = base.AlwaysIncludeServerUrl,
                LanguageEmbedding = base.LanguageEmbedding,
                LanguageLocation = base.LanguageLocation,
                LowercaseUrls = base.LowercaseUrls,
                EncodeNames = base.EncodeNames,
                UseDisplayName = base.UseDisplayName
            };
        }
    }

In conclusion, our custom SiteItemUrlBuilder class offers a flexible solution for customizing Sitecore’s URL builder options and enabling support for multiple sites with different URL builder configurations. By loading custom options from a specified configuration path, this class inherits from the DefaultUrlBuilderOptions class and returns an instance of ItemUrlBuilderOptions with the desired options selected.

Happy Sitecore-ing and happy building!

Related Posts

Leave a Reply

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