Skip to main content

Sitecore 9 - Custom Page Events & Filtering with XConnect

This is the first article of a series. I am going to start with creating a custom page event and will show how we can fetch event data using xconnect api.

Let's start with reminding demo scenario:

Imagine that you have a website displaying movies. Visitors are able to see movie details and take some actions like save movie or share it. 

You want to follow the visitors' activities and you want to take some marketing actions based on those activities. For example, if a contact visits a movie more than X time or she/he saves a movie, you want to send those movies to an external system. In addition, there is going to be a limit to send same movie. Such as, it will not be possible to send same movie more than 2 times. 

You want to configure this as a marketing automation plan to give flexibility to your marketing managers. They should be able to add configurable rules and activities. 

My first focus is movie detail page. I want to track visitors when they visit the movie page. The initial step will be creating a page event.

After I added the event in Sitecore (under /sitecore/system/Settings/Analytics/Page Events), I created its definition. MovieDetailEvent keeps id of a movie:

namespace Playground.XConnect.Events
{
    public class MovieDetailEvent : Event
    {
        public static Guid EventId = Guid.Parse("{5A43706A-0307-4B63-89B8-A713F4DA1B38}");

        public MovieDetailEvent(Guid definitionId, DateTime timestamp) : base(definitionId, timestamp)
        {
        }

        public int MovieId { get; set; }
    }
}

Then, I defined my event to my xdb model using XdbModelBuilder:

 modelBuilder.DefineEventType<MovieDetailEvent>(false);

You can see how to register xdb model from official documentation, I am going to skip the details.

