<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitry Bastron</title>
    <description>The latest articles on DEV Community by Dmitry Bastron (@diger74).</description>
    <link>https://dev.to/diger74</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F416018%2F7af59a1f-425c-418c-8a1c-bc308d180977.jpg</url>
      <title>DEV Community: Dmitry Bastron</title>
      <link>https://dev.to/diger74</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diger74"/>
    <language>en</language>
    <item>
      <title>Activity logging with Xperience by Kentico</title>
      <dc:creator>Dmitry Bastron</dc:creator>
      <pubDate>Tue, 26 Sep 2023 11:58:26 +0000</pubDate>
      <link>https://dev.to/byteminds_agency/activity-logging-with-xperience-by-kentico-4kh3</link>
      <guid>https://dev.to/byteminds_agency/activity-logging-with-xperience-by-kentico-4kh3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In our previous post, we explored activity tracking data modeling principles and common pitfalls. If you missed it, &lt;a href="https://dev.to/byteminds_agency/mastering-advanced-tracking-with-kentico-xperience-5hcf"&gt;catch up here&lt;/a&gt;. In this article, we'll dive into practical implementation in your Xperience by Kentico project. We'll guide you through setting up a custom activity type and show you how to log visitor activities effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Example: Selling Property Search
&lt;/h2&gt;

&lt;p&gt;Let's implement one of the custom activity types that was mentioned in the previous post - &lt;strong&gt;Selling property search&lt;/strong&gt;. We aim to log this activity whenever a website visitor searches for a property for sale. If the visitor interacted with any filters, then some additional context information should be captured as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;property location: postcode area, city or district&lt;/li&gt;
&lt;li&gt;property type: house, flat, etc.&lt;/li&gt;
&lt;li&gt;price range&lt;/li&gt;
&lt;li&gt;number of bedrooms&lt;/li&gt;
&lt;li&gt;status: available or sold&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a custom activity type
&lt;/h2&gt;

&lt;p&gt;First of all, we need to create our custom Activity type via Xperience by Kentico interface. This can be done in &lt;strong&gt;Digital Marketing&lt;/strong&gt; &amp;gt; &lt;strong&gt;Contact Management&lt;/strong&gt; &amp;gt; &lt;strong&gt;Activity Types&lt;/strong&gt; section of Xperience by Kentico admin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpgqv1z8xbw650gu1vyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpgqv1z8xbw650gu1vyr.png" alt="Image description" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember the code name of the custom activity type - &lt;strong&gt;SellingPropertySearch&lt;/strong&gt; - this will be used later in the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging activity
&lt;/h2&gt;

&lt;p&gt;There are two main methods to log visitor activity in Xperience by Kentico - &lt;a href="https://docs.xperience.io/xp/developers-and-admins/digital-marketing-setup/set-up-activities/custom-activities#Customactivities-Server-sidecode"&gt;server-side&lt;/a&gt; and &lt;a href="https://docs.xperience.io/xp/developers-and-admins/digital-marketing-setup/set-up-activities/custom-activities#Customactivities-Client-sidecode"&gt;client-side&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main difference is that server-side activity tracking happens as part of the request execution pipeline before the result is returned to the visitor, and client-side happens after the page is returned to the visitor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-side logging
&lt;/h3&gt;

&lt;p&gt;For most activity logging scenarios, we recommend employing client-side logging. This ensures that activities are logged only after the visitor has seen the rendered page, preventing false-positive tracking triggered by crawlers or bots. Additionally, certain user interface interactions, like button clicks, can only be logged on the client side.&lt;/p&gt;

&lt;p&gt;Consider this code snippet to log our custom activity when a visitor lands on a search results page for properties on sale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@using Kentico.Activities.Web.Mvc

@* Registers the script that allows custom activity logging *@
@Html.Kentico().ActivityLoggingAPI()

...

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trackSalesSearch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;kxt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customactivity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SellingPropertySearch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Keep it blank for now, we will add some context later&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Property search for sale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Add an event listener to track the activity after the page load&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trackSalesSearch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server-side logging
&lt;/h3&gt;

