Sitecore xConnect How-to: Service Plugin Implementation

Sitecore xConnect: Service plugin implementation

Have you ever thought about implementing a service plugin for Sitecore xConnect? You probably know that there is a lack of information on how to do that. Let’s figure it out together!

What does the service plugin mean in general? It is something like a pipeline in Sitecore which runs for each request to the xConnect and where you can hook into the following events raised by xConnect:

  • Batch executed
  • Batch executing
  • Batch execution failed
  • Operation added
  • Operation completed
  • Operation executing

Looking at the list above we can see two main terms: Batch (Sitecore.XConnect.Operations.XdbOperationBatch) and Operation (Sitecore.XConnect.Operations.IXdbOperation). Each communication with xConnect happens by batches. At the same time, each batch can contain any number of operations. You can read more about batches by the following link: Sitecore Batching.

For example, when we search for a contact in the Experience Profile, a batch with only one Sitecore.XConnect.Operations.XdbSearchOperation<Contact> operation will be executed in xConnect.

Requesting a contact from xConnect by ContactId will initialize the batch with Sitecore.XConnect.Web.Infrastructure.Operations.GetEntitiesOperation<Contact> and Sitecore.XConnect.Operations.GetEntityOperation<Contact> operations, where the first operation represents a Direct Operation and the second one is an dependent operation.

It means that our request initializes a direct operation which will return or add some data. At the same time, a direct operation can add dependent operations for “helping” achieve their goals.

In case of creating a new contact, two batches would be launched: first Sitecore tries to retrieve contact by an identifier, then, if a contact doesn’t exist, Sitecore creates it by running the batch with four direct operations: Sitecore.XConnect.Operations.AddContactOperation and two Sitecore.XConnect.Operations.SetFacetOperation<TFacet> for setting data in the common facets like the PhoneNumbers, Personal, and Emails. Code below describes a contact creation process and shows where the batch executions initiates:

     
	//Creating new contact identify reference
	IdentifiedContactReference reference = new IdentifiedContactReference(source, identifier);

	//Trying to retrieve contact by identifier.
	//At this step, the first separate batch will be launched.
	var contactTask = client.GetAsync(
		reference,
		new ContactExpandOptions(
			PersonalInformation.DefaultFacetKey,
			EmailAddressList.DefaultFacetKey,
			PhoneNumberList.DefaultFacetKey)
	);

	Contact existingContact = await contactTask;

	//Checking if contact exists. If it is, we don't need to continue the contact creation process
	if (existingContact != null)
	{
		return false;
	}

	//Adding an identifier for new contact
	var contactIdentifier = new[]
	{
		new ContactIdentifier(source, identifier, ContactIdentifierType.Known)
	};

	//Creating a new contact object
	Contact contact = new Contact(contactIdentifier);

	//Creating a new Personal Information facet and filling a data
	var personal = new PersonalInformation
	{
		FirstName = firstName,
		LastName = lastName,
		Title = title
	};
	
	//Creating a new Phone Numbers facet and filling a data
	var preferredPhoneNumber = new PhoneNumber(string.Empty, phone);
	var phoneNumbers = new PhoneNumberList(preferredPhoneNumber, "Work");

	//Creating a new Emails facet and filling a data
	var preferredEmail = new EmailAddress(email, true);
	var emails = new EmailAddressList(preferredEmail, "Work");

	//The following line looks like it adds the contact. But in fact it just adds the Sitecore.XConnect.Operations.AddContactOperation operation to the current context batch.
	client.AddContact(contact);
	
	//Adding Sitecore.XConnect.Operations.SetFacetOperation<Sitecore.XConnect.Collection.Model.PhoneNumberList> operation to the current context batch.
	client.SetPhoneNumbers(contact, phoneNumbers);
	
	//Adding Sitecore.XConnect.Operations.SetFacetOperation<Sitecore.XConnect.Collection.Model.PersonalInformation> operation to the current context batch.
	client.SetPersonal(contact, personal);
	
	//Adding Sitecore.XConnect.Operations.SetFacetOperation<Sitecore.XConnect.Collection.Model.EmailAddressList> operation to the current context batch.
	client.SetEmails(contact, emails);

	//Initiating execition of the context batch.
	await client.SubmitAsync();

In general, service plugin represents a “subscriber” to xConnect events where they will be executed in the following order:

  1. Operation added
  2. Batch executing
  3. Operation executing
  4. Operation completed

The events above would be applied to each operation that adds to the batch.

  1. Batch executed - the last endpoint and runs ones for the batch.
  2. Batch execution failed - would be fired if something was wrong during batch execution.

At each step, you can access current operation and/or batch. Batch, at the same time, has a list of operations added to it. It allows us to control every stage of operation processing, extend it, validate, subscribe to operation events, log something and so on.

In my case, the goal was pretty simple. I needed to track and send a request to a third party API when a new contact is created. To achieve that I decided to create a plugin and subscribe to the “Operation completed” event:

     
using System;
using Serilog;
using Sitecore.Framework.Messaging;
using Sitecore.XConnect.Operations;
using Sitecore.XConnect.Service.Plugins;
using Sitecore.XConnect.ServicePlugins.ContactTracker.Models;

