CX trends to watch for in 2019

Driving Personalisation with xConnect APIs and Custom Facet Data

Driving Personalisation with xConnect APIs and Custom Facet Data | Part 1

  • Posted by: Surbhi
  • 04/02/2019

Introduction

Personalization aims at delivering the right content to the right customer. The moment a user interacts with any of your systems, you are presented with a significant amount of data that can be utilized to deliver a moderately tailored and personalized experience to your visitor. The content can be significantly improved further once the user is identified and more is known about his preferences.

Sitecore xConnect provides the capability to capture data about a user from various sources and helps integrate everything into XDB for us to leverage.

The blog explains how custom data from external sources can be captured and used to personalize content created in Sitecore to improve the overall customer experience.

Follow our session on YouTube

Creating a Custom Facet and Model

1. In order to capture custom data about a contact, we first need to define a model that would hold the new information. We do so by creating a class with its properties resenting the data to be captured and inheriting the class Sitecore.XConnect.Facet. We also define a Default Facet Key which would later help us consult the facet using the API.


 [FacetKey(DefaultFacetKey)] 
    public class StudentInfo : Facet 
    { 
        public const string DefaultFacetKey = "StudentInfo"; 
        public string Courses { get; set; } 
        public string Country { get; set; } 
    } 

2. The next step is to create a custom collection model. A collection model defines what data is collected for a contact and stored in XDB. This model is used in extension with the default Core Collection Model, which defines the basic information such as First Name, Email etc., collected in XDB by default.


 public  class SitecoreStudentCustomModel 
    { 
        public static XdbModel Model { get; } = BuildModel(); 
        private static XdbModel BuildModel() 
        { 
            XdbModelBuilder modelBuilder = new XdbModelBuilder("SitecoreCustomModel", new XdbModelVersion(1, 0)); 
            modelBuilder.DefineFacet<contact,>(FacetKeys.StudentInfo); 
            modelBuilder.ReferenceModel(Sitecore.XConnect.Collection.Model.CollectionModel.Model); 
            return modelBuilder.BuildModel(); 
        } 
    } 

    public class FacetKeys 
    { 
        public const string StudentInfo = "StudentInfo"; 
    } 

3. The next step is to deploy the new model in the form of a JSON file to xConnect roles. In order to do so, create a console application and run the following code.


 class Program 
        { 
            static void Main(string[] args) 
            { 
                var model = SCM.SitecoreStudentCustomModel.Model; 
                var serializedModel = Sitecore.XConnect.Serialization.XdbModelWriter.Serialize(model); 
                File.WriteAllText("D:\\ImportCSV\\files\\" + model.FullName + ".json", serializedModel); 
            } 
        } 

4. Copy the created model JSON file to the following paths:


C:\\root\App_Data\Models 
C:\\root\App_data\Models 
C:\\root\App_Data\Models 


Importing Data into XDB

For demo purpose, we created a custom console application which would import contact information into XDB from a CSV file. A google form was created to capture the relevant information and the list of submissions were converted to a CSV file and used to create contacts.

1. Create method to load CSV file data in model


//load csv data to student list 
        public static List LoadCSV(string filePath) 
        { 
            try 
            { 
                var listStudent = (from row in File.ReadAllLines(filePath).Skip(1) 
                                   let studentColData = row.Split(',') 
                                   select new StudentData 
                                   { 
                                       StudentName = studentColData[1], 
                                       Courses = studentColData[3], 
                                       Country= studentColData[4], 
                                       Email= studentColData[2], 
                                       Uid= studentColData[0] 
                                   }).ToList(); 
                return listStudent; 
            } 
            catch (Exception ex) { System.IO.File.AppendAllText(ConfigHelper.getConfigValue("LogFilePath"), DateTime.Now.ToString() + ": " + ex.ToString() + "\n"); return null; } 
        } 


2. Create method to load xConnect configuration for the API

This method would contain logic to load the new custom collection model when getting contact data from XDB


