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.

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