xDB: Tracking the Untrackable – Part 1

Tracking page visits is easy. It just works. A user visits a page and the analytics pipelines attached to the request processing cycle do their thing. Modern sites and web apps do a lot of dynamic and asynchronous data lookups – they try to provide a better user experience by actively loading partials or raw data and only refreshing parts of the screen that needs to change. The more of an interactive application your site is – the more of these behaviors you will encounter. In Sitecore we build these with AJAX, MVC routes, Web API routes, Sitecore.Services.Client, and SignalR.

Trackable

Your page visits are trackable thanks to this:

<mvc.requestBegin patch:source="Sitecore.Mvc.config">
    <processor type="Sitecore.Mvc.Pipelines.Request.RequestBegin.SetupPageContext, Sitecore.Mvc"/>
    <processor type="Sitecore.Mvc.Pipelines.Request.RequestBegin.ExecuteFormHandler, Sitecore.Mvc"/>
    <processor type="Sitecore.Mvc.Analytics.Pipelines.MvcEvents.RequestBegin.StartTracking, Sitecore.Mvc.Analytics"/>
    ...
</mvc.requestBegin>

Your MVC routes registered via:

context.Routes.MapRoute("ProductDetailsPage", "product-catalog/product/{id}", new
{
    controller = "ProductDetailsPage",
    action = "Index"
});

are trackable thanks to this code in Sitecore.Mvc.Pipelines.Loader.InitializeRoutes (part of the initialize pipeline):

foreach (RouteBase routeBase in (Collection<RouteBase>) routes)
{
    Route route = routeBase as Route;
    if (route != null)
    {
        IRouteHandler routeHandler = route.RouteHandler;
        if (routeHandler != null && !(routeHandler is StopRoutingHandler))
        {
            route.RouteHandler = new RouteHandlerWrapper(routeHandler);
        }
    }
}

where the custom handler wires in the MVC pipelines:

protected virtual void BeginRequest()
{
  PipelineService.Get().RunPipeline("mvc.requestBegin", new RequestBeginArgs(this.RequestContext));
}

protected virtual void EndRequest()
{
  PipelineService.Get().RunPipeline("mvc.requestEnd", new RequestEndArgs());
}

Untrackable

Your MVC routes with the attribute routing are not trackable. I blogged about it before. You can help it by running the tracker yourself:

if (!Tracker.IsActive)
{
    Tracker.StartTracking();
}

Your Web API routes are not trackable either. This also includes your Sitecore.Services.Client routes. Trying to run the same Tracker.StartTracking() code won’t help you this time though.

xDB tracking requires HTTP session and Web API controllers don’t have it

If you try to start tracking in your SSC controller it won’t activate the tracker and the log file will have a stack trace from analytics complaining about not being able to grab a session.

Session Aware

There are good reasons Web API is session-less by default. I get it but I also want the promise of xDB. I can do GA from the client but I already have all the details coming into my controllers in Sitecore. xDB won’t track it for me if I don’t give it a session so that’s what we will do – we will make SSC routes session aware.

SSC entity services are all handled by a single route:

HttpRouteCollectionExtensions.MapHttpRoute(
    config.Routes, 
    "EntityService", 
    this._routeBase + "{namespace}/{controller}/{id}/{action}", 
    new
    {
        id = RouteParameter.Optional,
        action = "DefaultAction"
    }
);

If we get a hold of this route at the right time we can give it a session aware route handler via IRequiredSessionState:

public class MakeEntityServiceSessionAware
{
    public void Process(PipelineArgs args)
    {
        var route = RouteTable.Routes["EntityService"] as Route;

        if (route != null)
        {
            route.RouteHandler = new SessionRouteHandler();
        }
    }
}

public class SessionRouteHandler : IRouteHandler
{
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
    {
        return new SessionControllerHandler(requestContext.RouteData);
    }
}

public class SessionControllerHandler : HttpControllerHandler, IRequiresSessionState
{
    public SessionControllerHandler(RouteData routeData) : base(routeData)
    {
    }
}

The right time is during initialize pipeline right after SSC routes registration. Please always remember to ask nicely:

<pipelines>
  <initialize>        
    <processor patch:after="...Sitecore.Services.Infrastructure.Sitecore.Pipelines.ServicesWebApiInitializer..."
               type="Please.MakeEntityServiceSessionAware" />
  </initialize>
</pipelines>

Coming Up Next

The untrackable is now trackable. Starting the tracker manually takes very little effort but we also had to make all entity service calls session aware. The latter makes me feel a little uneasy. I don’t mind HTTP session in my own controllers but I am not so sure about everybody else (e.g. all the built-in SSC controllers for analytics dashboard, path analyzer, and FXM, for example). I will definitely ask @KevinObee.

Next time I will show you how you can very elegantly record details about your AJAX endpoints – MVC and Web API alike – and we will talk about the nuances of xDB Page Events.

Stay tuned!

The idea to use a custom RouteHandler to enable HTTP session in a Web API controller came from this blog post by @filip_woj.

Pavel Veller

Add a Comment

Your email address will not be published. Required fields are marked *

Or request call back