Separating the URL from the item in Sitecore (Part 1)

This Post is in two parts, covering routing the request from Url to item (this), and Generating an appropriate Url for content based on user context (Part 2).

Routing a Url request to a Sitecore Item

One of the good things about Sitecore is that it is quite easy to make a distinction between an item of content and the Url on which it can be found. In the normal run of things, content located on the content node will receive a Url to reflect the position of that content in the content tree.

One common use-case with large websites is the desire to re-use content items on multiple Urls: clearly it is desirable to have one copy of the item in question for ease of maintenance and update, but served via different Url “contexts”.
The solution to this requirement is to use the Sitecore pipeline features and write a handler to identify an appropriate item, based on the Url requested, even if this Url does not actually exist in the content tree.

The example code illustrates how this can be achieved for the same item served to different regions:

http://website.com/uk/foo
http://website.com/us/foo

with the content actually residing at http://website.com/uk/foo. The algorithm can be seen as:

1.) Check that the item is one the resolver should try to handle, if not return
2.) Try to resolve the item using its Url, if that works return the item
3.) If that fails, alter the Url to reflect what the Url would be if the content was not regional, if that works return the item
4.) Try resolving the item by path or Alias, if that works return the item
5.) If all fails, return and Sitecore’s 404 handler will do the rest for you

The RegionalItemResolver

public class RegionalItemResolver : ItemResolver
{
	public override void Process(HttpRequestArgs args)
	{
		if (Context.Site.Name != "website")
			return;

		base.Process(args);

		if (!HttpContext.Current.Request.Url.AbsolutePath.Contains("sitecore"))
		{
			var url = HttpContext.Current.Request.Url.AbsolutePath;
			var item = Context.Database.GetItem(url);
			if(item == null)
			{
				if(GeoHelper.IsRegionalUrl(url))
				{
					// Try to Find the item
					var nonRegionalUrl = GetNonRegionalPath(url);
					if (!string.IsNullOrEmpty(nonRegionalUrl))
					{
						item = GetItemUsingAlias(nonRegionalUrl)
						?? GetItemUsingPath(nonRegionalUrl);
						if (item != null)
							Context.Item = item;
					}
				}
			}
		}
	}
}

This function examines the requested Url, and identifies if it can be found (if so, nothing to do, fall through), then tries to locate the content. First, a helper method identifies if the Url requested is a regional Url and if so tries to create a non-regional path for the item:

private static string GetNonRegionalPath(string url)
{
	var pathElements = (url.Split('/')).Skip(2).ToArray();
	return pathElements.Any() ? string.Join("/", pathElements.ToArray()) : string.Empty;
}

Then the code tries to locate the item itself first using a Sitecore Alias:

private static Item GetItemUsingAlias(string url)
{
	var alias = Context.Database.GetItem(@"/sitecore/system/Aliases/" + url);
	if(alias != null)
	{
		LinkField lf = alias.Fields["Linked item"];
		if (lf != null && lf.TargetItem != null)
		{
			return lf.TargetItem;
		}
	}
	return null;
}

and then a Sitecore-Query based query.

private static Item GetItemUsingPath(string url)
{
	var items = url.Split('/');

	var itempath = items.Select(item => item.Replace("-", " "))
	.Aggregate("fast:/sitecore/content/",
	(current, thisitem) => current + (@"/#" + thisitem + "#"));

	return Context.Database.SelectSingleItem(itempath);

If at the end of all of this, no item is found, the execution falls through and into Sitecore’s standard 404 handling.