Working with XConnect Client API - Implementation - Part 2

In the previous blog, we discussed what is the xConnect Client API withpurpose and how we can start configuring a custom application to able to connect it. 

In this blog, I am going to show how we can extract the interactions and contacts using the same application. 

Recently, I came up with a requirement where I need to fetch the interactions from xDB for specific duration for some reporting purpose. Although we can access to those Shard database directly, BUT Sitecore best practises says we should always use xConnect Client API while dealing with xDB. 

To do so, we need to setup a independent application and can be hosted on IIS server which is authorized to connect to xConnect thru secure channel. 

Please note, you must have ssl certificate for your application in order to connect to xConnect API.

Working with Interactions and Contacts

I am assuming you have gone thru my previous blog, where we were able to connect to xConnect Client API. 

I will be continuing from there and will be creating a new controller class to fetch the interaction. 

Step 1: Create a Logger Utility Class 

This step is important, as we do not have access to Sitecore.Diagnostics.Log library. So to log the message I have created my own helper class. This can be used commonly in any dotnet project. 

public static void Info(string message, string InfoType, string objGuid)

{

    FileStream fileStream = null;

    StreamWriter streamWriter = null;

    try

    { 

        string logFilePath = ConfigurationManager.AppSettings["LogPath"]; 

        if (string.IsNullOrWhiteSpace(logFilePath))

            logFilePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) + "\\Logs\\";

 

        logFilePath = logFilePath + "ProgramLog" + "-" + DateTime.Now.ToString("yyyyMMddhh") + "-" + objGuid + "-Info." + "txt";

 

        #region Create the Log file directory if it does not exists

        DirectoryInfo logDirInfo = null;

        FileInfo logFileInfo = new FileInfo(logFilePath);

        logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName);

        if (!logDirInfo.Exists) logDirInfo.Create();

        #endregion Create the Log file directory if it does not exists

        if (!logFileInfo.Exists)

        {

            fileStream = logFileInfo.Create();

        }

        else

        {

            fileStream = new FileStream(logFilePath, FileMode.Append);

        }

        streamWriter = new StreamWriter(fileStream);

        streamWriter.WriteLine(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + " Info: " + InfoType + " " + message);

    }

    finally

    {

        if (streamWriter != null) streamWriter.Close();

        if (fileStream != null) fileStream.Close();

    }

} 

Step 2: Create new Controller and Action : Controller Overview

public class ManageContactsController : Controller

{

       /*

       Private data members

       */

public async Task<ActionResult> FetchCount()

{

} 

public async Task<ActionResult> Process()

{

} 

public async Task<ActionResult> ResumeProcess()

{

}

/*

Private Functions

*/

} 

FetchCount() Action will fetch the total number of interactions during a period and will show the total count as result. This will ensure your xConnect connections are working fine and you are able to connect to xDB. 

Process() Action will again fetch the same interactions and will process each iteration combing all data and sending it to further functions 

ResumeProcess() Action will be used to resume from last bookmark, in case of lost connection with xConnect Client API. This is an important part of application which allows to resume from last endpoint and save lot of time. 

The complete class is available at this link https://github.com/arjunarora208/sitecore-xconnect-client/blob/main/src/Sitecore.Practise.App/Controllers/ManageContactsController.cs 

Step 3: Understanding important properties 

In order to make this blog clean, I will explain one of the action in detail: 

public async Task<ActionResult> FetchCount()

