Multi custom links with Sitecore 10 using ItemUrlBuilder

A common need for multisite implementation

·

3 min read

Problem

In the past, a multisite project used to create a few custom link provider to support dynamic URL with wildcard item in Sitecore solution.

Unfortunately, when I try to migrating from an older Sitecore solution into Sitecore 10, I noticed that the LinkProvider is obsolete in Sitecore 10, this is no longer the way to approach custom URL.

However, we can extend Sitecore.Links.UrlBuilders.ItemUrlBuilder and override the main method Build if the solution is a single site project.

With multisite project, we cannot have multiple ItemUrlBuilder because we can only replace the core Sitecore.Links.UrlBuilders.ItemUrlBuilder once.

Example

  • Site A using CustomLinkProviderSiteA
  • Site B using CustomLinkProviderSiteB

Solutions

There are 2 solutions here can resolve the above issue.

  1. Create a foundation UrlBuilder with custom pipeline
  2. Extend from LocalizableLinkProvider If you are using SXA. The default LocalizableLinkProvider is actually allow us to extend but have to override GetItemUrl(Item, ItemUrlBuilderOptions) instead

Here, I will only provide detailed solution for foundation UrlBuilder with custom pipeline.

Foundation ItemUrlBuilder with custom pipeline

Since we can only replace the core Sitecore.Links.UrlBuilders.ItemUrlBuilder once. I decided to create a foundational ItemUrlBuilder that kicks off a custom pipeline to generate the URL

Create a pipeline argument class ItemUrlBuilderExtensionsArgs

using Sitecore.Data.Items;
using Sitecore.Links.UrlBuilders;
using Sitecore.Pipelines;

namespace Sitecore.Foundation.SitecoreExtensions.Pipeline
{
    public class ItemUrlBuilderExtensionsArgs : PipelineArgs
    {
        public ItemUrlBuilderOptions ItemUrlBuilderOptions { get; set; }
        public Item Item { get; set; }
        public string ItemUrl { get; set; }
    }

}

Create foundation ItemUrlBuilder

using Sitecore.Foundation.SitecoreExtensions.Pipeline;
using Sitecore.Data.Items;
using Sitecore.Links.UrlBuilders;
using Sitecore.Pipelines;

namespace Sitecore.Foundation.SitecoreExtensions.Links
{
    public class ItemUrlBuilder : Sitecore.Links.UrlBuilders.ItemUrlBuilder
    {
        public ItemUrlBuilder(DefaultItemUrlBuilderOptions defaultOptions) : base(defaultOptions)
        {
        }

        public override string Build(Item item, ItemUrlBuilderOptions options)
        {
            options.LanguageEmbedding = Sitecore.Links.LanguageEmbedding.Never;
            options.LowercaseUrls = true;
            var args = new ItemUrlBuilderExtensionsArgs
            {
                ItemUrlBuilderOptions = options,
                Item = item
            };

            CorePipeline.Run("itemUrlBuilderExtensions", args);

            if (!string.IsNullOrEmpty(args.ItemUrl))
            {
                return args.ItemUrl;
            }

            return base.Build(item, options);
        }
    }
}

Patch Sitecore Config

Create a config file to patch <itemUrlBuilder> and introduce new <itemUrlBuilderExtensions> in pipelines

Config filename: Sitecore.Foundation.SitecoreExtensions.UrlBuilder.config

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>

        <links>
            <itemUrlBuilder>
                <patch:attribute name="type">Sitecore.Foundation.SitecoreExtensions.Links.ItemUrlBuilder, Sitecore.Foundation.SitecoreExtensions</patch:attribute>
            </itemUrlBuilder>
        </links>
        <pipelines>
            <itemUrlBuilderExtensions>
            </itemUrlBuilderExtensions>
        </pipelines>
    </sitecore>
</configuration>

Now, the custom itemUrlBuilder is ready and we can introduce new processor in pipeline to generate custom URL based on the project needs

Here is an example of the custom code used for processor:

using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;
using Sitecore.XA.Foundation.Multisite;
using Sitecore.Data;
using Sitecore.Foundation.SitecoreExtensions.Pipeline;

namespace Sitecore.Feature.Article.ItemUrlBuilder
{
    public class ArticleUrlBuilder
    {
        public void Process(ItemUrlBuilderExtensionsArgs args)
        {
            var options = args.ItemUrlBuilderOptions;
            var item = args.Item;
            var setting = ServiceLocator.ServiceProvider.GetService<IMultisiteContext>().GetSettingsItem(item);

            if (item.DescendsFrom(Templates.Article.ID))
            {
                //return custom url to args.ItemUrl 
                var articleWildCard = Context.Database.GetItem(setting.GetField("Article Wildcard Page"));
                if (articleWildCard != null && articleWildCard.Parent != null)
                {
                    var baseUrl = Links.LinkManager.GetItemUrl(articleWildCard.Parent, options);
                    args.ItemUrl = $"{baseUrl}/{MainUtil.EncodePath(item.Name, '/').ToLower()}";
                }
            }
        }
    }
}

Finally, register the processor in the custom <itemUrlBuilderExtensions> pipeline .

<itemUrlBuilderExtensions>
                <processor type="Sitecore.Feature.Article.ItemUrlBuilder.ArticleUrlBuilder, Sitecore.Feature.Article" />
</itemUrlBuilderExtensions>

With the custom <itemUrlBuilderExtensions> pipeline, other developers can add new processor to handle their custom URL without override the one and only one ItemUrlBuilder.