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
_25using Sitecore.Analytics;_25using Sitecore.Diagnostics;_25using System.Web.Mvc;_25_25namespace Client.Foundation.xConnectPoc.Controllers_25{_25 public class VisitController : Controller_25 {_25 [HttpGet]_25 public JsonResult Data()_25 {_25 var currentTracker = Tracker.Current;_25 var data = currentTracker.Interaction.ToVisitData();_25 return Json(data, JsonRequestBehavior.AllowGet);_25 }_25_25 [HttpGet]_25 public JsonResult End()_25 {_25 Session.Abandon();_25 Log.Info("Closing xDB session", this);_25 return Json(new { Message = "xDB Session Closed" }, JsonRequestBehavior.AllowGet);_25 }_25 }_25}
_24using System.Web.Mvc;_24using System.Web.Routing;_24using Sitecore.Pipelines;_24_24namespace Client.Foundation.xConnectPoc.Pipelines.Initialize_24{_24 public class InitializeRoutes : Sitecore.Mvc.Pipelines.Loader.InitializeRoutes_24 {_24 public override void Process(PipelineArgs args)_24 {_24 RouteTable.Routes.MapRoute(_24 "VisitData", // Route name_24 "api/visit/data",_24 new { controller = "Visit", action = "Data" },_24 new[] { "Client.Foundation.xConnectPoc.Controllers" });_24_24 RouteTable.Routes.MapRoute(_24 "VisitEnd",_24 "api/visit/end",_24 new { controller = "Visit", action = "End" },_24 new[] { "Client.Foundation.xConnectPoc.Controllers" });_24 }_24 }_24}
_10<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">_10 <sitecore>_10 <pipelines>_10 <initialize>_10 <!-- Only enable this in test environments -->_10 <processor type="Client.Foundation.xConnectPoc.Pipelines.Initialize.InitializeRoutes, Client.Foundation.xConnectPoc" patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']"/>_10 </initialize>_10 </pipelines>_10 </sitecore>_10</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.
_156using Sitecore.Analytics;_156using Sitecore.Analytics.Model;_156using Sitecore.Analytics.Model.Entities;_156using Sitecore.Analytics.Pipelines.CreateVisits;_156using Sitecore.XConnect;_156using Sitecore.XConnect.Client;_156using Sitecore.XConnect.Collection.Model;_156using System.Linq;_156using System.Net;_156_156namespace Client.Foundation.xConnectPoc.Pipelines.CreateVisit_156{_156 public class SpoofVisit : CreateVisitProcessor_156 {_156 public override void Process(CreateVisitArgs args)_156 {_156 // We're using an email in the query string as the primary identification mechanism_156 var email = args.Request.Params["email"];_156 if (string.IsNullOrWhitespace(email))_156 {_156 return;_156 }_156_156 var firstName = args.Request.Params["firstName"];_156 var lastName = args.Request.Params["lastName"];_156_156 // IP address info, if you want_156 if (!string.IsNullOrWhitespace(args.Request.Params["geoIp"]))_156 {_156 var geoip = args.Request.Params["geoIp"];_156 var ipAddress = IPAddress.Parse(geoIp);_156 args.Interaction.Ip = ipAddress.GetAddressBytes();_156 }_156_156 args.Session.IdentifyAs("Email", email);_156_156 var manager = Sitecore.Configuration.Factory.CreateObject("tracking/contactManager", true) as Sitecore.Analytics.Tracking.ContactManager;_156 if (manager == null)_156 {_156 return;_156 } _156_156 if (Tracker.Current.Contact.IsNew)_156 {_156 // Save contact to xConnect; at this point, a contact has an anonymous_156 // TRACKER IDENTIFIER, which follows a specific format. Do not use the contactId overload_156 // and make sure you set the ContactSaveMode as demonstrated_156 Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;_156 manager.SaveContactToCollectionDb(Tracker.Current.Contact);_156_156 // Now that the contact is saved, you can retrieve it using the tracker identifier_156 var trackerIdentifier = new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"));_156_156 // Get contact from xConnect, update and save the facet_156 using (XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())_156 {_156 try_156 {_156 var contact = client.Get(trackerIdentifier, new Sitecore.XConnect.ContactExpandOptions());_156_156 if (contact == null)_156 {_156 // Handle it_156 }_156_156 client.SetFacet(contact, PersonalInformation.DefaultFacetKey, new PersonalInformation()_156 {_156 FirstName = firstName,_156 LastName = lastName_156 });_156_156 var emails = new EmailAddressList(new EmailAddress(email, true), "Home"); _156 client.SetFacet(contact, emails);_156_156 client.Submit();_156_156 // Remove contact data from shared session state - contact will be re-loaded_156 // during subsequent request with updated facets_156 manager.RemoveFromSession(Tracker.Current.Contact.ContactId);_156 Tracker.Current.Session.Contact = manager.LoadContact(Tracker.Current.Contact.ContactId);_156 }_156 catch (XdbExecutionException ex)_156 {_156 // Manage conflicts / exceptions_156 Sitecore.Diagnostics.Log.Error(ex.Message, ex, this);_156 }_156 }_156 }_156 else_156 {_156 var anyIdentifier = Tracker.Current.Contact.Identifiers.FirstOrDefault();_156_156 // Get contact from xConnect, update and save the facet_156 using (XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())_156 {_156 try_156 {_156 var contact = client.Get(new IdentifiedContactReference(anyIdentifier.Source, anyIdentifier.Identifier), new ContactExpandOptions(PersonalInformation.DefaultFacetKey));_156_156 if (contact == null)_156 {_156 // Handle it_156 }_156_156 if (contact.Personal() != null)_156 {_156 if (!string.IsNullOrEmpty(firstName))_156 contact.Personal().FirstName = firstName;_156 if (!string.IsNullOrEmpty(lastName))_156 contact.Personal().LastName = lastName;_156_156 client.SetFacet(contact, PersonalInformation.DefaultFacetKey, contact.Personal());_156 }_156 else_156 {_156 client.SetFacet(contact, PersonalInformation.DefaultFacetKey, new PersonalInformation()_156 {_156 FirstName = firstName,_156 LastName = lastName_156 });_156 }_156_156 var emailAddressList = contact.GetFacet<EmailAddressList>(EmailAddressList.DefaultFacetKey);_156 if (emailAddressList != null)_156 {_156 // Change facet properties_156 emailAddressList.PreferredEmail = new EmailAddress(email, true);_156 emailAddressList.PreferredKey = "Home";_156_156 // Set the updated facet_156 client.SetFacet(contact, EmailAddressList.DefaultFacetKey, emailAddressList);_156 }_156 else_156 {_156 // Facet is new_156 var emails = new EmailAddressList(new EmailAddress(email, true), "Home");_156 client.SetFacet(contact, EmailAddressList.DefaultFacetKey, emails);_156 }_156_156 client.Submit();_156_156 // Remove contact data from shared session state - contact will be re-loaded_156 // during subsequent request with updated facets_156 manager.RemoveFromSession(Tracker.Current.Contact.ContactId);_156 Tracker.Current.Session.Contact = manager.LoadContact(Tracker.Current.Contact.ContactId);_156 }_156 catch (XdbExecutionException ex)_156 {_156 // Manage conflicts / exceptions_156 Sitecore.Diagnostics.Log.Error(ex.Message, ex, this);_156 }_156 }_156 }_156 }_156 }_156}
_10<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">_10 <sitecore>_10 <pipelines>_10 <createVisit>_10 <!-- Make sure to disable this in PROD -->_10 <processor type="Client.Foundation.xConnectPoc.Pipelines.CreateVisit.SpoofVisit, Client.Foundation.xConnectPoc" patch:after="processor[@type='Sitecore.Analytics.Pipelines.CreateVisits.XForwardedFor, Sitecore.Analytics']" />_10 </createVisit>_10 </pipelines>_10 </sitecore>_10</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