//set the Xconnect configuration 
        public static async Task GetXConnectConfiguration() 
        { 
            CertificateWebRequestHandlerModifierOptions options = 
            CertificateWebRequestHandlerModifierOptions.Parse(CWRHMO); 
            var certificateModifier = new CertificateWebRequestHandlerModifier(options); 
            List clientModifiers = new List(); 
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20)); 
            clientModifiers.Add(timeoutClientModifier); 
            var collectionClient = new CollectionWebApiClient(new Uri(ConfigHelper.getConfigValue("CollectionWebApiClient")), clientModifiers, new[] { certificateModifier }); 
            var searchClient = new SearchWebApiClient(new Uri(ConfigHelper.getConfigValue("SearchWebApiClient")), clientModifiers, new[] { certificateModifier }); 
            var configurationClient = new ConfigurationWebApiClient(new Uri(ConfigHelper.getConfigValue("ConfigurationWebApiClient")), clientModifiers, new[] { certificateModifier }); 
            XdbModel[] models = { CollectionModel.Model, SitecoreStudentCustomModel.Model }; 
            var cfg = new XConnectClientConfiguration( 
            new XdbRuntimeModel(models), collectionClient, searchClient, configurationClient); 

            try 
            { 
                await cfg.InitializeAsync(); 
                return cfg; 
            } 
            catch (XdbModelConflictException ce) 
            { 
                System.IO.File.AppendAllText(ConfigHelper.getConfigValue("LogFilePath"), DateTime.Now.ToString() + ": " + ce.ToString() + "\n"); 
                return null; 
            } 
        } 

3. Create method to Import data in XDB

This would create contacts based on the information loaded from CSV and defines what data is stored in each facet.


