Sitecore cross-site and cross-language links resolved in 6.4.1 release 120113

In my previous post, Sitecore links in multisite solutions – SiteResolving, I described a limitation to the LinkManager when having multi-site and multi-language solutions.

The limitations occur in solutions where you have seperate domain names for languages AND having multiple sites at the same time. In this case it’s very hard to get the optimal links when linking between sites.

But from Sitecore 6.4.1 rev. 120113 (6.4.1 Update-6) Sitecore has introduced 2 new settings, allowing you to have more control on how links between sites and languages are created:

<setting name="Rendering.SiteResolvingMatchCurrentSite" value="true" />

If Rendering.SiteResolvingMatchCurrentSite is true, the link provider will check if the target item is located under the start item for the current site before it tries to find a match in the full list of site definitions. This ensures that when the target item can be resolved using the current site, the target link will not change to a different site/hostname.

<setting name="Rendering.SiteResolvingMatchCurrentLanguage" value="true" />

If Rendering.SiteResolvingMatchCurrentLanguage is true, the link provider will take the language attribute of the site definitions into consideration when resolving which site/hostname to use when rendering a cross-site link. This setting is enabled by default.

Thanks to Sitecore support for clarifying this.

 

Sitecore links in multisite solutions – SiteResolving

One of the key features in Sitecore is that you can have multiple sites in the same installation. This is not just about saving money on licenses. The feature allows you to, for example, operate one main website and a multitude of subsites each having their own URL. Because they share the same Sitecore, you can share resources like the media library or templates, layouts, users etc.

Another key benefit is that it allows you to create internal links between sites. These links are maintained by the LinkManager so links are updated automatically. And when they are rendered they will still point to the correct host.

Imagine this simplified scenario where you have 2 websites called SITE1 and SITE2. In Sitecore you create the 2 websites:

/sitecore/

/content/

/site1/

/home/

/subitems…

/site2/

/home/

/subitems…

To give each website its own hostname you need to set up 2 new sites in the web.config:

<site name="website_1" hostName="www.site1.com" language="en" cacheHtml="false" virtualFolder="/" physicalFolder="/" rootPath="/sitecore/content/site1" startItem="/home" database="web" domain="extranet" allowDebug="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="website_2" hostName="www.site2.com" language="en" cacheHtml="false" virtualFolder="/" physicalFolder="/" rootPath="/sitecore/content/site2" startItem="/home" database="web" domain="extranet" allowDebug="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

Now when accessing www.site1.com you are given the home page at /sitecore/content/site1/home, and www.site2.com returns the home page at /sitecore/content/site2/home.

Imagine you create a link on the page at /site1/home/subitem to /site2/home/subitem. Internally in Sitecore this is stored as a GUID. But when rendered, the LinkManager ensures that the link is rendered as www.site2.com/subitem.aspx.

It does so because in the web.config, SiteResolving is enabled by default:

<setting name="Rendering.SiteResolving" value="true" />

When SiteResolving is true, the LinkManager will resolve the correct site from the <sites> section in web.config. It makes a best match and finds the site that the page belongs to, and resolves the hostname and URL based on the context of the site it found.

If you disable the SiteResoving the LinkManager stays in the current context. The link would then have been www.site1.com/sitecore/content/site2/home/subitem.aspx.

LIMITATIONS IN THE SITERESOLVER

UPDATE: Please note that these limitations have been adressed from Sitecore 6.4.1 Update 6. Read more about it here.

There are limitations to siteresolving. Imagine you have a setup where you not only have multiple sites, but also multiple languages per site. And that each of the languages have their own host.

So if your SITE1 has 2 languages (english and danish) you have www.site1.com and www.site1.dk. And SITE2 also has 2 languages, www.site2.com and www.site2.dk.

If you enable SiteResolving, all links linking to the same website (all links from SITE1 to SITE2) will be resolved as links to the first site content found in the web.config.

What does this mean? It means that if you in the web.config have the following order of sites:

