Add Contact Id in Interactions for Non-Logged in Visitors

Hi Folks,

In previous post https://sitecorerocksblog.wordpress.com/2017/10/17/sitecore-web-forms-for-marketers-creating-custom-save-action-for-adding-contact-profile-for-non-logged-in-visitors/ we have added contact for non logged-in user in experience profile. Now we had a requirement to add same contact id in Interactions table as well.

Firstly I checked Interactions table in mongo db after submitting a form. For this I used Robomongo tool and checked Interactions table. I saw last entry in the table and saw that contact id is different in this entry then our contact id.

See below image for contact id (Contacts Table)-

ContactEntry

See below image for interaction entry (Interactions Table)-

InteractionsEntry

 

In above 2 images you can clearly see contact id is different. So, for this I searched on google and found a nice article of Brian Pedersen , so I took help from this post and added code for this in CustomUpdateContactDetails class (added this class in previous post to add contact id). I have added below line –

// Identify contact for interactions
if (!Tracker.IsActive)
return;
Tracker.Current.Session.Identify(Sitecore.Context.User.Name.ToLower() == “extranet\\anonymous”
? email
: Sitecore.Context.User.Name);

 

CustomUpdateContactDetails class including above code –

using Sitecore.Data;
using Sitecore.WFFM.Abstractions.Actions;
using Sitecore.WFFM.Actions.Base;
using Sitecore.Diagnostics;
using Sitecore.WFFM.Abstractions.Analytics;
using Sitecore.WFFM.Abstractions.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Script.Serialization;
using System.Web;
using Sitecore.Analytics;
using Sitecore.Analytics.Data;
using Sitecore.Analytics.DataAccess;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Analytics.Tracking.SharedSessionState;
using Sitecore.Configuration;

