If you are a developer and you love shopping on , you probably had the same thought: what would it take to re-create in Sitecore? Completely from scratch. Ok, maybe it’s just me.

So you will need to think about how to architect your content tree properly. Luckily, Derek has dedicated solely to content tree architecture so I don’t need to go into much detail here.
Let’s say you have your content tree already designed. We are going to take Nicam demo site as an example:
So all the camera products are structured in categories, that’s nice.
Since we talk about URLs in this post, everything is set there too. Each product has a content path which more or less serves as product URL on the public facing side.
In other words, I would be able to access my D3X camera using the following URL:

One of the things you need to know is that Sitecore is constructing those URLs on the fly based on LinkManager configuration in web.config. And it is actually doing a pretty great job out of the box by giving you a number of options when it comes to URL construction. You can prepend language ISO code in URL, use display name instead of item name, etc. You can learn more about it .

But what if we want to have fluid URLs that are constructed based on product attributes, meta data or some other criteria? Just as on , where the product URL is clearly driven by product attributes:

For example, for all of our SLR cameras, use the following URL pattern: }, where SKU is an attribute on the product itself:

Well, as it turns out, it is quite straightforward to do.
When it comes to handling any custom URL handling requirements, there are mainly two components you have to deal with.
1. Custom Item Resolver.
The custom logic here will attempt to resolve a valid item in the content tree by the custom URL.
2. Custom Link Provider.
This is the flip side of the solution. We need to teach Sitecore to generate product URLs based on our custom rules.
In addition, you would generally need a component to process such custom URL rules. In my example I would simply use IDTable, which allows to store any mapping to an item in a flat table. For the sake of simplicity I will be updating my IDTable based mapping table every time an item is saved via a handler.
The result will look something like this:

So here are all the pieces:
1. Custom Item Resolver:

public class ProductUrlResolver : HttpRequestProcessor  
{

public override void Process(HttpRequestArgs args)

{

Assert.ArgumentNotNull(args, "args");

if (Context.Item != null || Context.Database  null || args.Url.ItemPath.Length  0) return;

Context.Item = ProductUrlManager.GetProductItemByFilePath(args.Url.FilePath);

}
}
<processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel"/>  
<!-- the proper order is important -->
<processor type="Custom.ProductUrlResolver, ProductUrlResolver"/>
<processor type="Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel"/>
  1. Custom Link Provider:
public class ProductLinkProvider : LinkProvider  
{

public override string GetItemUrl(Item item, UrlOptions options)

{

Assert.ArgumentNotNull(item, "item");

Assert.ArgumentNotNull(options, "options");

return item.IsProduct() ? item.ProductUrl() : base.GetItemUrl(item, options);

}
}
<linkManager defaultProvider="sitecore">

<providers>

<clear/>

<add name="sitecore"

type="Custom.ProductLinkProvider, ProductUrlResolver"

.../>

</providers>
</linkManager>
  1. ItemSaved event handler:
public class ProductHandler  
{

protected void OnItemSaved(object sender, EventArgs args)

{

if (args  null) return;

var item = Event.ExtractParameter(args, 0) as Item;

if (item  null) return;

if (item.IsProduct())

{

item.RegisterMapping();

}

}
}
<event name="item:saved">

...

<handler type="Custom.ProductHandler,ProductUrlResolver" method="OnItemSaved"/>
</event>
  1. Utility Manager where all the logic is handled:
public static class ProductUrlManager

{

public static string IdTableKey

{

get { return "ProductResolver"; }

}

public static bool IsProduct(this Item item)

{

var template = TemplateManager.GetTemplate(item);

return template != null &&  
template.DescendsFromOrEquals(ID.Parse("{B87EFAE7-D3D5-4E07-A6FC-012AAA13A6CF}"));

}

public static string ProductUrl(this Item item)

{

return "/{0}/{1}/{2}".FormatWith(item.TemplateName.ToLowerInvariant(),  
item.Name.ToLowerInvariant(),  
item["SKU"]);

}

public static Item GetProductItemByFilePath(string filePath)

{

var id = IDTable.GetID(ProductUrlManager.IdTableKey, filePath);

if (id != null && !ID.IsNullOrEmpty(id.ID))

{

return Context.Database.GetItem(id.ID);

}

return null;

}

public static void RegisterMapping(this Item item)

{

IDTable.RemoveID(ProductUrlManager.IdTableKey, item.ID);

IDTable.Add(ProductUrlManager.IdTableKey, item.ProductUrl(), item.ID);

}

}

Conceptually, what do you think about this?
image