3

We have Sitecore 9.3 multisite setup on Azure PAAS. We have been noticing a weird issue where for "internal" urls, LinkManager.GetItemUrl is intermittently generating urls for different site and not the context site .

e.g. we have 3 sites setup in the following order

  1. portal.mysite.com.au
  2. mysite.com.au
  3. myothersite.com.au

portal.mysite.com.au has navigation links to transactional pages like, update personal details and change password, e.g. portal.mysite.com.au/update-password

We have seen from our monitoring tools that the navigation links in portal.mysite.com.au sometimes have mysite.com.au in the hostname.

e.g. Sometimes portal.mysite.com.au/update-password link url is mysite.com.au/update-password which is causing 404 Page Not Found.

Has anyone experienced similar issue and/or suggest what we could try to fix this issue.

  public static string LinkFieldUrl(this Item item, ID fieldID, bool includeServerName = false, bool siteResolving = true, SiteContext site = null, bool disableSecurity = false)
        {
            if (item == null)
                throw new ArgumentNullException(nameof(item));

            LinkField linkField = null;
            Item targetItem = null;
            if (disableSecurity)
            {
                using (new SecurityDisabler())
                {
                    linkField = item.LinkField(fieldID);
                    targetItem = linkField?.TargetItem;
                }
            }
            else
            {
                linkField = item.LinkField(fieldID);
                targetItem = linkField?.TargetItem;
            }

            if (linkField == null)
                return string.Empty;

            switch (linkField.LinkType.ToLower())
            {
                case "internal":
                    // Use LinkMananger for internal links, if link is not empty
                    var options = LinkManager.GetDefaultUrlBuilderOptions();
                    options.AlwaysIncludeServerUrl = includeServerName;
                    options.SiteResolving = siteResolving;

                    if (site != null)
                        options.Site = site;
                    if (targetItem == null) return string.Empty;
                    var url = LinkManager.GetItemUrl(targetItem, options);
                    var queryString = item.LinkFieldQuerystring(fieldID);
                    if (!string.IsNullOrEmpty(queryString))
                    {
                        url = $"{url}?{queryString}";
                    }

                    return url;
                case "media":
                    // Use MediaManager for media links, if link is not empty
                    return targetItem != null ? MediaManager.GetMediaUrl(targetItem) : string.Empty;
                case "anchor":
                    // Prefix anchor link with # if link if not empty
                    return !string.IsNullOrEmpty(linkField.Anchor) ? "#" + linkField.Anchor : string.Empty;
                case "external":
                case "mailto":
                case "javascript":
                    return linkField.Url;
                default:
                    return string.Empty;
            }
        }

3 Answers 3

0

I had similar problems for a Sitecore 9.3 instance. There is a hotfix specific for 9.3 that you can download here: https://sitecore.box.com/s/spxtvsfdx81hmypq75uh815q81kqadtv that addresses among other "LinkManager.GetItemUrl" gets the wrong site issue. Please read the readme.txt

As part of the fix for #177155 'LinkManager.GetItemUrl gets the wrong site' please add 'disableTrailingWildcard="true"' attribute to the site configuration under node for every site involved in the issue.

2

Link Generation in Sitecore depends on a few things.

  • What your current Site Context is (under which site was your Sitecore.Context.Item resolved)
  • Where your target item is located (same site, different site, any site at all)

Your code is a bit "all over the place". But Sitecore does not intermittently change behaviour on this from time to time. So if you're getting unexpected results, it would be because you're

  • Sending it something wrong in your site parameter
  • Or one of your links has a .TargetItem pointing to a different site than your context site

You need to check your field values to ensure that these internal links are indeed pointing where you expect them to point.

And then you probably need to get rid of a LOT of redundant code. Your code above could and should be

var lf = (LinkField)item.Fields[fieldID];
return lf?.GetFriendlyUrl();

And nothing more.

1
  • Thanks for the reply Mark. I can confirm that the TargetItem is pointing to the context site. We ported over the code from 8.2 to 9.3 and never experienced any issue with 8.2. I do agree that the specified code needs to be cleaned up. But, I can't see it causing the weird issue that we are experiencing.
    – Tiger
    Commented Jan 31, 2021 at 6:37