//Import csv data to XDB 
        private static async Task ExportToXDB(List students) 
        { 
            try 
            { 
                var offlineGoal = Guid.Parse(ConfigurationManager.AppSettings.Get("OfflineGoal"));  
                var channelId = Guid.Parse(ConfigurationManager.AppSettings.Get("ChannelId")); 
                var source = "Uid"; 
                var cfg = await GetXConnectConfiguration(); 
                using (var client = new XConnectClient(cfg)) 
                { 
                    foreach (var objStudent in students) 
                    { 
                        // to identifier the student by uid 
                        IdentifiedContactReference reference = new IdentifiedContactReference(source, objStudent.Uid); 
                        Contact existingContact = await client.GetAsync(reference, new ContactExpandOptions(new string[] { PersonalInformation.DefaultFacetKey, EmailAddressList.DefaultFacetKey, StudentInfo.DefaultFacetKey })); 
                        if (existingContact != null) 
                        { 
                            PersonalInformation existingContactPersonalFacet = existingContact.GetFacet(PersonalInformation.DefaultFacetKey); 
                            if (existingContactPersonalFacet != null) 
                            { 
                                existingContactPersonalFacet.FirstName = objStudent.StudentName; 
                                existingContactPersonalFacet.Nickname = objStudent.Country; 
                                existingContactPersonalFacet.JobTitle = objStudent.Courses; 
                                client.SetFacet(existingContact, PersonalInformation.DefaultFacetKey, existingContactPersonalFacet); 
                            } 
                            else 
                            { 
                               PersonalInformation personalInfoFacet = new PersonalInformation(); 
                               personalInfoFacet.FirstName = objStudent.StudentName; 
                               client.SetFacet(existingContact, PersonalInformation.DefaultFacetKey, personalInfoFacet); 
                            } 

                            EmailAddressList existingContactEmailFacet = existingContact.GetFacet(EmailAddressList.DefaultFacetKey); 
                            if (existingContactEmailFacet != null) 
                            { 
                                existingContactEmailFacet.PreferredEmail = new EmailAddress(objStudent.Email, true); 
                                existingContactEmailFacet.PreferredKey = "work"; 
                                client.SetFacet(existingContact, EmailAddressList.DefaultFacetKey, existingContactEmailFacet); 
                            } 

                            else 
                            { 
                                EmailAddressList emailAddressList = new EmailAddressList(new EmailAddress(objStudent.Email, true), "work"); 
                                client.SetFacet(existingContact, EmailAddressList.DefaultFacetKey, emailAddressList); 
                            } 

                            StudentInfo existingStudentInfoFacet = existingContact.GetFacet(StudentInfo.DefaultFacetKey); 
                            if (existingStudentInfoFacet != null) 
                            { 
                                existingStudentInfoFacet.Uid = objStudent.Uid; 
                                client.SetFacet(existingContact, StudentInfo.DefaultFacetKey, existingStudentInfoFacet); 
                            } 

                            else 

                            { 
                                StudentInfo stuudentInfo = new StudentInfo() { Country = objStudent.Country, Courses = objStudent.Courses }; 
                                client.SetFacet(existingContact, StudentInfo.DefaultFacetKey, stuudentInfo); 
                            } 

                            // Create a new interaction for that contact 

                            Interaction interaction = new Interaction(existingContact, InteractionInitiator.Brand, channelId, ""); 

                           // Add events - all interactions must have at least one event 

                            var xConnectEvent = new Goal(offlineGoal, DateTime.UtcNow); 
                            interaction.Events.Add(xConnectEvent); 

                            // Add the contact and interaction 

                           client.AddInteraction(interaction); 
                        } 

                       else 
                        { 

                            // Identifier for a 'known' contact 

                            var identifier = new ContactIdentifier[] 
                           { 
                                    new ContactIdentifier(source, objStudent.Uid, ContactIdentifierType.Known) 
                            }; 

 

                            // Create a new contact with the identifier 

                            Contact knownContact = new Contact(identifier); 
                            PersonalInformation personalInfoFacet = new PersonalInformation(); 
                            personalInfoFacet.FirstName = objStudent.StudentName; 
                            client.SetFacet(knownContact, PersonalInformation.DefaultFacetKey, personalInfoFacet); 
                            EmailAddressList emailAddressList = new EmailAddressList(new EmailAddress(objStudent.Email, true), "work"); 
                            client.SetFacet(knownContact, EmailAddressList.DefaultFacetKey, emailAddressList); 
                            StudentInfo studentInfo = new StudentInfo() { Country = objStudent.Country, Courses = objStudent.Courses}; 
                            client.SetFacet(knownContact, StudentInfo.DefaultFacetKey, studentInfo); 
                            client.AddContact(knownContact); 


                            // Create a new interaction for that contact 

                            Interaction interaction = new Interaction(knownContact, InteractionInitiator.Brand, channelId, ""); 


                            // Add events - all interactions must have at least one event 

                            var xConnectEvent = new Goal(offlineGoal, DateTime.UtcNow); 
                            interaction.Events.Add(xConnectEvent); 


                            // Add the contact and interaction 

                           client.AddInteraction(interaction); 
                        } 


                        // Submit contact and interaction - a total of two operations 

                        await client.SubmitAsync(); 
                        Console.WriteLine(objStudent.Email); 
                        Console.WriteLine(objStudent.StudentName + "UID:" + objStudent.Uid); 

                        // Get the last batch that was executed 

                        var operations = client.LastBatch; 
                        Console.WriteLine("RESULTS..."); 

                        // Loop through operations and check status 

                        foreach (var operation in operations) 
                        { 
                            Console.WriteLine(operation.OperationType + operation.Target.GetType().ToString() + " Operation: " + operation.Status); 
                        } 
                    } 
                } 
                return true; 
            } 
            catch (Exception ex) 
            { 
                System.IO.File.AppendAllText(ConfigHelper.getConfigValue("LogFilePath"), DateTime.Now.ToString() + ": " + ex.ToString() + "\n"); 
                return false; 
            } 
        } 


4. Call the methods created above for execution


 private static void Main(string[] args) 
        { 
            string filePath = ConfigurationManager.AppSettings.Get("csvPath"); 
            if (IsFileExists(filePath)) 
            { 
                ExportToXDB(LoadCSV(filePath)).ConfigureAwait(false).GetAwaiter().GetResult(); 
            } 
            else 
            { 
               Console.WriteLine("File doesn't exists"); 
            } 
          } 


After this step, you can verify the contacts created in the Experience Profile application in Sitecore. This concludes the steps required to capture custom data in XDB using XConnect APIs.

Follow part 2 of the blog to see how to use this custom data to personalize content using custom personalization rules.

About Author

Surbhi

Surbhi

Sitecore - Technical Lead