Using cross site links.. aka dynamic links (part 2)

Recently, it came to my attention that dynamic links were STILL not working properly on our websites! So I cracked open the Sitecore.Kernel.dll again to investigate…

How it works now…

The LinkBuilder.ResolveTargetSite method is responsible for determining what host name to use if the requested item is outside the scope of the context site. To do this, it builds a “SiteResolvingTable” which consists of Dictionary object where the key is:

Site.RootPath + Site.StartItem

RootPath and StartItem come from the attribute values for each site as defined in your <sites> web.config section. The next step is to take the fullpath to the requested item and recursively chop off the last part of the path and try to match it up to one of the sites in the SiteResolvingTable. Like this:

Requested item: /sitecore/content/home/mysite/path1/path2/path3/myitem

  1. No match? Ok try: /sitecore/content/home/mysite/path1/path2/path3
  2. No match? Ok try: /sitecore/content/home/mysite/path1/path2
  3. No match? Ok try: /sitecore/content/home/mysite/path1
  4. etc…

When/if it finds a match, it takes the “TargetHostName” attribute (or “HostName” if there are no ‘*’ or ‘|’ characters in it) and builds the URL: http://TargetHostName/{ItemPath}.

The problem…

From the web.config documentation:

/*
  rootPath: The path to the root item of the site.
            The item path specified in the URL will be appended to this value to find the item.

  startItem: The path to the start item of the site.
             This is used as the default item path if no path is specified in the URL.
             It will be combined with rootPath to find the default item of the site.
*/

So why is StartItem included in the SiteResolvingTable key? It makes no sense to me… it must be a bug. The key should just be RootPath. No problem! We configuring “/” as the StartItem. The problem here is that all keys in the SiteResolvingTable will then end with “/”. The ItemPath logic above chops off the trailing slashes when recursing up the item path and so we’d never resolve any of our sites.

UPDATE: I contacted Sitecore support about this issue and they are aware of the issue and have it registered as a bug. Read more about it here. Still, if their suggested work-arounds do not work for you, read on…

UPDATE 2: Sitecore support has informed me that  “The issue is fixed in Sitecore 6.4.1 rev.110928. You can find the following entry in the Release Notes.”

In the end, we decided to override Sitecore’s logic to build the SiteResolvingTable. I hate overriding things like this because eventually the overridden code will change (with new versions) and you start missing out on bug fixes. So hopefully they fix it and I can remove this. Here’s the custom LinkProvider we built:

    ///
<summary> /// Extensions for the Sitecore.Links.LinkProvider class
 /// </summary>
    public class LinkProvider : Sitecore.Links.LinkProvider
    {
        private delegate string KeyGetter(SiteInfo siteInfo);

        public override string GetItemUrl(Item item, UrlOptions options)
        {
            string itemUrl;

            options.SiteResolving = Sitecore.Configuration.Settings.Rendering.SiteResolving;

            // Use our custom LinkBuilder that fixes the SiteResolvingTable...
            var builder = new LinkBuilder(options);
            itemUrl = builder.GetItemUrl(item);

            return itemUrl;
        }

        private static string GetTargetHostName(SiteInfo siteInfo)
        {
            if (!siteInfo.TargetHostName.IsNullOrEmpty())
                return siteInfo.TargetHostName;

            string hostName = siteInfo.HostName;
            if (hostName.IndexOfAny(new char[] { '*', '|' }) < 0)
                return hostName;

            return string.Empty;
        }

        public new class LinkBuilder : Sitecore.Links.LinkProvider.LinkBuilder
        {
            // Fields
            private readonly UrlOptions _options;
            private static Dictionary _siteResolvingTable;
            private static List _sites;
            private static readonly object _syncRoot;

            static LinkBuilder() { _syncRoot = new object(); }
            public LinkBuilder(UrlOptions options) : base(options) { _options = options; }

            protected override SiteInfo ResolveTargetSite(Item item)
            {
                SiteContext site = Context.Site;
                SiteContext context2 = this._options.Site ?? site;
                SiteInfo info = (context2 != null) ? context2.SiteInfo : null;
                if (!this._options.SiteResolving || (item.Database.Name == "core"))
                {
                    return info;
                }
                if ((this._options.Site != null) && ((site == null) || (this._options.Site.Name != site.Name)))
                {
                    return info;
                }
                if ((context2 != null) && item.Paths.FullPath.StartsWith(context2.StartPath))
                {
                    return info;
                }
                Dictionary siteResolvingTable = GetSiteResolvingTable();
                string key = item.Paths.FullPath.ToLower();
            Label_00AE:
                if (siteResolvingTable.ContainsKey(key))
                {
                    return siteResolvingTable[key];
                }
                int length = key.LastIndexOf("/");
                if (length > 1)
                {
                    key = key.Substring(0, length);
                    goto Label_00AE;
                }
                return info;
            }

            private static Dictionary GetSiteResolvingTable()
            {
                List sites = SiteContextFactory.Sites;
                if (!object.ReferenceEquals(_sites, sites))
                {
                    lock (_syncRoot)
                    {
                        if (!object.ReferenceEquals(_sites, sites))
                        {
                            _sites = sites;
                            _siteResolvingTable = null;
                        }
                    }
                }
                if (_siteResolvingTable == null)
                {
                    lock (_syncRoot)
                    {
                        if (_siteResolvingTable == null)
                        {
                            _siteResolvingTable = BuildSiteResolvingTable(_sites);
                        }
                    }
                }
                return _siteResolvingTable;
            }

            private static Dictionary BuildSiteResolvingTable(IEnumerable sites)
            {
                Dictionary dictionary = new Dictionary();
                List list = new List();
                //KeyGetter[] getterArray = new KeyGetter[] { info => FileUtil.MakePath(info.RootPath, info.StartItem).ToLower() };
                KeyGetter[] getterArray = new KeyGetter[] { info => info.RootPath.ToLower() };
                foreach (KeyGetter getter in getterArray)
                {
                    foreach (SiteInfo info in sites)
                    {
                        if (!string.IsNullOrEmpty(info.HostName) && string.IsNullOrEmpty(GetTargetHostName(info)))
                        {
                            Sitecore.Diagnostics.Log.Warn("LinkBuilder. Site '{0}' should have defined 'targethostname' property in order to take participation in site resolving process.".FormatWith(new object[] { info.Name }), typeof(LinkProvider.LinkBuilder));
                        }
                        //else if ((!string.IsNullOrEmpty(GetTargetHostName(info)) && !string.IsNullOrEmpty(info.RootPath)) && !string.IsNullOrEmpty(info.StartItem))
                        else if (!string.IsNullOrEmpty(GetTargetHostName(info)) && !string.IsNullOrEmpty(info.RootPath))
                        {
                            string key = getter(info);
                            if (!list.Exists(new Predicate(key.StartsWith)))
                            {
                                dictionary.Add(key, info);
                                list.Add(key);
                            }
                        }
                    }
                }

                return dictionary;
            }
        }
    }
About these ads

About Paul Martin

I enjoy rock climbing, playing guitar, writing code...
This entry was posted in Dynamic Links, Sitecore. Bookmark the permalink.

One Response to Using cross site links.. aka dynamic links (part 2)

  1. Pingback: Preventing Cross-Site Links in Sitecore | TCSC Blog

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s