&lt;p&gt;In some cases, like dynamic Ajax requests, server-side tracking is more suitable. Imagine a visitor landing on a search results page, then applying various filters, and triggering a request to the backend, resulting in new properties matching the filter criteria appearing. Xperience by Kentico provides three options to accomplish this:&lt;/p&gt;

&lt;h4&gt;
  
  
  Manually insert ActivityInfo object
&lt;/h4&gt;

&lt;p&gt;You can manually create and save an ActivityInfo object into the database, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ActivityInfo&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ActivityType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SellingPropertySearch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ActivitySiteID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;siteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ActivityContactID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContactManagementContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentContactID&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this method has its drawbacks, such as always logging the activity irrespective of global settings and cookie consent, and not populating extra fields automatically.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using standard ICustomActivityLogger
&lt;/h4&gt;

&lt;p&gt;Another option is to utilize Kentico's implementation of the &lt;strong&gt;ICustomActivityLogger&lt;/strong&gt; interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;CMS.Activities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ICustomActivityLogger&lt;/span&gt; &lt;span class="n"&gt;customActivityLogger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;salesPropertySearchActivityData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CustomActivityData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ActivityTitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Property search for sale"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ActivityValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;customActivityLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SellingPropertySearch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salesPropertySearchActivityData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method respects cookie consent and populates extra activity fields but can become unwieldy if you have numerous custom activity types with unique logic.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementing CustomActivityInitializerBase
&lt;/h4&gt;

&lt;p&gt;For more streamlined activity logging, especially when dealing with multiple custom activity types, it's recommended to inherit activity type implementations from the &lt;strong&gt;CustomActivityInitializerBase&lt;/strong&gt; base class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SellingPropertySearchActivityInitializer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CustomActivityInitializerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;activityValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;activityItemId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SellingPropertySearchActivityInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;activityValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;activityItemId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activityValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activityValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activityItemId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activityItemId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IActivityInfo&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActivityTitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Property search for sale"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActivityValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activityValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActivityItemID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activityItemId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ActivityType&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"SellingPropertySearch"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual logging part of the code will then look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActivityLogService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// or retrieve it from DI container&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activityInitializer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SellingPropertySearchActivityInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityInitializer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Beware server-side caching
&lt;/h3&gt;

&lt;p&gt;One critical consideration to keep in mind is server-side caching. If the activity logging code is part of a widget or component with output caching enabled, the activity will only be logged the first time the page is opened. Subsequent requests for the same page will return the cached widget, bypassing the tracking code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attaching context data
&lt;/h2&gt;

&lt;p&gt;We've successfully logged a simple property search activity, but now it's time to enhance it with the context data mentioned at the beginning of this article. Specifically, we want to include location, price range, and other relevant details.&lt;/p&gt;

&lt;p&gt;For each activity, we have four spare fields to attach context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ActivityItemID - integer identifier of whatever "primary" object from DXP database you would like to relate to this activity: location or office is a good example&lt;/li&gt;
&lt;li&gt;ActivityItemDetailID - yet another integer identifier to relate something "secondary", e.g. the property itself&lt;/li&gt;
&lt;li&gt;ActivityValue - free text field where we can put any additional information&lt;/li&gt;
&lt;li&gt;ActivityComment - same as previous, another free text field&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating a custom class
&lt;/h3&gt;

&lt;p&gt;Initially, you might consider creating a separate container to store this context information, such as a Module Custom Class. Then, you can save an integer reference to this object in the &lt;strong&gt;ActivityItemID&lt;/strong&gt; or &lt;strong&gt;ActivityItemDetailID&lt;/strong&gt; fields.&lt;/p&gt;

