A common practice in Sitecore design is to use a wildcard item to handle dynamic URLs. On the site I am working on, the wildcard item is primary used to abstract content out of the site tree, and to maintain one definition for presentation details (on the * item), rather than define presentation on each individual page. Abstracted content is received based on the slug.

Experience Analytics
The wildcard design pattern creates all kinds of headaches. The most recent issue that I stumbled upon was that the wildcard item itself was appearing in Analytics tracking, rather than the abstracted data item. After doing a little research on the internet, I found Sander Bouwmeester’s blog that outlined a simple solution. Here is essentially Sander’s code:
using Sitecore;
using Sitecore.Analytics.Tracking;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Web;
using System;
using System.Web;
using Sitecore.Analytics.Pipelines.InitializeTracker;
namespace Someproject.Pipelines.InitializeTracker
{
public class CustomCreatePage : InitializeTrackerProcessor
{
private void CreateAndInitializePage(HttpContextBase httpContext, CurrentInteraction visit)
{
IPageContext empty = visit.CreatePage();
empty.SetUrl(WebUtil.GetRawUrl());
DeviceItem device = Context.Device;
if (device == null)
{
empty.SitecoreDevice.Id = Guid.Empty;
empty.SitecoreDevice.Name = string.Empty;
}
else
{
empty.SitecoreDevice.Id = device.ID.Guid;
empty.SitecoreDevice.Name = device.Name;
}
// Default Sitecore implementation
Item item = Context.Item;
// Our logic starts here: if the current item is a wildcard
if (item != null && item.Name == "*")
{
// Perform a call to the logic which resolves the correct item
var resolvedItem = this.ResolveWildcardItem(item);
if (resolvedItem != null)
{
item = resolvedItem;
}
}
// Resume the default behaviour
if (item == null)
{
return;
}
empty.SetItemProperties(item.ID.Guid, item.Language.Name, item.Version.Number);
}
public override void Process(InitializeTrackerArgs args)
{
Assert.ArgumentNotNull(args, "args");
if (args.IsSessionEnd)
{
return;
}
HttpContextBase httpContext = args.HttpContext;
if (httpContext == null)
{
args.AbortPipeline();
return;
}
this.CreateAndInitializePage(httpContext, args.Session.Interaction);
}
private Item ResolveWildcardItem(Item item)
{
return Someproject.BusinessLogic.AbstactedData.GetDataSource(item, Someproject.BusinessLogic.SlugFactory.GetSlugPreSelected(item));
}
}
}
The InitializeTracker CreatePage processor gets overridden with an include file:
< ? xml version="1.0" ?>
< configuration xmlns :patch="http:/ / www.sitecore.net/xmlconfig /" xmlns :set="http: / / www.sitecore.net/xmlconfig/set/" >
< sitecore >
< pipelines >
< initializeTracker >
< processor type="Someproject.Pipelines.InitializeTracker.CustomCreatePage,Someproject" patch:instead="*[@type='Sitecore.Analytics.Pipelines.InitializeTracker.CreatePage, Sitecore.Analytics']" />
< /initializeTracker >
< /pipelines >
< /sitecore >
< /configuration >
Profile Cards
This was a good start, but I needed profile cards assigned the abstracted content to get added to the tracking session. Sitecore Profile cards are used to identify and segment contacts and personalize web site content. After a little more research, I read Nick Allen’s blog and created the following solution.

using Sitecore;
using Sitecore.Analytics;
using Sitecore.Analytics.Pipelines.StartTracking;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Analytics.Pipelines.ProcessItem;
namespace Someproject.Pipelines.StartTracking
{
public class ProcessItem : Sitecore.Analytics.Pipelines.StartTracking.ProcessItem
{
public override void Process(StartTrackingArgs args)
{
Assert.ArgumentNotNull(args, "args");
if (Tracker.Current != null && Tracker.Current.Session != null && Tracker.Current.Session.Interaction != null)
{
// Default Sitecore implementation
Item item = Context.Item;
// Our logic starts here: if the current item is a wildcard
if (item != null && item.Name == "*")
{
// Perform a call to the logic which resolves the correct item
var resolvedItem = this.ResolveWildcardItem(item);
if (resolvedItem != null)
{
item = resolvedItem;
}
}
if (item != null)
{
ProcessItemArgs args2 = new ProcessItemArgs(Tracker.Current.Session.Interaction, item);
ProcessItemPipeline.Run(args2);
}
}
}
private Item ResolveWildcardItem(Item item)
{
return Someproject.BusinessLogic.AbstactedData.GetDataSource(item, Someproject.BusinessLogic.SlugFactory.GetSlugPreSelected(item));
}
}
}
The startTracking ProcessItem processor gets overridden with an include file:
< ? xml version="1.0" ?>
< configuration xmlns :patch="http:/ / www.sitecore.net/xmlconfig /" xmlns :set="http: / / www.sitecore.net/xmlconfig/set/" >
< sitecore >
< pipelines >
< startTracking >
< processor type="Someproject.Pipelines.StartTracking.ProcessItem,Someproject" patch:instead="*[@type='Sitecore.Analytics.Pipelines.StartTracking.ProcessItem, Sitecore.Analytics']" />
< /startTracking >
< /pipelines >
< /sitecore >
< /configuration >
Conclusion
It’s possible to maintain the wildcard design pattern in Sitecore 8 Customer Experience without having to resort to large amounts of redesign. Sitecore profile cards can be assigned to abstracted Sitecore content and programmatically added to the analytics session by overriding the InitializeTracker CreatePage and startTracking ProcessItem pipeline processors.