All links from www.site1.dk will point to www.site1.com/dk/subitem.aspx. The SiteResolver does this because the algorithm resolves URLS as the first path that is the shortest is the one used. And since www.site1.com/dk/subitem.aspx has just as short a path as www.site1.dk/dk/subitem.aspx it’s a match, and voila, problem solved.

MORE READING

Working with multiple sites in Sitecore

Sitecore is a multisite system, meaning that you can administer multiple websites in the same Sitecore, and that these websites can share common data (like users, templates, masters, items, etc.).

Each website in Sitecore will run in its own context, and this context is managed from the web.config. The context is set up by defining a Site. Each context has its own site definition, and it includes a “shell” site, defining the Sitecore editor context (the shell), “modules_shell” and “modules_website”, defining the context for modules running in either the Sitecore Shell or the default website.

The site called “website” defines a default web context:

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

The attributes to pay attention to (regarding multisite solutions) are the following:

  • rootPath: The path to the root of the website. This is not the path to the first page of the website, but simply defines where Sitecore will look for items.
  • startItem: The path relative to the rootPath where the first page (home page) of the website is found.

When defining multiple websites in your Sitecore you take advantage of these 2 attributes, along with a new attribute called “hostName“, which I will explain later. First I will define my multisite structure:

In my multisite solution I will have a shared “Settings” folder, and 2 websites, each with their own “Settings” folder. This is my imaginary Sitecore tree:

/sitecore/
     /content/
          /website1/
               /home/
                    /subitems…/
               /settings/
          /website2/
               /home/
                    /subitems…/
               /settings/
          /settings/

In my web.config I will need to set up 2 new “website” sites. I add these above the existing “website” site (or I can delete the old “website” and replace it with my 2 new ones). Never add your website sites to the top of the list, as Sitecore finds the current context based on first match rather than best match:

<site hostName="site1.mysite.com" name="website_1" language="en" rootPath="/sitecore/content/website1" startItem="/home" cacheHtml="false" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site hostName="site2.mysite.com" name="website_2" language="en" rootPath="/sitecore/content/website2" startItem="/home" cacheHtml="false" virtualFolder="/" physicalFolder="/" database="web" domain="extranet" allowDebug="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />

Please notice the hostName attribute. This attribute defines on which URL your site will be hit, and which context sitecore will create when calling this url. The hostName is important in a multisite environment as this is the way Sitecore will determine which item to render.

In my example, the url http://site1.mysite.com/ hits the item at /sitecore/content/website1/home, because I have defined that the rootPath and startItem for the hostName site1.mysite.com is on that path.

Sitecore provides functions to get the root path and start item for the current context. So when working with multisite solutions you should always get a path relative to the Sitecore.Context.Site settings:

Response.Write("Current site: " + Sitecore.Context.Site.Name + "<br/>");
Response.Write("Database: " + Sitecore.Context.Site.Database.Name + "<br/>");
Response.Write("Content start path: " + Sitecore.Context.Site.ContentStartPath + "<br/>");
Response.Write("Home node: " + Sitecore.Context.Site.StartPath + "<br/>");
Response.Write("Language: " + Sitecore.Context.Language.GetDisplayName() + "<br/>");

The code above would give me the following output for the site at site1.mysite.com:

Current site: website_1
Database: web
Content start path: /sitecore/content/website1
Home node: /sitecore/content/website1/home
Language: English : English

So, in order to get the “Settings” folder for the site at hostname site1.mysite.com, I should write the following code:

Sitecore.Data.Items.Item settingsFolder = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.ContentStartPath + "/settings");

In Sitecore 6, the Sitecore LinkManager uses the context to render an URL, as internal links in the Rich Text Editor is stored internally as a GUID, but when rendered, the GUID is replaced with a path relative to the current context of the GUID. So in my case: if a page below website1 links to a page below website2, the LinkManager uses the context of website2 to generate the relative path, even when the calling page is in the context of website1.
The LinkManager uses this little partytrick to allow you to cross-reference different websites and still get the right URL.

LinkManager – Working with URL’s in Sitecore 6

