xConnect: Rapid Interaction Spoofing and Contact Identification

Cover Image for xConnect: Rapid Interaction Spoofing and Contact Identification

When you're testing contact sessions and contact identification, you don't want to wait for sessions to end on their own; it's too slow. We are going to set up a handful of bookmarkable API GET endpoints so you can rapidly start a session, identify as a specific contact, viw the current session data, and end the session so that the data is written to the database immediately instead of having to wait.

Endpoints for Ending Sessions and Viewing Session Data

using Sitecore.Analytics;
using Sitecore.Diagnostics;
using System.Web.Mvc;
namespace Client.Foundation.xConnectPoc.Controllers
{
public class VisitController : Controller
{
[HttpGet]
public JsonResult Data()
{
var currentTracker = Tracker.Current;
var data = currentTracker.Interaction.ToVisitData();
return Json(data, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult End()
{
Session.Abandon();
Log.Info("Closing xDB session", this);
return Json(new { Message = "xDB Session Closed" }, JsonRequestBehavior.AllowGet);
}
}
}
using System.Web.Mvc;
using System.Web.Routing;
using Sitecore.Pipelines;
namespace Client.Foundation.xConnectPoc.Pipelines.Initialize
{
public class InitializeRoutes : Sitecore.Mvc.Pipelines.Loader.InitializeRoutes
{
public override void Process(PipelineArgs args)
{
RouteTable.Routes.MapRoute(
"VisitData", // Route name
"api/visit/data",
new { controller = "Visit", action = "Data" },
new[] { "Client.Foundation.xConnectPoc.Controllers" });
RouteTable.Routes.MapRoute(
"VisitEnd",
"api/visit/end",
new { controller = "Visit", action = "End" },
new[] { "Client.Foundation.xConnectPoc.Controllers" });
}
}
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<initialize>
<!-- Only enable this in test environments -->
<processor type="Client.Foundation.xConnectPoc.Pipelines.Initialize.InitializeRoutes, Client.Foundation.xConnectPoc" patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']"/>
</initialize>
</pipelines>
</sitecore>
</configuration>

Now we need to set up the logic to get or set the contact and populate its fields. Getting or setting the contact also implies that we need to support contact identification. Modify the code below to suit your needs.

// SpoofVisit.cs
using Sitecore.Analytics;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Model.Entities;
using Sitecore.Analytics.Pipelines.CreateVisits;
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Collection.Model;
using System.Linq;
using System.Net;
namespace Client.Foundation.xConnectPoc.Pipelines.CreateVisit
{
public class SpoofVisit : CreateVisitProcessor
{
public override void Process(CreateVisitArgs args)
{
// We're using an email in the query string as the primary identification mechanism
var email = args.Request.Params["email"];
if (string.IsNullOrWhitespace(email))
{
return;
}
var firstName = args.Request.Params["firstName"];
var lastName = args.Request.Params["lastName"];
// IP address info, if you want
if (!string.IsNullOrWhitespace(args.Request.Params["geoIp"]))
{
var geoip = args.Request.Params["geoIp"];
var ipAddress = IPAddress.Parse(geoIp);
args.Interaction.Ip = ipAddress.GetAddressBytes();
}
args.Session.IdentifyAs("Email", email);
var manager = Sitecore.Configuration.Factory.CreateObject("tracking/contactManager", true) as Sitecore.Analytics.Tracking.ContactManager;
if (manager == null)
{
return;
}
if (Tracker.Current.Contact.IsNew)
{
// Save contact to xConnect; at this point, a contact has an anonymous
// TRACKER IDENTIFIER, which follows a specific format. Do not use the contactId overload
// and make sure you set the ContactSaveMode as demonstrated
Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
manager.SaveContactToCollectionDb(Tracker.Current.Contact);
// Now that the contact is saved, you can retrieve it using the tracker identifier
var trackerIdentifier = new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"));
// Get contact from xConnect, update and save the facet
using (XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())
{
try
{
var contact = client.Get(trackerIdentifier, new Sitecore.XConnect.ContactExpandOptions());
if (contact == null)
{
// Handle it
}
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, new PersonalInformation()
{
FirstName = firstName,
LastName = lastName
});
var emails = new EmailAddressList(new EmailAddress(email, true), "Home");
client.SetFacet(contact, emails);
client.Submit();
// Remove contact data from shared session state - contact will be re-loaded
// during subsequent request with updated facets
manager.RemoveFromSession(Tracker.Current.Contact.ContactId);
Tracker.Current.Session.Contact = manager.LoadContact(Tracker.Current.Contact.ContactId);
}
catch (XdbExecutionException ex)
{
// Manage conflicts / exceptions
Sitecore.Diagnostics.Log.Error(ex.Message, ex, this);
}
}
}
else
{
var anyIdentifier = Tracker.Current.Contact.Identifiers.FirstOrDefault();
// Get contact from xConnect, update and save the facet
using (XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())
{
try
{
var contact = client.Get(new IdentifiedContactReference(anyIdentifier.Source, anyIdentifier.Identifier), new ContactExpandOptions(PersonalInformation.DefaultFacetKey));
if (contact == null)
{
// Handle it
}
if (contact.Personal() != null)
{
if (!string.IsNullOrEmpty(firstName))
contact.Personal().FirstName = firstName;
if (!string.IsNullOrEmpty(lastName))
contact.Personal().LastName = lastName;
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, contact.Personal());
}
else
{
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, new PersonalInformation()
{
FirstName = firstName,
LastName = lastName
});
}
var emailAddressList = contact.GetFacet<EmailAddressList>(EmailAddressList.DefaultFacetKey);
if (emailAddressList != null)
{
// Change facet properties
emailAddressList.PreferredEmail = new EmailAddress(email, true);
emailAddressList.PreferredKey = "Home";
// Set the updated facet
client.SetFacet(contact, EmailAddressList.DefaultFacetKey, emailAddressList);
}
else
{
// Facet is new
var emails = new EmailAddressList(new EmailAddress(email, true), "Home");
client.SetFacet(contact, EmailAddressList.DefaultFacetKey, emails);
}
client.Submit();
// Remove contact data from shared session state - contact will be re-loaded
// during subsequent request with updated facets
manager.RemoveFromSession(Tracker.Current.Contact.ContactId);
Tracker.Current.Session.Contact = manager.LoadContact(Tracker.Current.Contact.ContactId);
}
catch (XdbExecutionException ex)
{
// Manage conflicts / exceptions
Sitecore.Diagnostics.Log.Error(ex.Message, ex, this);
}
}
}
}
}
}

<!-- Patch.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<createVisit>
<!-- Make sure to disable this in PROD -->
<processor type="Client.Foundation.xConnectPoc.Pipelines.CreateVisit.SpoofVisit, Client.Foundation.xConnectPoc" patch:after="processor[@type='Sitecore.Analytics.Pipelines.CreateVisits.XForwardedFor, Sitecore.Analytics']" />
</createVisit>
</pipelines>
</sitecore>
</configuration>

I also found a browser extension for analytics testing that may be worth trying out.

Keep on building,

Marcel


More Posts