&lt;p&gt;Start by creating a custom class from Development &amp;gt; Modules &amp;gt; Classes interface:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wn5kkv0ai1ysyqxg3ln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wn5kkv0ai1ysyqxg3ln.png" alt="Image description" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following fields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4459d47i5qjk3gwbtvqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4459d47i5qjk3gwbtvqd.png" alt="Image description" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generate a strongly-typed C# class for your module custom class following &lt;a href="https://docs.xperience.io/xp/developers-and-admins/api/generate-code-files-for-system-objects#Generatecodefilesforsystemobjects-Generatecodefiles"&gt;the documentation's instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we can modify the activity logging code as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;propertySearchAttributes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertySearchAttributesInfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PropertyType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Flat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PriceFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;350000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PriceTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BedroomsFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BedroomsTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Available"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;propertySearchAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActivityLogService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activityInitializer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SellingPropertySearchActivityInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;propertySearchAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertySearchAttributesID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityInitializer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can query the joined result of our logging via SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;OM_Activity&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Custom_PropertySearchAttributes&lt;/span&gt; &lt;span class="n"&gt;psa&lt;/span&gt;
        &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActivityItemID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertySearchAttributesID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActivityType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SellingPropertySearch'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the technical perspective, it looks great because the data connected to the activity is decoupled from the activity itself. It can be processed, amended, or queried separately. &lt;/p&gt;

&lt;p&gt;The reality though sounds much more pragmatic: &lt;strong&gt;this will never be the case&lt;/strong&gt;. When marketers require this activity info, they always need the whole thing with all the context information available. For example, based on activities they may want to create a dynamic contact group with those who were searching for properties for sale in London to send out some communication for these people when there are new interesting properties available.&lt;/p&gt;

&lt;p&gt;However, evaluating a connected object from the &lt;strong&gt;Custom_PropertySearchAttributes&lt;/strong&gt; table to check the Location field would necessitate an additional SQL query, potentially impacting performance.&lt;/p&gt;

&lt;p&gt;In the current version of Xperience by Kentico this method is also unavailable and we can only inspect data stored within &lt;strong&gt;ActivityValue&lt;/strong&gt; field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzdm1xcokt9jsdh4dk8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzdm1xcokt9jsdh4dk8e.png" alt="Image description" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured XML
&lt;/h3&gt;

&lt;p&gt;The alternative solution is to store serialized XML or JSON in &lt;strong&gt;ActivityValue&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;We only need a couple of things to make this work. First is a POCO-model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Serializable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesSearch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;PropertyType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PriceFrom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PriceTo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;BedroomsFrom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;BedroomsTo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a generic method to serialize this object into XML string, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SerializeToXml&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;XmlWriterSettings&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OmitXmlDeclaration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Indent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StringWriter&lt;/span&gt; &lt;span class="n"&gt;stringWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringWriter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;XmlWriter&lt;/span&gt; &lt;span class="n"&gt;xmlWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XmlWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stringWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XmlSerializerNamespaces&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XmlSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stringWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally our tracking code would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;propertySearchAttributes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SalesSearch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PropertyType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Flat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PriceFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;350000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PriceTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BedroomsFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BedroomsTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Available"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActivityLogService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activityInitializer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SellingPropertySearchActivityInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;SerializeToXml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertySearchAttributes&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityInitializer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, this would be the XML in ActivityValue column in OM_Activity table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;SalesSearch&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyType&amp;gt;&lt;/span&gt;Flat&lt;span class="nt"&gt;&amp;lt;/PropertyType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Location&amp;gt;&lt;/span&gt;London&lt;span class="nt"&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PriceFrom&amp;gt;&lt;/span&gt;350000&lt;span class="nt"&gt;&amp;lt;/PriceFrom&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PriceTo&amp;gt;&lt;/span&gt;500000&lt;span class="nt"&gt;&amp;lt;/PriceTo&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;BedroomsFrom&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/BedroomsFrom&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;BedroomsTo&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/BedroomsTo&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Status&amp;gt;&lt;/span&gt;Available&lt;span class="nt"&gt;&amp;lt;/Status&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/SalesSearch&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach will allow our marketers to accomplish their task of setting up a dynamic contact group with buying prospects from London:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0eee3niqofahgf8j900.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0eee3niqofahgf8j900.png" alt="Image description" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar effect can be achieved with client-side tracking as well. We just need to withdraw this XML into the view with our tracking code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@using Kentico.Activities.Web.Mvc