Have you noticed that a page in Sitecore 6 can be accessed through several URL’s? This is an example of the products page on the Sitecore website. The following URL’s are all valid and points to the same page and the same language:

http://www.sitecore.net/en/Products.aspx
http://www.sitecore.net/Products.aspx
http://www.sitecore.net/Products.aspx?sc_lang=en
http://www.sitecore.net/Products
http://www.sitecore.net/?sc_itemid={5D8489BF-419B-4336-B9DA-CA704C682B51}

This can confuse statistical tools like Google Analytics, as the statistical data is collected per URL basis. Sitecore’s own Online Marketing Suite does not have this problem, but all tools that collects data from Javascripts inserted on the page will get confused. It is hard to get a total number of hits for one page, as you have to collect the statistics scattered over many URL’s.

So how can you limit the number of URL’s to one page? The solution lies within the linkManager setting in web.config:

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

These are the default settings in the web.config, which allows the widest range of available URL’s per page. But this is not always a good thing. The following settings caught my eye:

languageEmbedding="asNeeded"

This setting allows Sitecore to add the language as a part of the URL “as needed”. In practice this means “as the wind blows” because it is very hard to find a pattern for when the language is added and not. My recommentation is to use “always” for multiple language sites, and “never” for single language sites.

Another cool setting is this:

addAspxExtension="true"

Setting this to “false” removes the .aspx extension for all URL’s. In order for this to work you will need to use IIS7 and map all incomming requests to ASP.NET, or write your own 404 error handler that redirects any incomming request to the correct .aspx page.

Sitecore.Links.LinkManager and the context

A new feature in Sitecore 6 is the LinkManager. Previously, in Sitecore 5, in order to get a friendly url (an URL with the .aspx extension) you would write the following:

Item item;
string path = item.Paths.GetFriendlyUrl(false);

In Sitecore 6, the GetFriendlyUrl() is deprecated. Instead we are encouraged to use the Sitecore.Links.LinkManager. The same piece of code would look like this:

Item item;
string path = Sitecore.Links.LinkManager.GetItemUrl(item);

The LinkManager provides us with a lot of features that the GetFriendlyUrl() did not. But it also introduces some pitfalls. The LinkManager runs in a context that is not necessarily the same as the Item you are getting the link from, nor the context you wish to run in. Let me explain. Imagine you write a Sitecore shell extension that returns the path to an item. The Item is grabbed from the web database:

Sitecore.Data.Database database = Sitecore.Data.Database.GetDatabase("web");
Sitecore.Data.Items.Item item = database.GetItem("/sitecore/content/home");
string path = item.Paths.FullPath;

The path to the item is “/sitecore/content/home”.

Now, lets get the item’s URL:

string path = Sitecore.Links.LinkManager.GetItemUrl(item)

This returns the item URL for the current Site as defined in the LinkManager.GetDefaultUrlOptions().Site. If my code runs in the modules_shell site, the url is /en/sitecore modules/shell.aspx. Is this really the url of my item? Yes, seen from the modules_shell site it is, but not as seen from the website site.

So my Item used the website site, but my LinkManager uses the modules_shell site. The URL i wanted is the one from the website site, but the URL i requested is from the modules_shell site.

What can I do about this? The URL is constructed by prefixing the current site’s virtual path (which for the modules_shell site is /sitecore modules/shell) to the actual path. The easy solution would be to remove the virtual path from the URL:

string virtualFolder = LinkManager.GetDefaultUrlOptions().Site.VirtualFolder.TrimEnd('/');
Response.Write(LinkManager.GetItemUrl(item).Replace(virtualFolder, ""));

This produces the URL /en.aspx, which is correct for my website site, since this site has no virtual path.

Another, and probably better, solution would be to change the context before getting the link:

string oldSiteName = Sitecore.Context.GetSiteName();
Sitecore.Context.SetActiveSite("website");
Response.Write(LinkManager.GetItemUrl(item));
Sitecore.Context.SetActiveSite(oldSiteName);

This produces the URL “/”, which is correct for the home page of my website.

Follow

Get every new post delivered to your Inbox.

Join 92 other followers