codeflood logo

Techniques for Sharing Content

This post is based on the materials I recently presented to the Sitecore Australia & New Zealand User Group.

Sitecore allows the sharing of content due to the separation of content from presentation. In fact, you can consider the presentation to itself be content as the presentation definition for an item is stored in a field; the __renderings field, whether the data for that field comes from the item itself (bad) or from template standard values (good).

Sharing a single piece of content between different sections of the same site is pretty straight forward. Th easiest way to do this would be to use proxy items in Sitecore. Proxy items are like symbolic links (for those that have used "real" operating systems) or shortcuts (for those that have used Windows :) Sorry, Windows has symbolic links now too. ). The item exists in a separate location but appears in multiple other locations. To use proxy items I need to ensure they are enabled for the database I'm working in which would be master when content editing.

To enable proxy items for a database, open the web.config file, find the database definition element and change the inner text of the proxiesEnabled element to true.

<database id="master" singleInstance="true" type="Sitecore.Data.Database, Sitecore.Kernel">
  <proxiesEnabled>true</proxiesEnabled>
  ...
</database>

We don't need to enable proxies in the web database. If the target database doesn't support proxy items then published proxy items end up as real items in the target database. This is because proxy items behave like real items, even to the point of publishing. This also helps with performance because the item can be resolved by the data provider rather than the proxy data provider which I believe is accessed after the data provider.

The biggest problem we face with sharing content using proxies is that all fields are shared, including the __renderings field. So the shared item will always have the same presentation.

A potential advantage to using proxy items for sharing content is that depending on how your site is structured, the author may have more control over which items are proxied into a location rather than just taking all items in a particular location. It depends on how you view this though as some people will actually want all items proxied. It will depend on the circumstances of the site you're building.

For the following examples, my site has an article listing item to list the proxied shared articles. The presentation for the shared article comes from the presentation definition of the shared article itself. Below I provide extracts from the renderings used on the list and article items.

I'll include the list rendering xsl:template and item display xsl:template here for completenes. I'm sure we all already know how to enumerate child items. But this code snippet does again illustrate that proxy items behave like normal items.

List rendering template:

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

Article rendering template:

<h1><sc:text field="title"/></h1>
<p>
  <sc:html field="text"/>
</p>

So what if I want to be able to change the presentation of the shared item depending on which site I'm currently in? I'll have to somehow override the __renderings field on a site by site basis. To do this I'll instead use a "view" item in each site which is used to display the shared article.

The rendering I use on this view item allows me to pick and choose the fields I pull out of the shared item. In this case, I'm pulling the normal content fields in. The presentation is defined on the view item itself. This allows me to define the presentation to be used for the shared articles view item by view item, site by site and even location by location (in the content tree).

The article list in this case doesn't enumerate the children of the current item, but instead enumerates the shared articles. We'll use the ID of the article as a query string to the view item so it knows which article it needs to render.

View item list rendering template:

<xsl:variable name="shared" select="sc:item('/sitecore/
  content/shared articles',.)"/>

<xsl:template match="*" mode="main">
  <xsl:variable name="link"
    select="sc:path(child::item[@key='detail'])"/>
  <ul>
    <xsl:for-each select="$shared/child::item">
      <li>
        <a href="{$link}?id={@id}">
          <xsl:value-of select="@name"/>
        </a>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

Note how I grab a reference to the shared articles in the variable "shared" and then enumerate it's children. To gain control over whether a shared article should be included in my site I could add another field to the shared article which would determine which sites to share the article into, then check for that condition in my list rendering above.

View item article rendering:

<xsl:template match="*" mode="main">
  <xsl:variable name="item" select="sc:item(sc:qs('id'),.)"/>
  <h1>
    <sc:text field="title" select="$item"/>
  </h1>
  <p>
    <sc:html field="text" select="$item"/>
  </p>
</xsl:template>

All I need to do above is grab the ID of the article from the query string, find that item then render the appropriate fields.