@* Registers the script that allows custom activity logging *@
@Html.Kentico().ActivityLoggingAPI()

@{
    var propertySearchAttributes = new SalesSearch
    {
        PropertyType = "Flat",
        Location = "London",
        PriceFrom = 350000,
        PriceTo = 500000,
        BedroomsFrom = 2,
        BedroomsTo = 3,
        Status = "Available"
    };
}
...

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trackSalesSearch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;kxt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customactivity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SellingPropertySearch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@SerializeToXml(propertySearchAttributes)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Property search for sale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Add an event listener to track the activity after the page load&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trackSalesSearch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we've explored the practical implementation of activity tracking in Xperience by Kentico, focusing on the "Selling Property Search" custom activity type as an example. &lt;/p&gt;

&lt;p&gt;For most cases, the recommended implementation is client-side tracking, ensuring accuracy after the page rendering. Finally, to enhance logged activities with context data, we suggested using structured XML or JSON in the ActivityValue field.&lt;/p&gt;

&lt;p&gt;By following these techniques, you can effectively track and gather insights from visitor activities, enhancing your marketing strategies.&lt;/p&gt;

</description>
      <category>kentico</category>
      <category>activity</category>
      <category>tracking</category>
      <category>dxp</category>
    </item>
    <item>
      <title>Mastering advanced tracking with Kentico Xperience</title>
      <dc:creator>Dmitry Bastron</dc:creator>
      <pubDate>Tue, 15 Aug 2023 13:20:37 +0000</pubDate>
      <link>https://dev.to/byteminds_agency/mastering-advanced-tracking-with-kentico-xperience-5hcf</link>
      <guid>https://dev.to/byteminds_agency/mastering-advanced-tracking-with-kentico-xperience-5hcf</guid>
      <description>&lt;p&gt;In the world of Digital Experience Platforms (DXPs), understanding and harnessing the power of data is key to unlocking success. In this blog post series, we will take you on a journey through a real-life scenario of implementing advanced tracking and analytics using Kentico Xperience 13 DXP. Whether you are working on a project with &lt;a href="https://docs.xperience.io/xp"&gt;Xperience by Kentico&lt;/a&gt; or &lt;a href="https://docs.xperience.io/"&gt;Kentico Xperience 13&lt;/a&gt;, the activity tracking principles remain consistent, making this information relevant for both.&lt;/p&gt;

&lt;p&gt;Very often, the process of building websites goes through the challenges of budget restrictions and pressing deadlines. As a result of multiple tough prioritization calls, tasks like personalized user experience, marketing automation, visitor segmentation, and advanced tracking are usually not making the MVP launch of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 0: Tracking Trumps All Marketing Features
&lt;/h2&gt;

&lt;p&gt;Data is the modern-day gold, and without detailed tracking from the very beginning, you risk missing out on valuable insights. Only building a solid foundation of data collection ensures all your marketing efforts, like segmentation, automation, and personalization, can reach their full potential in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: Going Beyond Out-of-the-Box Tracking
&lt;/h2&gt;

&lt;p&gt;While Kentico Xperience 13 and other DXPs offer default activity tracking, it's not enough to fully comprehend the business context of your website. Injecting context into the tracked activities becomes your responsibility to gain deeper insights into visitor interactions.&lt;/p&gt;