0

To fix the issue we ended up implementing a custom UrlBuilder and patching it in place of Sitecore.Links.UrlBuilders.ItemUrlBuilder, Sitecore.Kernel.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Foundation.SitecoreExtensions.Extensions;
using Sitecore.Globalization;
using Sitecore.IO;
using Sitecore.Links;
using Sitecore.Links.UrlBuilders;
using Sitecore.Sites;
using Sitecore.Web;

namespace Sitecore.Foundation.SitecoreExtensions
{
    public class CustomItemUrlBuilder : ItemUrlBuilder
    {
        private ItemUrlBuilderOptions Options;

        #region Constructors

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

        #endregion

        #region Methods

        /// <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)
        {
            if (Context.Site == null || Context.Site.Name == "shell")
            {
                return base.Build(item, options);
            }

            Options = options;
            var url = BuildItemUrl(item);

            if (Options.LowercaseUrls != null && Options.LowercaseUrls.Value)
                url = url.ToLowerInvariant();

            return url;
        }

        #endregion

        public string BuildItemUrl(Item item)
        {
            Assert.ArgumentNotNull((object)item, nameof(item));
            SiteInfo siteInfo = item.GetSite();
            string itemPathElement = GetItemPathElement(item, siteInfo);
            if (itemPathElement.Length == 0)
                return string.Empty;
            string serverUrlElement = GetServerUrlElement(siteInfo);
            if (siteInfo != null)
                return BuildItemUrl(serverUrlElement, itemPathElement, siteInfo.VirtualFolder);
            return BuildItemUrl(serverUrlElement, itemPathElement);
        }

        public string BuildItemUrl(
            string serverUrl,
            string itemPath,
            string virtualFolder)
        {
            Assert.ArgumentNotNull((object)serverUrl, nameof(serverUrl));
            Assert.ArgumentNotNull((object)itemPath, nameof(itemPath));
            Assert.ArgumentNotNull((object)virtualFolder, nameof(virtualFolder));
            string part1 = string.Empty;
            if (!serverUrl.EndsWith("/", StringComparison.InvariantCulture))
                part1 += "/";
            string path = FileUtil.MakePath(part1, itemPath, '/');
            if (path.Length > 1)
                path = StringUtil.RemovePostfix('/', path);
            if (Options.EncodeNames != null && Options.EncodeNames.Value)
                path = MainUtil.EncodePath(path, '/');

            return serverUrl + path;
        }

        public string BuildItemUrl(string serverUrl, string itemPath)
        {
            Assert.ArgumentNotNull((object)serverUrl, nameof(serverUrl));
            Assert.ArgumentNotNull((object)itemPath, nameof(itemPath));
            return BuildItemUrl(serverUrl, itemPath, "/");
        }

        public bool IsDescendantOrSelfOf(string itemPath, string rootPath)
        {
            Assert.ArgumentNotNull((object)itemPath, nameof(itemPath));
            Assert.ArgumentNotNull((object)rootPath, nameof(rootPath));
            return !string.IsNullOrEmpty(rootPath) && itemPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase) && (string.Compare(itemPath, rootPath, StringComparison.OrdinalIgnoreCase) == 0 || itemPath.StartsWith(rootPath + "/", StringComparison.OrdinalIgnoreCase));
        }

        public string GetRootPath(
            SiteInfo site,
            Language language,
            Database database,
            bool useStartItem = false)
        {
            return useStartItem ? FileUtil.MakePath(site.RootPath, site.StartItem) : site.RootPath;
        }

