Creating Outcomes in Sitecore 8

Sitecore introduced outcomes in version 8.  While creating and implementing outcomes, I came across an error that I thought was worth noting.

What is an Outcome

According to “Practical Sitecore 8 Configuration and Strategy”:

Outcomes are used to track the lifelong value of a particular customer to your business over time.  An outcome in its most basic form is just a tag on the customer’s profile.  That tag name can be whatever you want it to be and it can represent whatever you want it to represent.  Typically, outcomes represent some combination of events, goals, and/or campaign interactions.

How to Create an Outcome

Sitecore documents how to create an outcome.  It’s pretty simple and the steps can be found in the Sitecore Experience Platform documentation.

Outcome Groups

Here is what is not documented or at least not documented well.  If you create an outcome, and do not add it to an outcome group, the outcome will not work.  The outcome will not only fail to register programmatically, but the exception raised will also cause any goals raised in the same tracking session, to fail to register as well.  When you create a new outcome, the outcome group is defaulted to none.

gotcha

Registering an Outcome

Outcomes can only be registered programmatically.  I’m going to cover how I implemented programmatically registering goals and outcomes in a separate post.

 

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.