namespace xyz.WFFMSaveAction
{
public class CustomUpdateContactDetails : WffmSaveAction
{
private const int _MAX_RETRIES = 10;
private int _retries;

private readonly IAnalyticsTracker analyticsTracker;

private readonly IAuthentificationManager authentificationManager;

private readonly ILogger logger;

private readonly IFacetFactory facetFactory;

public string Mapping
{
get;
set;
}

public CustomUpdateContactDetails(IAnalyticsTracker analyticsTracker, IAuthentificationManager authentificationManager, ILogger logger, IFacetFactory facetFactory)
{
Assert.IsNotNull(analyticsTracker, “analyticsTracker”);
Assert.IsNotNull(authentificationManager, “authentificationManager”);
Assert.IsNotNull(logger, “logger”);
Assert.IsNotNull(facetFactory, “facetFactory”);
this.analyticsTracker = analyticsTracker;
this.authentificationManager = authentificationManager;
this.logger = logger;
this.facetFactory = facetFactory;
}

public override void Execute(ID formId, AdaptedResultList adaptedFields, ActionCallContext actionCallContext = null, params object[] data)
{
this.UpdateContact(adaptedFields);
}

protected virtual void UpdateContact(AdaptedResultList fields)
{
Assert.ArgumentNotNull(fields, “adaptedFields”);
Assert.IsNotNullOrEmpty(this.Mapping, “Empty mapping xml.”);
Assert.IsNotNull(this.analyticsTracker.CurrentContact, “Tracker.Current.Contact”);

var email = string.Empty;
foreach (AdaptedControlResult acr in fields)
{
if (acr.FieldName == “examplemailcom”)
{
email = acr.Value;
}
}

if (!this.authentificationManager.IsActiveUserAuthenticated)
{
var objContact = GetOrCreateContact(email);

IEnumerable<FacetNode> enumerable = this.ParseMapping(this.Mapping, fields);
IContactFacetFactory contactFacetFactory = this.facetFactory.GetContactFacetFactory();
foreach (FacetNode current in enumerable)
{
contactFacetFactory.SetFacetValue(objContact, current.Key, current.Path, current.Value, true);
}
ReleaseAndSaveContact(objContact);

// Identify contact for interactions
if (!Tracker.IsActive)
return;
Tracker.Current.Session.Identify(Sitecore.Context.User.Name.ToLower() == “extranet\\anonymous”
? email
: Sitecore.Context.User.Name);
}
}

public IEnumerable<FacetNode> ParseMapping(string mapping, AdaptedResultList adaptedFieldResultList)
{
Assert.ArgumentNotNullOrEmpty(mapping, “mapping”);
Assert.ArgumentNotNull(adaptedFieldResultList, “adaptedFieldResultList”);
JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
object[] source = (object[])javaScriptSerializer.Deserialize(mapping, typeof(object));
return (from Dictionary<string, object> item in source
let itemValue = item[“value”].ToString()
let itemId = (item.ContainsKey(“id”) && item[“id”] != null) ? item[“id”].ToString() : “Preferred”
let value = adaptedFieldResultList.GetValueByFieldID(ID.Parse(item[“key”].ToString()))
where !string.IsNullOrEmpty(value)
select new FacetNode(itemId, itemValue, value)).ToList<FacetNode>();
}

public Sitecore.Analytics.Tracking.Contact GetOrCreateContact(string userName)
{
if (IsContactInSession(userName))
return Tracker.Current.Session.Contact;

ContactRepository contactRepository = Factory.CreateObject(“tracking/contactRepository”, true) as ContactRepository;
ContactManager contactManager = Factory.CreateObject(“tracking/contactManager”, true) as ContactManager;

Assert.IsNotNull(contactRepository, “contactRepository”);
Assert.IsNotNull(contactManager, “contactManager”);

try
{
Sitecore.Analytics.Tracking.Contact contact = contactRepository.LoadContactReadOnly(userName);
LockAttemptResult<Sitecore.Analytics.Tracking.Contact> lockAttempt;

lockAttempt = contact == null ? new LockAttemptResult<Sitecore.Analytics.Tracking.Contact>(LockAttemptStatus.NotFound, null, null) : contactManager.TryLoadContact(contact.ContactId);

return GetOrCreateContact(userName, lockAttempt, contactRepository, contactManager);
}
catch (Exception ex)
{
throw new Exception(this.GetType() + ” Contact could not be loaded/created – ” + userName, ex);
}
}

private bool IsContactInSession(string userName)
{
var tracker = Tracker.Current;

if (tracker != null &&
tracker.IsActive &&
tracker.Session != null &&
tracker.Session.Contact != null &&
tracker.Session.Contact.Identifiers != null &&
tracker.Session.Contact.Identifiers.Identifier != null &&
tracker.Session.Contact.Identifiers.Identifier.Equals(userName, StringComparison.InvariantCultureIgnoreCase))
return true;

return false;
}

private Sitecore.Analytics.Tracking.Contact GetOrCreateContact(string userName, LockAttemptResult<Sitecore.Analytics.Tracking.Contact> lockAttempt, ContactRepository contactRepository, ContactManager contactManager)
{
switch (lockAttempt.Status)
{
case LockAttemptStatus.Success:
Sitecore.Analytics.Tracking.Contact lockedContact = lockAttempt.Object;
lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave;
return lockedContact;

case LockAttemptStatus.NotFound:
Sitecore.Analytics.Tracking.Contact createdContact = CreateContact(userName, contactRepository);
contactManager.FlushContactToXdb(createdContact);
// Avoid going into an infinite loop.
_retries++;
if (_retries >= _MAX_RETRIES)
throw new Exception(string.Format(“ExtendedContactRepository: Contact {0} could not be created. “, userName));
return GetOrCreateContact(userName);

default:
throw new Exception(this.GetType() + ” Contact could not be locked – ” + userName);
}
}

private Sitecore.Analytics.Tracking.Contact CreateContact(string userName, ContactRepository contactRepository)
{
Sitecore.Analytics.Tracking.Contact contact = contactRepository.CreateContact(ID.NewID);
contact.Identifiers.Identifier = userName;
contact.Identifiers.IdentificationLevel = Sitecore.Analytics.Model.ContactIdentificationLevel.Known;
contact.System.Value = 0;
contact.System.VisitCount = 0;
contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
return contact;
}

public void ReleaseAndSaveContact(Sitecore.Analytics.Tracking.Contact contact)
{
ContactManager manager = Factory.CreateObject(“tracking/contactManager”, true) as ContactManager;
if (manager == null)
throw new Exception(this.GetType() + ” Could not instantiate ” + typeof(ContactManager));
manager.SaveAndReleaseContact(contact);
ClearSharedSessionLocks(manager, contact);
}

private void ClearSharedSessionLocks(ContactManager manager, Sitecore.Analytics.Tracking.Contact contact)
{
if (HttpContext.Current != null && HttpContext.Current.Session != null)
return;

var sharedSessionStateManagerField = manager.GetType().GetField(“sharedSessionStateManager”, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.IsNotNull(sharedSessionStateManagerField, “Didn’t find field ‘sharedSessionStateManager’ in type ‘{0}’.”, typeof(ContactManager));
var sssm = (SharedSessionStateManager)sharedSessionStateManagerField.GetValue(manager);
Assert.IsNotNull(sssm, “Shared session state manager field value is null.”);

var contactLockIdsProperty = sssm.GetType().GetProperty(“ContactLockIds”, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.IsNotNull(contactLockIdsProperty, “Didn’t find property ‘ContactLockIds’ in type ‘{0}’.”, sssm.GetType());
var contactLockIds = (Dictionary<Guid, object>)contactLockIdsProperty.GetValue(sssm);
Assert.IsNotNull(contactLockIds, “Contact lock IDs property value is null.”);
contactLockIds.Remove(contact.ContactId);
}
}
}

 

Now you can see in below image contact id is same.

interactionEntry2

And that’s it. So now we have added contact in experience profile as well as added same contact id in interactions table, that was our requirement.

Happy coding 🙂

 

Advertisements

Sitecore Web Forms for Marketers – Creating Custom Save Action for Adding Contact Profile for Non-Logged In Visitors

Hi Folks,

In one of our website there was a requirement that client wanted to see contact in Experience Profile even if user is not authenticated. There is already a save action Update Contact Details by default provided by web form for marketers module. This save action will allow us to update the experience database contact information of a logged in user when they fill out the form. We simply map each form field to experience database contact field facet and that’s how the web form will know which data to store.

updatecontactdetailsaction

What if we want to use this same functionality to update the contact details of an anonymous user who fills out our web form from a campaign we are running? This is currently not something that WFFM allows us to do out of the box.

So we decided to create custom contact details action that will save the information in Experience Profile of a user who is not logged into the website. Below image is for custom save action. Path is (/sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Save Actions/)

customContactDetailsAction

For this first we have created a patch config for save action as below –

<configuration xmlns:patch=”http://www.sitecore.net/xmlconfig/&#8221; xmlns:set=”http://www.sitecore.net/xmlconfig/set/”&gt;
<sitecore>
<wffm>
<!–Save actions constructor configuration–>
<actions>
<updateCustomContactDetails type=”xyz.WFFMSaveAction.CustomUpdateContactDetails, xyz”>
<param name=”analyticsTracker” ref=”/sitecore/wffm/analytics/analyticsTracker” />
<param name=”authentificationManager” ref=”/sitecore/wffm/authentificationManager” />
<param name=”logger” ref=”/sitecore/wffm/logger” />
<param name=”facetFactory” ref=”/sitecore/wffm/analytics/facetFactory” />
</updateCustomContactDetails>
</actions>
</wffm>
</sitecore>
</configuration>

 

Now we have created class CustomUpdateContactDetails. We took reference from the post Brian’s Post to create contact. Here is the code –

using Sitecore.Data;
using Sitecore.WFFM.Abstractions.Actions;
using Sitecore.WFFM.Actions.Base;
using Sitecore.Diagnostics;
using Sitecore.WFFM.Abstractions.Analytics;
using Sitecore.WFFM.Abstractions.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Script.Serialization;
using System.Web;
using Sitecore.Analytics;
using Sitecore.Analytics.Data;
using Sitecore.Analytics.DataAccess;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Analytics.Tracking.SharedSessionState;
using Sitecore.Configuration;

namespace xyz.WFFMSaveAction
{
public class CustomUpdateContactDetails : WffmSaveAction
{
private const int _MAX_RETRIES = 10;
private int _retries;

private readonly IAnalyticsTracker analyticsTracker;

private readonly IAuthentificationManager authentificationManager;

private readonly ILogger logger;

private readonly IFacetFactory facetFactory;

public string Mapping
{
get;
set;
}

public CustomUpdateContactDetails(IAnalyticsTracker analyticsTracker, IAuthentificationManager authentificationManager, ILogger logger, IFacetFactory facetFactory)
{
Assert.IsNotNull(analyticsTracker, “analyticsTracker”);
Assert.IsNotNull(authentificationManager, “authentificationManager”);
Assert.IsNotNull(logger, “logger”);
Assert.IsNotNull(facetFactory, “facetFactory”);
this.analyticsTracker = analyticsTracker;
this.authentificationManager = authentificationManager;
this.logger = logger;
this.facetFactory = facetFactory;
}

public override void Execute(ID formId, AdaptedResultList adaptedFields, ActionCallContext actionCallContext = null, params object[] data)
{
this.UpdateContact(adaptedFields);
}

protected virtual void UpdateContact(AdaptedResultList fields)
{
Assert.ArgumentNotNull(fields, “adaptedFields”);
Assert.IsNotNullOrEmpty(this.Mapping, “Empty mapping xml.”);
Assert.IsNotNull(this.analyticsTracker.CurrentContact, “Tracker.Current.Contact”);

var email = string.Empty;
foreach (AdaptedControlResult acr in fields)
{
if (acr.FieldName == “abc”)
{
email = acr.Value;
}
}

if (!this.authentificationManager.IsActiveUserAuthenticated)
{
var objContact = GetOrCreateContact(email);

IEnumerable<FacetNode> enumerable = this.ParseMapping(this.Mapping, fields);
IContactFacetFactory contactFacetFactory = this.facetFactory.GetContactFacetFactory();
foreach (FacetNode current in enumerable)
{
contactFacetFactory.SetFacetValue(objContact, current.Key, current.Path, current.Value, true);
}
ReleaseAndSaveContact(objContact);
}
}

public IEnumerable<FacetNode> ParseMapping(string mapping, AdaptedResultList adaptedFieldResultList)
{
Assert.ArgumentNotNullOrEmpty(mapping, “mapping”);
Assert.ArgumentNotNull(adaptedFieldResultList, “adaptedFieldResultList”);
JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
object[] source = (object[])javaScriptSerializer.Deserialize(mapping, typeof(object));
return (from Dictionary<string, object> item in source
let itemValue = item[“value”].ToString()
let itemId = (item.ContainsKey(“id”) && item[“id”] != null) ? item[“id”].ToString() : “Preferred”
let value = adaptedFieldResultList.GetValueByFieldID(ID.Parse(item[“key”].ToString()))
where !string.IsNullOrEmpty(value)
select new FacetNode(itemId, itemValue, value)).ToList<FacetNode>();
}

public Sitecore.Analytics.Tracking.Contact GetOrCreateContact(string userName)
{
if (IsContactInSession(userName))
return Tracker.Current.Session.Contact;

ContactRepository contactRepository = Factory.CreateObject(“tracking/contactRepository”, true) as ContactRepository;
ContactManager contactManager = Factory.CreateObject(“tracking/contactManager”, true) as ContactManager;

Assert.IsNotNull(contactRepository, “contactRepository”);
Assert.IsNotNull(contactManager, “contactManager”);

try
{
Sitecore.Analytics.Tracking.Contact contact = contactRepository.LoadContactReadOnly(userName);
LockAttemptResult<Sitecore.Analytics.Tracking.Contact> lockAttempt;

lockAttempt = contact == null ? new LockAttemptResult<Sitecore.Analytics.Tracking.Contact>(LockAttemptStatus.NotFound, null, null) : contactManager.TryLoadContact(contact.ContactId);

return GetOrCreateContact(userName, lockAttempt, contactRepository, contactManager);
}
catch (Exception ex)
{
throw new Exception(this.GetType() + ” Contact could not be loaded/created – ” + userName, ex);
}
}

private bool IsContactInSession(string userName)
{
var tracker = Tracker.Current;

if (tracker != null &&
tracker.IsActive &&
tracker.Session != null &&
tracker.Session.Contact != null &&
tracker.Session.Contact.Identifiers != null &&
tracker.Session.Contact.Identifiers.Identifier != null &&
tracker.Session.Contact.Identifiers.Identifier.Equals(userName, StringComparison.InvariantCultureIgnoreCase))
return true;

return false;
}

private Sitecore.Analytics.Tracking.Contact GetOrCreateContact(string userName, LockAttemptResult<Sitecore.Analytics.Tracking.Contact> lockAttempt, ContactRepository contactRepository, ContactManager contactManager)
{
switch (lockAttempt.Status)
{
case LockAttemptStatus.Success:
Sitecore.Analytics.Tracking.Contact lockedContact = lockAttempt.Object;
lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave;
return lockedContact;

case LockAttemptStatus.NotFound:
Sitecore.Analytics.Tracking.Contact createdContact = CreateContact(userName, contactRepository);
contactManager.FlushContactToXdb(createdContact);
// Avoid going into an infinite loop.
_retries++;
if (_retries >= _MAX_RETRIES)
throw new Exception(string.Format(“ExtendedContactRepository: Contact {0} could not be created. “, userName));
return GetOrCreateContact(userName);

default:
throw new Exception(this.GetType() + ” Contact could not be locked – ” + userName);
}
}

private Sitecore.Analytics.Tracking.Contact CreateContact(string userName, ContactRepository contactRepository)
{
Sitecore.Analytics.Tracking.Contact contact = contactRepository.CreateContact(ID.NewID);
contact.Identifiers.Identifier = userName;
contact.Identifiers.IdentificationLevel = Sitecore.Analytics.Model.ContactIdentificationLevel.Known;
contact.System.Value = 0;
contact.System.VisitCount = 0;
contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
return contact;
}

public void ReleaseAndSaveContact(Sitecore.Analytics.Tracking.Contact contact)
{
ContactManager manager = Factory.CreateObject(“tracking/contactManager”, true) as ContactManager;
if (manager == null)
throw new Exception(this.GetType() + ” Could not instantiate ” + typeof(ContactManager));
manager.SaveAndReleaseContact(contact);
ClearSharedSessionLocks(manager, contact);
}

private void ClearSharedSessionLocks(ContactManager manager, Sitecore.Analytics.Tracking.Contact contact)
{
if (HttpContext.Current != null && HttpContext.Current.Session != null)
return;

var sharedSessionStateManagerField = manager.GetType().GetField(“sharedSessionStateManager”, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.IsNotNull(sharedSessionStateManagerField, “Didn’t find field ‘sharedSessionStateManager’ in type ‘{0}’.”, typeof(ContactManager));
var sssm = (SharedSessionStateManager)sharedSessionStateManagerField.GetValue(manager);
Assert.IsNotNull(sssm, “Shared session state manager field value is null.”);

var contactLockIdsProperty = sssm.GetType().GetProperty(“ContactLockIds”, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.IsNotNull(contactLockIdsProperty, “Didn’t find property ‘ContactLockIds’ in type ‘{0}’.”, sssm.GetType());
var contactLockIds = (Dictionary<Guid, object>)contactLockIdsProperty.GetValue(sssm);
Assert.IsNotNull(contactLockIds, “Contact lock IDs property value is null.”);
contactLockIds.Remove(contact.ContactId);
}
}
}

After this code we tried to submit a form on the website and after some time searched for our contact in Experience Profile, we found our contact there, that’s it, we are able to add non-logged in user contact in Experience Profile. If you have any question then please contact me.

If you want this newly created contact id entry in Interactions table in mongodb then read my next blog.

Happy coding 🙂