&lt;p&gt;With the enabled &lt;a href="https://docs.xperience.io/on-line-marketing-features/configuring-and-customizing-your-on-line-marketing-features/configuring-activities/enabling-activity-tracking"&gt;default activity tracking&lt;/a&gt; Kentico Xperience tracks multiple activity types automatically, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Page visits.&lt;/strong&gt; In the activity log, you will have the URL of the visited page and the referer URL (the page the user visited just before, what will be included there is defined by &lt;a href="https://web.dev/referrer-best-practices/#what-policies-are-available-and-how-do-they-differ"&gt;referer policy&lt;/a&gt;). However, there is no information regarding the context of content on the visited page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form submissions.&lt;/strong&gt; This activity provides a bit more information, including a link to a form submission data and the type of the form. But if there was a generic form used on many pages, the context of the page is not included here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-commerce events&lt;/strong&gt;. Products added to the basket, purchases, basket abandonment. This is probably the most advanced area of standard tracking because it includes a lot of data about products. But not the page context where it was performed on the website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a common pattern here - the context of the visited page is missing.&lt;/p&gt;

&lt;p&gt;To explain this in more detail, let's have a look at a real-life scenario we implemented recently with Kentico Xperience 13:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Website for one of the top real-estate agencies in the UK operating on a franchising model.&lt;/li&gt;
&lt;li&gt;More than 100 offices advertising their properties for sale and to rent.&lt;/li&gt;
&lt;li&gt;Different types of properties - homes, flats, commercial, etc.&lt;/li&gt;
&lt;li&gt;Geolocation search feature to find a property in chosen city, postcode area, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjujnbj8cae09ikqykt6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjujnbj8cae09ikqykt6l.png" alt="Image description" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: Unveiling User Interests with Page Visits
&lt;/h2&gt;

&lt;p&gt;Page visits offer more than just a measure of visitor traffic. Understanding which specific pages users engage with can provide valuable insights into their interests, in our case, such as property type, location, price range, and status. Augmenting page visits with this additional information enriches your tracking data, leading to more informed decisions.&lt;/p&gt;

&lt;p&gt;In our example, landing on property details page reveals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Property is for sale or to rent&lt;/li&gt;
&lt;li&gt;Location of property&lt;/li&gt;
&lt;li&gt;Price&lt;/li&gt;
&lt;li&gt;Property type (home, flat, commercial, etc)&lt;/li&gt;
&lt;li&gt;Status (available now or in the future, already sold/let, etc)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the search results page we could gather very similar insights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buy or rent filter&lt;/li&gt;
&lt;li&gt;Region filter, where the property is located&lt;/li&gt;
&lt;li&gt;Price range&lt;/li&gt;
&lt;li&gt;Property type&lt;/li&gt;
&lt;li&gt;Status&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ideally, all this valuable information should be tracked in addition to a page visit activity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg44a76zikr7xwebc2a01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg44a76zikr7xwebc2a01.png" alt="Image description" width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: Making Analytics Actionable
&lt;/h2&gt;

&lt;p&gt;Collecting data is only the first step, but making it actionable is where the real value lies. To achieve effective marketing automation, the activity data must be captured within your DXP. However, if your primary focus is reporting on the data and then applying actions manually, platforms like Google Analytics may suffice.&lt;/p&gt;

&lt;p&gt;Below are the examples of reports that business could be interested in our example. These reports can be achieved in either of systems - Kentico Xperience or Google Analytics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many users are looking to buy or rent properties&lt;/li&gt;
&lt;li&gt;How price range adjusts over time&lt;/li&gt;
&lt;li&gt;What number of bedrooms is the most popular on the market&lt;/li&gt;
&lt;li&gt;What offices perform the best in terms of leads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However later it can evolve into something more sophisticated, improving the visitor's experience based on the analytics. Supporting these cases would be easier if the analytical data is stored in the DXP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user searched property for sale in a particular region, then suggest properties nearby&lt;/li&gt;
&lt;li&gt;If they have seen N number of properties to rent, personalize their homepage to showcase more rental properties&lt;/li&gt;
&lt;li&gt;If they have searched properties within a specific price range, then prioritize similarly priced properties on listing pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72tytiem8elln92ridr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72tytiem8elln92ridr2.png" alt="Image description" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 4: Plan Personas First
&lt;/h2&gt;

