Problem Personalizing with Salesforce Marketing Campaigns in Sitecore

I recently came across an issue synchronizing Salesforce Marketing Campaigns with Sitecore using Sitecore 8.2 and Sitecore Salesforce Connect 1.4.1. While troubleshooting the issue, I discovered a bug in one of the Sitecore Salesforce Connect DLLs. If you discover a similar problem and want to see how it was resolved, please read on.

Sitecore Contact Not Part of Salesforce Marketing Campaign

Background

I recently worked on a project that was implementing personalized content from Sitecore in a Xamarin based mobile application. In this scenario, we wanted to create a unique experience for visitors that were part of a Salesforce campaign using the Sitecore xDB personalization engine. We eventually got to a point when we could see that the Salesforce Campaigns were synchronizing properly, but the personalization rules based on campaign membership were not functioning.

Investigation

The adventure began like many complex Sitecore problems by decompiling a DLL. Specifically, the Salesforce DLL, Sitecore.Analytics.Salesforce.dll, was decompiled to discover that the code uses a field with name "Campaigns" on the campaign item to get the ID of the campaign. No field with that name exists on the Sitecore campaign item. The actual field name is "Marketing List ID".

The CampaignConditionForPersonalization class inherits BaseMembershipCondition<T> from Sitecore.Connect.Crm.Local that takes field name in the constructor and uses it in GetEntityId method to get the campaign ID from the campaign Sitecore item and passes that value to the DoesMatch method.

 public abstract class BaseMembershipCondition<T> : OperatorCondition<T> where T : RuleContext
  {
    protected BaseMembershipCondition(string fieldName)
    {
      this.OperatorId = "{10537C58-1684-4CAB-B4C0-40C10907CE31}";
      this.FieldName = fieldName;
    }

    public string ContactFacetName { get; set; }

    public string ExternalEntityId { get; set; }

    public string FieldName { get; private set; }

    private string GetEntityId(string externalEntityItemId)
    {
      ID result = ID.Null;
      if (ID.TryParse(externalEntityItemId, out result))
      {
        Database database = Database.GetDatabase(AnalyticsSettings.DefaultDefinitionDatabase);
        if (database != null)
        {
          Item obj = database.GetItem(result);
          if (obj != null)
            return obj[this.FieldName];
        }
      }
      return (stringnull;
    }

    protected override bool Execute(T ruleContext)
    {
      if ((object) ruleContext == null)
        throw new ArgumentNullException(nameof (ruleContext));
      ITracker current = Tracker.Current;
      if (current == null)
        throw new InvalidOperationException("Sitecore.Analytics.Tracker.Current is null.");
      Contact contact = current.Contact;
      if (contact == null)
        throw new InvalidOperationException("Sitecore.Analytics.Tracker.Current.Contact is null.");
      return this.DoesMatch(this.GetEntityId(this.ExternalEntityId), contact);
    }

    protected abstract bool DoesMatch(string entityId, Contact contact);
  }


CampaignConditionForPersonalization class in DoesMatch method checks if current contact belongs to campaign. The problem is that the field name passed to the base constructor is "Campaigns".

public class CampaignConditionForPersonalization<T> : BaseMembershipCondition<T> where T : RuleContext
  {
    public CampaignConditionForPersonalization()
      base("Campaigns")
    {
      this.ContactFacetName = "Salesforce";
    }

    protected override bool DoesMatch(string entityId, Contact contact)
    {
      ISalesforceContactData facet = contact.GetFacet<ISalesforceContactData>(this.ContactFacetName);
      if (facet == null)
        return false;
      return facet.Campaigns.Contains(entityId);
    }
  }

Resolution

To prove that this was the problem, I added a field named "Campaigns" to the campaign data template and manually copied the value from field "Marketing List ID" to the new field. Magic! Everything began to work. Note that this solution will not permanently resolve the problem since the campaign synchronization would wipe it out the next time it runs. Time to implement a permanent fix.




Comments

Popular posts from this blog

Why a better search experience builds your brand

Sitecore JSS Meets TeamCity

Strategy to Separate Code from Content