This technique has allowed me to define a custom presentation for the shared articles per view item, but it leaves me with ugly URLs. Instead of having a nice URL such as in the proxy item case (1st case above) like http://sc61.localhost/Articles/Article2.aspx, instead I end up with an ugly URL using query string parameters like http://sc61.localhost/view/Articles/Detail.aspx?id={FCCC6478-9BA5-4634-B793-45DBC800EDEC}.

But don't despair, there is one last technique which still allows presentation to be defined per view item, but doesn't require ugly URLs. This is done using wildcard items. A wildcard item will handle the request for any item at that level if another item doesn't match by name. A wildcard item is created the same as any other item. It's name just needs to be * (star).

wildcard item

Mark Cassidy recently posted an article about wildcard nodes in which he refers to a post made by Lars Nielsen about using wildcard items and dynamic URLs to avoid query strings.

So given the above content tree, if I make a request for /articles/myarticle I will get the myarticle item. If I make a request for /articles/somethingelse the wildcard item will be used to handle the request. This opens the opportunity to handle the request in a different way. Now I can have the wildcard item list rendering generate a link for each shared article as if the article was in the same location as the wildcard item. The wildcard article rendering will need to parse the current URL to determine which article it needs to render.

Wildcard item list rendering template:

<xsl:variable name="shared" select="sc:item('/sitecore/
  content/shared articles',.)"/>

<xsl:template match="*" mode="main">
  <xsl:variable name="listroot" select="concat(substring-before(
    sc:path($sc_currentitem), '.aspx'), '/')"/>
  <ul>
    <xsl:for-each select="$shared/child::item">
      <li>
        <a href="{concat($listroot,@name)}.aspx">
          <xsl:value-of select="@name"/>
        </a>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

Note how I have to manually create the URL for the article myself. This is because the URL we're generating doesn't point to a real item. The wildcard item will handle the request instead.

Wildcard item article rendering template:

<xsl:variable name="shared" select="sc:item('/sitecore/
  content/shared articles',.)"/>

<xsl:template match="*" mode="main">
  <xsl:variable name="item" select="$shared/*[@key = sc:ToLower(
    sc:urlname(0))]"/>
  <h1>
    <sc:text field="title" select="$item"/>
  </h1>
  <p>
    <sc:html field="text" select="$item"/>
  </p>
</xsl:template>

And in the article rendering I need to parse the URL to work out which item to render.

That has now solved our problems. I can change the presentation per wildcard item and I don't have ugly URLs anymore. They look the same as the proxy technique above.

But wildcard has introduced 1 small potential issue. Although the wildcard item in the above screenshot will handle myarticle, it won't handle myarticle\somethingelse. Wildcard items will only handle requests for a single path and level in the hierarchy. If my shared articles were created in a hierarchical structure I would need a wildcard item for each level of that hierarchy. However this may not be a problem, depending on if you intent to have hierarchical shared articles or not.

hierarchical wildcard item

All the techniques presented above don't require any change to normal Sitecore behavior and would be preferred by most. But as you know Sitecore has a huge strength in allowing it's behavior to be changed. I could override the default ItemResolver in the httpRequestBegin pipeline to have it route requests differently to cover off that last issue with the wildcard item technique, the hierarchical shared articles.

For this tweak I want to be able to specify a root path and a handler item path. The root path will define the root of any request which should be handled by the handler item which the handler item path references. In this way we only require a single handler item in the content tree which will handle all requests below a certain path. I'll also make sure I can have multiple handler definitions. After all, the whole purpose for what we're doing is to allow sharing of content across multiple sites and each site will have a different handler item.

To allow multiple root item / handler item pair definitions, I'll need to use a list add method in my class to allow the default Sitecore class factory to handle creation of my class from web.config. This method will need to take an instance of a custom class which contains both pieces of data I need per definition.

Let's start by defining this custom data class.