        public string GetItemPathElement(Item item, SiteInfo site)
        {
            ItemPathType type = ItemPathType.Name;
            string str = item.Paths.GetPath(type);
            if (site == null)
                return str;
            string rootPath1 = GetRootPath(site, item.Language, item.Database, true).Trim().TrimEnd('/');
            if (IsDescendantOrSelfOf(str, rootPath1))
            {
                str = str.Substring(rootPath1.Length);
            }
            else
            {
                string rootPath2 = GetRootPath(site, item.Language, item.Database, true).Trim().TrimEnd('/');
                if (IsDescendantOrSelfOf(str, rootPath2))
                    str = str.Substring(rootPath2.Length);
            }
            string virtualFolder = site.VirtualFolder;
            if (virtualFolder.Length > 0 && !str.StartsWith(virtualFolder, StringComparison.OrdinalIgnoreCase))
                str = FileUtil.MakePath(virtualFolder, str);
            return str;
        }

        public string GetServerUrlElement(SiteInfo siteInfo, bool alwaysIncludeServerUrl = false)
        {
            SiteContext site = Context.Site;
            string str1 = site != null ? site.Name : string.Empty;
            string hostName = Sitecore.Web.WebUtil.GetHostName();
            string str2 = alwaysIncludeServerUrl ? WebUtil.GetServerUrl() : string.Empty;
            if (siteInfo == null)
                return str2;
            string str3;
            if (string.IsNullOrEmpty(siteInfo.HostName) || string.IsNullOrEmpty(hostName) || !siteInfo.Matches(hostName))
                str3 = StringUtil.GetString(new string[2]
                {
                    siteInfo.TargetHostName, siteInfo.HostName
                });
            else
                str3 = hostName;
            string str4 = str3;
            string str5 = StringUtil.GetString(new string[2]
            {
        siteInfo.Scheme,
        WebUtil.GetScheme()
            });
            int defaultValue = MainUtil.GetInt(siteInfo.Port, WebUtil.GetPort());
            int port = WebUtil.GetPort();
            int num = MainUtil.GetInt(siteInfo.ExternalPort, defaultValue);
            if (num != defaultValue)
            {
                if (alwaysIncludeServerUrl)
                    str2 = num == 80 ? string.Format("{0}://{1}", (object)str5, hostName) : string.Format("{0}://{1}:{2}", (object)str5, hostName, (object)num);
                defaultValue = num;
            }
            if (alwaysIncludeServerUrl && siteInfo.Name.Equals(str1, StringComparison.OrdinalIgnoreCase) && (string.IsNullOrEmpty(hostName) || hostName.Equals(str4, StringComparison.OrdinalIgnoreCase)) || (string.IsNullOrEmpty(str4) || str4.IndexOf('*') >= 0))
                return str2;
            string scheme = WebUtil.GetScheme();
            StringComparison comparisonType = StringComparison.OrdinalIgnoreCase;
            if (str4.Equals(hostName, comparisonType) && defaultValue == port && str5.Equals(scheme, comparisonType))
                return str2;
            string str6 = str5 + "://" + str4;
            if (defaultValue > 0 && defaultValue != 80)
                str6 = str6 + ":" + (object)defaultValue;
            return str6;
        }

    }

}

Patch config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>

    <links>
      <itemUrlBuilder type="Sitecore.Links.UrlBuilders.ItemUrlBuilder, Sitecore.Kernel">
        <patch:delete />
      </itemUrlBuilder>

      <itemUrlBuilder type="Sitecore.Foundation.SitecoreExtensions.CustomItemUrlBuilder, Sitecore.Foundation.SitecoreExtensions">
        <param desc="defaultOptions" type="Sitecore.Links.UrlBuilders.DefaultItemUrlBuilderOptions, Sitecore.Kernel">
          <alwaysIncludeServerUrl>true</alwaysIncludeServerUrl>
          <languageEmbedding>always</languageEmbedding>
          <languageLocation>filePath</languageLocation>
          <lowercaseUrls>true</lowercaseUrls>
          <encodeNames>true</encodeNames>
          <useDisplayName>false</useDisplayName>
          <addAspxExtension>false</addAspxExtension>
          <siteResolving>true</siteResolving>
          <shortenUrls>true</shortenUrls>
        </param>
      </itemUrlBuilder>
    </links>
  </sitecore>
</configuration>
1
  • Sorry, little confused on what was the actual fix.. did you apply hotfix as in the accepted answer? or overriding itemurlbuilder as in your/this answer?
    – nari447
    Commented Sep 1, 2021 at 18:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.