&lt;p&gt;Now we have a list of additional data to be tracked. Shall we go ahead and implement it? Not precisely. Although the data makes perfect sense now, there are multiple ways we can achieve these tracking requirements.&lt;/p&gt;

&lt;p&gt;Before diving into implementing additional tracking information, it's essential to consider your website personas. By tailoring the tracking to individual personas, such as purchasers, landlords, renters, and vendors from our example, you can capture relevant data that aligns with their unique behavior. This targeted approach enhances user experience and optimizes marketing efforts.&lt;/p&gt;

&lt;p&gt;From our example the main personas and their typical journeys were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sally, the purchaser.&lt;/strong&gt; Is looking to buy a new property. Typically they would:

&lt;ul&gt;
&lt;li&gt;Visits bying landing page&lt;/li&gt;
&lt;li&gt;Performs a few searches for properties for sale, providing their region, price range, type and size of the property&lt;/li&gt;
&lt;li&gt;Visits a number of property pages for sale&lt;/li&gt;
&lt;li&gt;Submits arrange a viewing form for the most interesting properties&lt;/li&gt;
&lt;li&gt;Adds those into favourites&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mike, the landlord.&lt;/strong&gt; Is looking to let his property:

&lt;ul&gt;
&lt;li&gt;Submits request a valuation (to rent) form&lt;/li&gt;
&lt;li&gt;Searches for already let properties in the past, to check how efficient estate agents are in the area they live&lt;/li&gt;
&lt;li&gt;Visits letting landing page&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;John, the renter.&lt;/strong&gt; Is looking to rent a property:

&lt;ul&gt;
&lt;li&gt;Performs a few searches for the properties to rent, providing their region, price range, type and size of the property&lt;/li&gt;
&lt;li&gt;Visits a number of property pages to rent&lt;/li&gt;
&lt;li&gt;Submits arrange a viewing form for the most interesting properties&lt;/li&gt;
&lt;li&gt;Visits rentals landing page&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;James, the vendor.&lt;/strong&gt; Is looking to sell a property, and then potentially buy or rent another:

&lt;ul&gt;
&lt;li&gt;Submits request a valuation (for sale) form&lt;/li&gt;
&lt;li&gt;Searches for already sold properties in the past, to check how efficient estate agents are in the area they live&lt;/li&gt;
&lt;li&gt;Visits selling landing pages&lt;/li&gt;
&lt;li&gt;Downloads terms and fees documents&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lisa, the franchisee prospect&lt;/strong&gt;. Is looking to invest into opening a new branch:

&lt;ul&gt;
&lt;li&gt;Visits franchising blog&lt;/li&gt;
&lt;li&gt;Subscribes to franchising newsletter&lt;/li&gt;
&lt;li&gt;Fills franchising contact form&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn74j9pub4ed2s1jgn86f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn74j9pub4ed2s1jgn86f.png" alt="Image description" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 5: Crafting the Ideal Data Structure
&lt;/h2&gt;

&lt;p&gt;The list above is a starting point of custom activities we want to track and here is what can be customized when a new custom activity type is created in Kentico Xperience 13 (or Xperience by Kentico):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ActivityType - this is the main unique identifier of custom activity type&lt;/li&gt;
&lt;li&gt;ActivityItemID - integer identifier of whatever "primary" object from DXP database you would like to relate to this activity: location or office is a good example&lt;/li&gt;
&lt;li&gt;ActivityItemDetailID - yet another integer identifier to relate something "secondary", e.g. the property itself&lt;/li&gt;
&lt;li&gt;ActivityValue - free text field where we can put any additional information&lt;/li&gt;
&lt;li&gt;ActivityComment - same as previous, another free text field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a nutshell we have one string unique identifier, two integer ID fields, and two free text fields where we can store any relevant info regarding tracked activities. Next is to define how many custom activity types we need, and what additional information is written in the rest of the fields.&lt;/p&gt;