public class Handler
{
  public string RootPath { get; set; }
  public string HandlerItemPath { get; set; }

  public Handler(string rootPath, string handlerItemPath)
  {
    RootPath = rootPath;
    HandlerItemPath = handlerItemPath;
  }
}

Now let's define the structure of our processor class including the list add method.

public class ItemResolver :
  Sitecore.Pipelines.HttpRequest.ItemResolver
{
  private List<Handler> m_handlers = null;

  public ItemResolver()
  {
    m_handlers = new List<Handler>();
  }

  public void AddHandler(Handler handler)
  {
    m_handlers.Add(handler);
  }
}

I'll also need the following using's to allow my code to compile.

using System;
using System.Collections.Generic;
using Sitecore;

Now to fill in the Process method which is called when a request is made.

public override void Process(
  Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
  var handled = false;
  for(int i = 0; i < m_handlers.Count; i++)
  {
    var currentPath = args.Url.ItemPath.ToLower();
    var toComp = m_handlers[i].RootPath.ToLower();
    if (currentPath.StartsWith(toComp) && currentPath != toComp)
    {
      var item = args.GetItem(m_handlers[i].HandlerItemPath);
      if (item != null)
      {
        Context.Item = item;
        handled = true;
        break;
      }
    }
  }

  if(!handled)
    base.Process(args);
}

In the code above we loop through each of the handlers checking if the current request item's path starts with the root path of the handler. If it does then we populate the context with out handler item instead.

And then we need to alter the web.config to use and configure our new ItemResolver. Open your web.config file and find the current ItemResolver in the httpRequestBegin pipeline and replace it with the following:

<processor type="HandlerItem.ItemResolver, HandlerItem">
  <handlers hint="list:AddHandler">
    <site1 type="HandlerItem.Handler">
      <param desc="rootPath">
        /sitecore/content/Site1/Articles
      </param>
      <param desc="handlerItemPath">
        /sitecore/content/Site1/Articles
      </param>
    </site1>
    <site2 type="HandlerItem.Handler">
      <param desc="rootPath">
        /sitecore/content/Site2/Articles
      </param>
      <param desc="handlerItemPath">
        /sitecore/content/Site2/Articles
      </param>
    </site2>
  </handlers>
</processor>

Now when anything below the root path items above is requested the request will get handed over to the appropriate handler item.

We would still use the same renderings as we used for the wildcard item technique above, although we need to ensure the wildcard item rendering can handle the parsing of multiple levels in the URL and not just the current level.

And there you have a few techniques and tweaks to share content between different sites in Sitecore. The one you should use will depend largely on your requirements.

Comments

Paul

For the purposes of Lucene indexing...
I'm assuming that target items of wildcard URLs would NOT be indexed with the intended wildcard URL. Whereas proxies (i assume) would be indexed as if they really were under the target item. Is this correct?
If so, then I think a useful feature would be the ability to apply filtering to proxies, instead of just getting the entire hierarchy of items under the proxied item.
Thoughts?

Alistair Deneys

Very good point Paul. I'll address the issues of indexing and using these techniques in another post very soon...

Alistair Deneys

Hi Paul, I've just posted a follow up article which covers the search considerations. [/blog/2010/02/16/search-considerations-for-sharing-content/](/blog/2010/02/16/search-considerations-for-sharing-content/)

[...] last year I wrote a post exploring some techniques for sharing content inside Sitecore. One thing I didn’t really take into account was searching and indexing of this shared content. [...]

[...] last year I wrote a post exploring some techniques for sharing content inside Sitecore. One thing I didn’t really take into account was searching and indexing of this shared content. [...]

[...] Alistair Deneys - [/blog/2009/07/28/techniques-for-sharing-content](/blog/2009/07/28/techniques-for-sharing-content) [...]

[...] If you would like to know more on how wildcards work and what they can do for you I recommend this blogpost from Alistair [...]

Leave a comment

All fields are required.