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.csusing 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>Note
By default, Sitecore will not create new contacts on instances with a role of ContentManagement (as opposed to Standalone or ContentDelivery).
I also found a browser extension for analytics testing that may be worth trying out.
Keep on building,
Marcel