{

    try

    {

        var _client = await XConnectClientReader.GetClient(new Configurations.MigrationSettings());

        using (Sitecore.XConnect.Client.XConnectClient client = new XConnectClient(_client))

        {

            var interactionFacets = client.Model.Facets.Where(c => c.Target == EntityType.Interaction).Select(x => x.Name).ToList();

            var facetArray = interactionFacets.ToArray();

 

            var interactionCursor = await client.CreateInteractionEnumerator(

                    new InteractionEnumeratorOptions(batchSize, DateTime.UtcNow,

                    new InteractionExpandOptions(facetArray)

                    {

                        Contact = new RelatedContactExpandOptions(PersonalInformation.DefaultFacetKey)

                    })

                    {

                        MinStartDateTime = startDate,

                        MaxStartDateTime = endDate

                    }

                    );

            ViewBag.StartDate = startDate;

            ViewBag.EndDate = endDate;

            ViewBag.TotalCount = interactionCursor.TotalCount;

            LogHelper.Info($"Interactions found from {startDate} to {endDate} : {interactionCursor.TotalCount}", null, executionIdentifier);

        }

    }

    catch (Exception ex)

    {

        LogHelper.Error(ex.Message, ex, executionIdentifier);

        ViewBag.ErrorMessage = ex.Message;

    }

    return View();

} 

Above code will first initiate the settings having the xConnect configurations urls and then will create a instance of xConnect client, by using which you can extract the interactions or contact data. I have tried fetching the interaction count during startdate and enddate. 

[Important] 

You can use below code to fetch all facets for interaction and contact: 

//For Interaction

var interactionFacets = client.Model.Facets.Where(c => c.Target == EntityType.Interaction).Select(x => x.Name).ToList();

var facetArray = interactionFacets.ToArray(); 

//For Contact

var contactFacets = client.Model.Facets.Where(c => c.Target == EntityType.Contact).Select(x => x.Name).ToList();

var facetArray = contactFacets.ToArray(); 

Step 4 (Optional): Fetch Contact with all facets

Along with above, you can even fetch a contact using the same client.

private Contact FetchContact(string contactId, XConnectClient client, string batchFileName, int rowCount)

{

    if (!string.IsNullOrEmpty(contactId))

        try

        {

            Contact contact = null;

            var contactFacets = client.Model.Facets.Where(c => c.Target == EntityType.Contact).Select(x => x.Name).ToList();

            var facetArray = contactFacets.ToArray();

 

            var reference = new Sitecore.XConnect.ContactReference(Guid.Parse(contactId));

            contact = client.Get<Contact>(reference, new ContactExecutionOptions(new ContactExpandOptions(facetArray) { }));

 

            return contact;

        }

        catch (Exception ex)

        {

            LogHelper.Error($"RowCount: {rowCount} || Error fetching contact: ContactId: {contactId}", ex, batchFileName);

            return null;

        }

    else

        return null; 

} 

Step 5: Resume Process

This is an important part because I was not getting any help over Sitecore documentation to how to save the bookmark in any format. 

To solve this, I have saved the bookmark which is actually a byte array into a bat file. You can even save that bookmark into sql table if you want to maintain bookmark history or have your own custom database accessible. 

Currently I am keep on overwriting the last bookmark value in the same file and Resume process read the array from that file. The only dis-advantage of this approach is that in case if you lost that file then you might have to start the whole process again.

...

 while (await interactionCursor.MoveNext())

 {

bookmark = interactionCursor.GetBookmark();//New bookmark for every iteration

 LogHelper.SaveBookmark(bookmark);//Saving it in a file

}

..

..

..

public async Task<ActionResult> ResumeProcess()

        {

            var bookmark = LogHelper.GetBookmark();//Get last bookmark

            if (bookmark != null)

                LogHelper.Info("Bookmark Found: Processing resumes", "Info", "ResumeProcess");

            else

                throw new Exception("No bookmark found");

            return await Process(bookmark);

        }

..

..

Github Project link:

I have composed a new project and setup it on github for your reference. You can clone it from

https://github.com/arjunarora208/sitecore-xconnect-client repository.

 Follow the readme file for better clarification.

Summary:

In these series of blogs, I have tried explaining how you can take leverage of xConnect Client API to extract the xDB databases. The core motive was to provide the re-usable code which can reduce the effort and time creating the external site. Hope this application could help you while dealing with Sitecore analytics data.

 References:

https://doc.sitecore.com/xp/en/developers/latest/sitecore-experience-platform/extracting-contacts-and-interactions.html 

Comments

Popular posts from this blog

Working with Device Detection Module in Sitecore

Setup A/B Testing in Sitecore - Part 1

Working with Webhooks in Sitecore - Part 1