Sitecore LinkManager – Multi-site configuration

Urls for sitecore items are generated by the static class Sitecore.Links.LinkManager. In normal use you wont have any issues, but things can get trickier when you step out of a simple scenario.
This is part 1 of 2 posts on detailed Sitecore.Linkmanager configuration.

Requirements like absolute urls, hostnames, schemes, requesting a url for an item in a different site or a site within a site, or even requesting a url for an item while not in a httprequest context. These are the areas where you might find yourself jumping through crazy hoops or hacking a solution where quite often just a configuration change and some understanding is all that it required.

Links are governed by two areas of web.config, the LinkManager settings which define how urls are formed on a system-wide level, and the site definition(s).

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

LinkManager Defaults

So long as you can run an integrated mode application pool in IIS for your sitecore site, I would suggest the following changes to LinkManager defaults:

  • addAspxExtensions=”false”
  • languageEmbedding=”never” (unless you have multiple languages within one domain in which case use “always”
  • alwaysIncludeServerUrl=”true” (only because we’re dealing with multiple-domains and therefor require absolute urls)
    This would then give you a url like /folder/page instead of /folder/page.aspx or potentially /en/folder/page.aspx

Example multi-site structure

Below is a multi-homed scenario with two main site structures – “home” containing another subsite “sub-home”, and a separate site “home2” :

tree

For this to work locally I added the relevant host file entries:

127.0.0.1 641website.local
127.0.0.1 641website2.local
127.0.0.1 641subsite.local

Added the hostname bindings in IIS:

bindings

Added the two new site definitions in web.config:

<site name="website" hostName="641website.local" rootPath="/sitecore/content/" startItem="/home" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="subsite" hostName="641subsite.local" rootPath="/sitecore/content/" startItem="/Home/apple/subhome" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website2" hostName="641website2.local" rootPath="/sitecore/content/" startItem="/home2" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

At this point I have three correctly resolving sites under different hostnames:

This works for visitor coming directly in on the hostnames provided. The sample page rendering just creates links to child items, however, there’s already a problem with the link to the subsite home item from its parent node “apple” (refer back to the tree structure image if this doesn’t make sense) :

The link to subhome is being rendered as:

http://641website.local/apple/subhome

instead of…

http://subsite.local

To resolve this we need to change the order that the sites are ordered in web.config. This is because sites are evaluated from top to bottom, with the first match being used as the item’s site (this is “site resolution”) Because subsite is within website,  the item /sitecore/content/Home/apple/subhome is being resolved as belonging to the “website” site definition and no further evaluation takes place. LinkManager is then building the url based on the hostname defined for that site and the relative path to the item. By changing the order so that “subsite” appears above “website” any item that exists in that part of the structure will resolve to “subsite”.

The new web.config now looks like:

<site name="subsite" hostName="641subsite.local" rootPath="/sitecore/content/" startItem="/Home/apple/subhome" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website" hostName="641website.local" rootPath="/sitecore/content/" startItem="/home" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website2" hostName="641website2.local" rootPath="/sitecore/content/" startItem="/home2" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

The page now renders the link to the subhome item correctly:

In order for all this to work, we must have :

  • hostName attribute defined for each site (in order to build the absolute url for an item in a different site)
  • The sites ordered so that the deepest start paths come first, and therefore match prior to the wider-catchment of the main site(s)

Inter-site links

The simple rendering in the sample page just lists children of the context node. In order to spice things up I want a full mix and match of cross links to and from all three sites.

I added three link fields to the template to enable linking to any sitecore item:

Template link fields

I’ve changed my simple rendering to that in addition to listing child nodes, it renders links to the selected LinkItem1, 2 and 3:

<xsl:template match="*">
<ul>
<xsl:for-each select="./item">
<li><sc:link /></li>
</xsl:for-each>
</ul>

<h2>Links</h2>
<br />
Link1: <xsl:value-of select="sc:path(sc:item(sc:fld('LinkItem1',.),.))"/>
<br />
<br />
Link2: <xsl:value-of select="sc:path(sc:item(sc:fld('LinkItem2',.),.))"/>
<br />
<br />
Link3: <xsl:value-of select="sc:path(sc:item(sc:fld('LinkItem3',.),.))"/>
<br />
<br />

</xsl:template>

I defined the the same links for each of three site homepages – a link to a page within website, subsite and website2

crosslinksdata

Which gives these links:

crosslinkrenderedproblem

There’s a problem generating Link3 at the bottom – it’s resolving to “website” instead of “website2” and giving us a url within the wrong domain. This should be:

http://641website2.local/dog

Back to web.config. –  we have two sites defined at the same level within sitecore, so they both have the same rootPath of /sitecore/content/ – they just have different startItems.

We can’t just move website2 above website because then it will just move the problem to website2? Fortunately, this isn’t true. Sites resolution here is dictated by the combined rootPath + startItem path  i.e. the startItem full path. Importantly, site resolving works on a path string basis, not a node/folder basis.

The third link item has a sitecore path of:

/sitecore/content/Home2/dog

In site resolving this is compared to the combined paths of each site definition, from top to bottom . So currently we have:

/sitecore/content/Home/apple/subhome (“subsite”)

/sitecore/content/Home (“website”)

/sitecore/content/Home2 (“website2”)

The item path fits within the “website” path and therefore the path is generated in the context of the “website” site. If we move “website” to bottom of our list of sites, we resolve the immediate problem:

<site name="subsite" hostName="641subsite.local" rootPath="/sitecore/content/" startItem="/Home/apple/subhome" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website2" hostName="641website2.local" rootPath="/sitecore/content/" startItem="/home2" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website" hostName="641website.local" rootPath="/sitecore/content/" startItem="/home" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

On our website homepage this now renders the third link correctly within the 641website2.local domain:

crosslinkrenderedfixed

Have we just moved the problem to the other site? No – when viewing the website2 homepage with the same links defined for linkItem1, 2 and 3 we still get the correct links:

crosslinkrenderedfixedsite2

Links from within a website2 path still resolve back to website. This still works because the path of the start item for website2 is one character longer than for website, so item paths within “website2” will never incorrectly resolve to “website”. It important to list the site with the longest combined path (aka the start item full path) first.

Multiple hostnames for one site

Stirring things up again – I want to configure our main “website” site to run under an additional domain – 641website.com as well as the current 641website.local. If we add this to the hosts file and host bindings in IIS (but not the <sites> definition yet) and visit our new domain url –  641website.com we just get an “not found” error page.

Because our three sites all have expicit hostnames defined, the request for 641website.com is not matching any of them and so we get a page not found error.

If we now add 641website.com to the site definition for the “website” site, the page displays correctly and our first link (within the “website” site definition) also picks up correct – contextual – domain:

641websitecom

Web.config (pipe-seperated multiple hostname for “website”):

<site name="subsite" hostName="641subsite.local" rootPath="/sitecore/content/" startItem="/Home/apple/subhome" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website2" hostName="641website2.local" rootPath="/sitecore/content/" startItem="/home2" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website" hostName="641website.local|641website.com" rootPath="/sitecore/content/" startItem="/home" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

With this multiple domain site configured, we introduced another problem. Pages within other sites no longer resolve links back to items within “website” correctly:

subsitemutihostnameproblem

Because website has more than one hostname, LinkManager can no longer determine which domain to use when creating an absolute link to this site, instead it defaults to the context domain of the page request. This also happens if we use a wildcard mapping as the hostname such as:

<site name="website" hostName="641website.*" rootPath="/sitecore/content/" startItem="/home" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

This this config my site can now responds under all these urls: (so long as they resolve or are added to the hosts file):

http://641website.local

http://641website.com

http://641website.co.uk

http://641website.org

With this setup I get the same result, links to items within “website” are not being picked up and we appear to have run out of settings to tweak. Fortunately there are some settings to deal with this, they are just not documented…

The targetHostName attribute

This setting only plays a part when:

  • We’re not in the context of this site
  • The site definition has multiple host names or uses a (*) wildcard
  • We linking from another sitecore site back to an item within this site and therefore…
  • Require absolute urls that include the domain

It simply defines the default host name for a site that have more than one hostname.

Added to the site definition:

<site name="subsite" hostName="641subsite.local" rootPath="/sitecore/content/" startItem="/Home/apple/subhome" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website2" hostName="641website2.local" rootPath="/sitecore/content/" startItem="/home2" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website" hostName="641website.*" rootPath="/sitecore/content/" startItem="/home" targetHostName="641website.local" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

Returning to the problematic “subsite” page which was not resolving to “website”,  we now get our nominated targetHostname being used for links:

subsitefixedagain

This now works perfectly for all links in all sites. You can actually specify any targethostName you like, even ones that don’t belong to the site. I can’t really see a use for doing this, but nonetheless… it works:

<site name="website" hostName="641website.*" targetHostName="somethingcompletelydifferent.local" rootPath="/sitecore/content/" startItem="/home" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

somethingcompletelydifferent

It works, but note that targetHostName values are not included in hostname resolution, visting this url will just not work unless also it is also added to the hostnames attribute.

I don’t know why the targetHostName attribute is missing from the documentation and also web.config comments – I’ve seen several messy work-arounds to this hostname issue which this setting can otherwise handle cleanly.

I hope this post is useful to those implementing multi-site setups.

4 thoughts on “Sitecore LinkManager – Multi-site configuration

  1. Pingback: Cross-site linking in Sitecore | True Clarity Developer Blog

  2. Phani

    Absolutely brilliant piece of work here. A must have, best practice & How-To guide to solve most of multisite-multidomain url generation in sitecore. I struggled more than months fixing one after the other. Unfortunately this post never surfaced in toplist of my searches earlier 🙂

    Reply
  3. Andrei Avram

    Hello,

    Did you try this with a wildcard placed at the beginning of the hostName attribute? Everything works fine for me if the wildcard is at the end, but not when the wildcard is at the beginning.

    Could you please help?

    Reply

Leave a Reply to Andrei Avram Cancel reply

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