Quantcast
Channel: briancaos – Brian Pedersen's Sitecore and .NET Blog
Viewing all articles
Browse latest Browse all 276

Sitecore Contacts – Create and save contacts to and from xDB (MongoDB)

$
0
0

The Sitecore Contact is the cornerstone of the Sitecore Experience Platform and is the place where you store every data you know about any contact, named and anonymous.

This library was made by my colleague Peter Wind for a project we are both working on. In some cases we need to manipulate a contact that is not currently identified, for example when updating contact facets from imported data.
To do so you need to find the contact in xDB. If it does not exist, you need to create the contact. And when you are done updating the contact, you must save the data back to xDB and release the lock on the contact.

Any Contact in Sitecore is identified by a string. There is no connection between the user database (the .NET Security Provider) and a contact other than the one you make yourself. The username IS your key, and the key should be unique. You must be careful when identifying a Sitecore User, and never identify extranet\anonymous.

A WORD OF CAUTION:

The code uses some direct calls to the Sitecore Analytics and thus explores some undocumented features that was not meant to be called directly. The code is therefore a result of trial-and-error plus help from Sitecore Support. In other words: Just because it works on my 500.000+ contacts, it might fail on yours.

ENOUGH TALK LETS CODE:

using System;
using System.Collections.Generic;
using System.Text;
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;
using Sitecore.Data;
using Sitecore.Diagnostics;

namespace MyNamespace
{
  public class ExtendedContactRepository
  {
    public 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
      {
        Contact contact = contactRepository.LoadContactReadOnly(userName);
        LockAttemptResult<Contact> lockAttempt;

        if (contact == null)
          lockAttempt = new LockAttemptResult<Contact>(LockAttemptStatus.NotFound, null, null);
        else
          lockAttempt = 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);
      }
    }

    public void ReleaseAndSaveContact(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 Contact GetOrCreateContact(string userName, LockAttemptResult<Contact> lockAttempt, ContactRepository contactRepository, ContactManager contactManager)
    {
      switch (lockAttempt.Status)
      {
        case LockAttemptStatus.Success:
          Contact lockedContact = lockAttempt.Object;
          lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave;
          return lockedContact;

        case LockAttemptStatus.NotFound:
          Contact createdContact = CreateContact(userName, contactRepository);
          contactManager.FlushContactToXdb(createdContact);
          return GetOrCreateContact(userName);

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

    private Contact CreateContact(string userName, ContactRepository contactRepository)
    {
      Contact contact = contactRepository.CreateContact(ID.NewID);
      contact.Identifiers.Identifier = userName;
      contact.System.Value = 0;
      contact.System.VisitCount = 0;
      contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
      return contact;
    }

    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 void ClearSharedSessionLocks(ContactManager manager, 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);
    }
  }
}

HOW TO USE THE CODE:

// Create an instance of the repository
ExtendedContactRepository extendedContactRepository = new ExtendedContactRepository();
// Get a contact by a username
Contact contact = extendedContactRepository.GetOrCreateContact(userName);

// Do some code that updates the contact
// For example update a facet:
// https://briancaos.wordpress.com/2015/07/16/sitecore-contact-facets-create-your-own-facet/

// Save the contact
extendedContactRepository.ReleaseAndSaveContact(contact);

SOME EXPLANATION:

The 2 public methods, GetOrCreateContact() and ReleaseAndSaveContact(), are the getter and setter methods.

GetOrCreateContact() tries to get a lock on a Contact. If the lock is successful, a Contact is found and the Contact can be returned.  If not, no Contact is found and we create one.

ReleaseAndSaveContact() saves and releases the contact which means that the data is stored in the Shared Session, and the contact is released; the ClearSharedSessionLocks() attempts to release the locks from the Sitecore Shared Session  State Database. Please note that the data is still not stored directly in xDB but in the Shared Session, and data is flushed when the session expires. The trick is that we open the contact in write-mode, and release the Contact after the update, making it available immediately after by other threads.

Generally, when using the Sitecore ContactManager(), data is not manipulated directly. Only when using the Sitecore ContactRepository() you update xDB directly, but Sitecore does not recommend this, as it may have unwanted side effects.

MORE TO READ:

 



Viewing all articles
Browse latest Browse all 276

Trending Articles