After I deployed the model to xconnect instance, now I registered my custom event.

    public class MoviesController : Controller
    {
        public ActionResult Index(int movieId)
        {
            ViewBag.MovieId = movieId;

            TriggerMovieVisitedEvent(movieId);

            return View();
        }

        /// <summary>
        /// Triggering a custom page view event
        /// ConvertMovieDetailEvent.cs is going to handle this event then
        /// </summary>
        /// <param name="movieId"></param>
        private void TriggerMovieVisitedEvent(int movieId)
        {
            var ev = Tracker.MarketingDefinitions.PageEvents[MovieDetailEvent.EventId];

            RegisterEvent(ev, movieId);
        }

        private void RegisterEvent(IPageEventDefinition ev, int movieId)
        {
            if (ev == null) return;

            var pageData = new Sitecore.Analytics.Data.PageEventData(ev.Alias, ev.Id);

            pageData.CustomValues.Add("movieId", movieId);

            Tracker.Current.CurrentPage.Register(pageData);
        }

When a session ends, an interaction is created with events. You can check xdb_collection database interaction tables. I checked and corrected that my event was triggered properly.

{
  "@odata.type": "#Playground.XConnect.Events.MovieDetailEvent",
  "CustomValues": [],
  "DefinitionId": "5a43706a-0307-4b63-89b8-a713f4da1b38",
  "ItemId": "8b79e04a-427b-44e1-a319-b84dab3abee3",
  "Id": "bf1a5c8e-dd60-4bf2-bd57-19eee04d6e50",
  "ParentEventId": "12ffd320-8448-46b7-a3ea-0e9455fcc350",
  "Timestamp": "2019-02-14T12:14:48.6959535Z",
  "MovieId": 9
}

By test purposes, you can use these codes to end your session and stop tracking:

  HttpContext.Session.Abandon();
  Sitecore.Analytics.Tracker.Current.EndVisit(true);
  Sitecore.Analytics.Tracker.Current.EndTracking();

Now, we have movie visit events for our contacts.

Let's use this info and do some xconnect queries. Those queries will be useful when we want to create our marketing automation rules in future.

To get all movie detail events, we can use a query like this:

private List<MovieDetailEvent> GetAllMovieDetailEvents() 
        {
            using (var client = SitecoreXConnectClientConfiguration.GetClient())
            {
                try
                {
                    var events = new List<MovieDetailEvent>();

                    IAsyncQueryable<Interaction> queryable = client.Interactions
                        .Where(x => x.Events.Any(y => y.DefinitionId == MovieDetailEvent.EventId));

                    var enumerable = queryable.GetBatchEnumeratorSync(20);

                    while (enumerable.MoveNext())
                    {
                        var interactionBatchPageEvent = enumerable.Current; // Batch of <= 20 interactions

                        foreach (var interaction in interactionBatchPageEvent)
                        {
                            var matchingEvents = interaction.Events.OfType<MovieDetailEvent>().Where(x => x.DefinitionId == MovieDetailEvent.EventId).ToList();

                            events.AddRange(matchingEvents);

                        }
                    }

                    return events;
                }
                catch (Exception e)
                {
                    throw;
                }
            }
        }

XConnect provides sync and async options to fetch data, you can check official documentation and use up to your case. I just used sync option demo purposes.

We can filter based on current contact.

IAsyncQueryable<Interaction> queryableAll = client.Interactions.Where(x => x.Contact.Id == contact.Entity.Id.Value);
                   

And the last one, I will need to filter movies visited X time in Y days. So, I made a query like this:


 using (var client = SitecoreXConnectClientConfiguration.GetClient())
            {
                try
                {
                    var references = new List<IEntityReference<Contact>>()

                    {
                        new IdentifiedContactReference("demo",
                            Tracker.Current.Contact.Identifiers.First(x => x.Source == "demo").Identifier),
                    };


                    var contact = client.Get<Contact>(references, new ContactExpandOptions()).FirstOrDefault();

                    if (contact != null)
                    {
                        var events = new List<MovieDetailEvent>();

                        IAsyncQueryable<Interaction> queryableAll =
                            client.Interactions.Where(x => x.Contact.Id == contact.Entity.Id.Value);

                        var enumerator = queryableAll.GetBatchEnumeratorSync(20);

                        while (enumerator.MoveNext())
                        {
                            var interactionBatch = enumerator.Current; // Batch of <= 20

                            foreach (var interaction in interactionBatch)
                            {
                                var matchingEvents = interaction.Events.OfType<MovieDetailEvent>().Where(x => x.DefinitionId == MovieDetailEvent.EventId).ToList();

                                events.AddRange(matchingEvents);

                            }
                        }

                        return events.Where(x => x.Timestamp.AddDays(inDays) >= DateTime.UtcNow)
                                    .GroupBy(x => x.MovieId).Where(x => x.Count() >= visitCount)
                                    .Select(x => new ContactPotentialMovie
                                    {
                                        MovieId = x.Key,
                                        VisitCount = x.Count()
                                    }).ToList();
                    }

                    return new List<ContactPotentialMovie>();

                }
                catch (Exception e)
                {
                    throw;
                }

            }

Everything is up to us, we can play with it depending on our needs. This is already an important step. Currently, we are able to create and fetch events. We are easily able to filter and find how many times a contact visited a movie.

However, I want to do that in another way. I am going to use a calculated facet which will keep all the necessary info for a contact.

Let's see it in next post: Calculated facets

Comments

Popular posts from this blog

Deploying SolrCloud with Zookeeper on Azure Kubernetes Service (AKS)

SolrCloud on Azure Kubernetes Service (AKS) Running SolrCloud on Kubernetes — particularly Azure Kubernetes Service (AKS) — can provide you with a highly scalable, cost-efficient, and cloud-native architecture.  This guide walks through how I deployed SolrCloud 8.11.2 with Zookeeper on AKS . Why This Matters for Sitecore Deployments If you're running Sitecore XP or XM , you know that Solr is a mandatory dependency — powering xDB indexing, content search. While Sitecore provides a developer-friendly Solr container for local use, it clearly states: ⚠️ The included Solr image is intended only for development and testing . This means Sitecore does not provide a production-ready Solr setup. If you're deploying Sitecore in production — especially in Kubernetes — you need to create your own scalable, HA SolrCloud cluster. That’s why this deployment matters: You’re building a production-grade SolrCloud setup You’re deploying 3 Solr + 3 Zookeeper nodes for high availabilit...

Sitecore Commerce – XC9 Tips – Missing Commerce Components in SXA Toolbox on Experience Editor

I've recently had an issue that commerce components were missing in SXA Toolbox. I setup Sitecore Commerce on top of an existing instance and I already had a SXA website working on it. The idea was to add commerce components and functionality to my existing website. But after commerce setup, the toolbox was still showing default SXA components and commerce components were missing although I add commerce tenant and website modules: I checked Available Renderings under Presentation folder, there was no problem, commerce renderings were there. I created another tenant and website to see if it shows the commerce components in toolbox. Nothing seemed different but I was seeing commerce components for new website and it was missing on existing one. Then, I noticed two things: 1- Selected catalog was empty in content editor (/sitecore/Commerce/Catalog Management/Catalogs) even if I see Habitat_Master catalog in Merchandising section on commerce management panel. 2- Bootstrap ...

Modern Observability for Sitecore 10.4 on AKS: Grafana, Alloy, Loki, and Prometheus

In this post, I’ll walk through how I extended the base Sitecore XM 10.4 AKS setup with a modern observability stack using Grafana, Alloy, Loki, and Prometheus. This setup provides deep insights into both infrastructure and application health, with powerful log aggregation and visualization. Project Overview Base:  Sitecore XM 10.4 running on Azure Kubernetes Service (AKS) Enhancements:  Added a full Grafana observability stack: Grafana  for dashboards and visualization Alloy  (Grafana Alloy, formerly Promtail) for log collection and multiline parsing Loki  for log aggregation and querying Prometheus  for metrics collection All configuration...