Sitecore Wildcard Design Pattern & Profile Cards

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.

sitecorewildcard

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.

WildcardSolution


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.