namespace Sitecore.XConnect.ServicePlugins.ContactTracker.Plugins
{
    public class ContactCreationTrackerPlugin : IXConnectServicePlugin, IDisposable
    {
        private XdbContextConfiguration _config;
        private readonly IMessageBus _messageBus;

        public ContactCreationTrackerPlugin(IMessageBus messageBus)
        {
            _messageBus = messageBus;
            Log.Information("Create {0}", nameof(ContactCreationTrackerPlugin));
        }

        /// Subscribes to events the current plugin listens to.
        /// 
        ///   A  object that provides access to the configuration settings.
        /// 
        /// 
        ///   Argument  is a null reference.
        /// 
        public void Register(XdbContextConfiguration config)
        {
            Log.Information("Register {0}", nameof(ContactCreationTrackerPlugin));
            _config = config;
            RegisterEvents();
        }

        /// 
        ///   Unsubscribes from events the current plugin listens to.
        /// 
        public void Unregister()
        {
            Log.Information("Unregister {0}", nameof(ContactCreationTrackerPlugin));
            UnregisterEvents();
        }

        private void RegisterEvents()
        {
            //Subscribe OperationCompleted event
            _config.OperationCompleted += OnOperationCompleted;
        }

        private void UnregisterEvents()
        {
            //Unsubscribe OperationCompleted event
            _config.OperationCompleted -= OnOperationCompleted;
        }

        /// 
        /// Handles the event that is generated when an operation completes.
        /// 
        /// The  that generated the event.
        /// A  object that provides information about the event.
        private void OnOperationCompleted(object sender, XdbOperationEventArgs xdbOperationEventArgs)
        {
            //Check if no exceptions are occurred during executing the operation. If it is, it will not guarantee that contact was created.
            if (xdbOperationEventArgs.Operation.Exception != null)
                return;

            //We need to track only the AddContactOperation operation. Trying to cast to a necessary type.
            var operation = xdbOperationEventArgs.Operation as AddContactOperation;

            //Checking if it is the necessary operation and if an operation execution status is "Succeeded"
            if (operation?.Status == XdbOperationStatus.Succeeded && operation.Entity.Id.HasValue)
            {

                //Sending a message with an id of newly created contact. 
                _messageBus.SendAsync(new CreateDynamicsContactMessage
                {
                    ContactId = operation.Entity.Id.Value
                });
            }
        }

        /// 
        ///   Releases managed and unmanaged resources used by the current class instance.
        /// 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// 
        ///   Releases managed and unmanaged resources used by the current class instance.
        /// 
        /// 
        ///   Indicates whether the current method was called from explicitly or implicitly during finalization.
        /// 
        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
                return;
            Log.Information("Dispose {0}", nameof(ContactCreationTrackerPlugin));
            _config = null;
        }
    }
}

You can download ContactCreationTrackerPlugin.cs file on GIT repository.

After we add a plugin implementation, we will need to register it in configuration:

    
 <Sitecore>
    <XConnect>
      <Collection>
        <Services>             
		  <Sitecore.XConnect.ServicePlugins.ContactTracker.Plugins.ContactCreationTrackerPlugin>
            <Type>Sitecore.XConnect.ServicePlugins.ContactTracker.Plugins.ContactCreationTrackerPlugin, Sitecore.XConnect.ServicePlugins.ContactTracker</Type>
            <As>Sitecore.XConnect.Service.Plugins.IXConnectServicePlugin, Sitecore.XConnect.Service.Plugins</As>
            <LifeTime>Singleton</LifeTime>
          </Sitecore.XConnect.ServicePlugins.ContactTracker.Plugins.ContactCreationTrackerPlugin>
        </Services>
      </Collection>
    </XConnect>
  </Sitecore>
</Settings>

You can download sc.Custom.Service.Plugins.xml file on GIT repository.

Then we need to copy a .dll file with your plugin to the bin directory of your xConnect instance and the sc.Custom.Service.Plugins.xml file to the xConnect\App_data\Config\sitecore\Collection folder.

And the end of article I would like to provide probably a full list of operations that you can catch in the plugin:

  • AddContactIdentifierOperation
  • AddContactOperation
  • AddDeviceProfileOperation
  • AddInteractionOperation
  • ClearFacetOperation
  • ClearFacetOperation<TFacet>, where TFacet : Sitecore.XConnect.Facet
  • CreateContactCursorOperation
  • CreateInteractionCursorOperation
  • GetEntityOperation<TEntity>
  • GetFacetOperation<TFacet>, where TFacet : Sitecore.XConnect.Facet
  • MergeContactsOperation
  • PatchFacetOperation<TFacet>, where TFacet : Sitecore.XConnect.Facet
  • ReadEntityCursorOperation<TEntity>
  • RemoveContactIdentifierOperation
  • RightToBeForgottenOperation
  • SetFacetOperation
  • SetFacetOperation<TFacet>, where TFacet : Sitecore.XConnect.Facet
  • SplitEntityCursorOperation
  • UpdateDeviceProfileOperation
  • XdbSearchOperation<TEntity>

This is a short example of how to implement a service plugin for Sitecore xConnect. Let me know if this was helpful!


Do you need help with your Sitecore project?
VIEW SITECORE SERVICES