&lt;p&gt;There are two extremes in modeling data for custom activity types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extremely specific:&lt;/strong&gt; Creating a new type for every specific activity you are tracking. Following this principle, there will be "search for properties to sale, from A to B price, in London," "visit of property details page, to rent, in Manchester" and so on. There will be many unique activity types in the system, and complimentary integer and string fields for each will not be utilized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extremely generic:&lt;/strong&gt; Creating one generic custom type, like "estate agents custom activity," and storing as much additional information as we can in the 4 fields available. For example, in ActivityItemID, store the ID of the office if relevant; in ActivityItemDetailID, store the ID of the property; in ActivityValue and ActivityComment fields, store all the additional information serialized as XML or JSON. This can include whether this activity is "sales" or "lettings," price range, identifier of area search, sub-type of activity performed - search, page visit, download, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Very often, if not always, these extreme approaches are not great, and we need to find some sweet spot in the middle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcp3ks57cenn9amb4g0ss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcp3ks57cenn9amb4g0ss.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 6: Keeping Numbers Manageable
&lt;/h2&gt;

&lt;p&gt;The rule of thumb here: keep the number of custom activity types manageable - between 10 and 25. Usually, you can park between these two numbers if you create a custom activity type for each combination of your main taxonomy comprising personas and a list of actions to be tracked.&lt;/p&gt;

&lt;p&gt;In our estate agency example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Main taxonomy - property interest:

&lt;ul&gt;
&lt;li&gt;Selling&lt;/li&gt;
&lt;li&gt;Letting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Type of activity performed:

&lt;ul&gt;
&lt;li&gt;Request valuation form submitted&lt;/li&gt;
&lt;li&gt;Request property viewing form submitted&lt;/li&gt;
&lt;li&gt;Property page visit&lt;/li&gt;
&lt;li&gt;Property search&lt;/li&gt;
&lt;li&gt;Property saved to favourites&lt;/li&gt;
&lt;li&gt;Property Search saved to favourites&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result there will be 12 unique activity types, like: selling property page visit, letting property page visit, selling property search, letting property search, and so on.&lt;/p&gt;

&lt;p&gt;One small but important detail worth noting here is that we could have avoided creating duplicates for both selling and letting in our example and stored selling or letting value in the details of the activity. However, this would have made the use of these activities inside other marketing features, like persona scoring rules or marketing automation, more complicated and less visual.&lt;/p&gt;

&lt;p&gt;For example, when configuring persona scoring rules for a purchaser, we could either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select "visited selling property page" activity type&lt;/li&gt;
&lt;li&gt;Or, select "visited property page", and configure further condition "where property page is selling"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second option is less attractive because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Further conditional checks could affect the performance on high traffic websites.&lt;/li&gt;
&lt;li&gt;It's less visual in the DXP admin. In persona scoring rules list, activity log, and other places, these extra conditions are not displayed, hence you have to expand the specific rule to notice the difference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjth78vjwq0pbmg44hfnr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjth78vjwq0pbmg44hfnr.png" alt="Image description" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 7: Attaching More Context
&lt;/h2&gt;

&lt;p&gt;Although custom activity gives you only 4 fields to fill, there are ways to inject more valuable context information.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Storing data in an attached object.&lt;/strong&gt; Here we can create a structured object like module custom class, record all necessary context information inside it, then while logging activity object just store integer ID referencing this custom class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storing data in structured text format.&lt;/strong&gt; Alternative option would be to prepare serialized context information as XML (or JSON) and storing it in one of the two text fields available in the activity object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2k45b30sjyu79d10wxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2k45b30sjyu79d10wxa.png" alt="Image description" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Mastering advanced tracking and analytics is essential for gaining valuable insights into user behavior and optimizing marketing efforts. By going beyond out-of-the-box tracking, understanding user interests with page visits, making analytics actionable, planning personas, crafting an ideal data structure, and attaching as much context as needed, you can create a comprehensive and effective tracking strategy for your website. Stay tuned for more insights in the next blog post in this series!&lt;/p&gt;

</description>
      <category>kentico</category>
      <category>xperience</category>
      <category>tracking</category>
      <category>dxp</category>
    </item>
  </channel>
</rss>
