Getting Started with xConnect

The following guide will give you a basic overview of xConnect and where it fits into the Experience Platform. The guide also includes tutorials for reading and writing experience data, as well as extending the xConnect model.

Sitecore Experience Platform (XP) and xConnect

xConnect is the service layer that sits in between the xDB and any trusted client that wants to read, write, or search experience data using the xConnect Client API. Communication must happen over HTTPS and the client must have the appropriate certificate thumbprint. xConnect and xDB are not interchangeable terms - they are two parts of the platform that work together.

xConnect currently has two services: Collection and Search. The following diagram demonstrates where xConnect fits into the overall XP architecture. In this diagram, Collection and Search are hosted on dedicated servers. Notice that there is no xConnect server role; Collection and Search together make up the xConnect layer:

xDB is an umbrella term for the services that are responsible for processing and storing experience data. This includes:

  • xDB Processing
  • xDB Reporting
  • Reference Data service
  • Marketing Automation Reporting service
  • Marketing Automation Operations service

Note

In a single server environment, Collection and Search can be combined into a single end point and hosted on a single server alongside two xDB services: Marketing Automation and Reference Data.

By default, xConnect implements Solr for experience search and SQL for collection. Be aware that xConnect Search and Content Search are totally separate search frameworks. You cannot use the Content Search API to access the xDB core; you must use the xConnect Client API.

See also:

The xConnect Client API

The xConnect Client API allows you to create, retrieve, and search contact and interaction data. No client has direct access to the collection database or the search index. The underlying REST-ful web API implements the oData protocol and all communication happens over HTTPS.

The following example demonstrates how to get an instance of the xConnect Client API in a Sitecore context:

using Sitecore.XConnect;
using System;
using System.Threading.Tasks;
using System.Linq;

namespace Documentation
{
    public class GetFacetsMultiple
    {
        public async void Example()
        {
            using (Sitecore.XConnect.Client.XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())
            {
                try
                {
                    // Get, create, or search experience data here
                }
                catch (XdbExecutionException ex)
                {
                    // Handle exceptions
                }
            }
        }
    }
}

The xConnect end points are configured in the \App_Config\ConnectionStrings.config file. In a developer environment, Collection and Search share a single end point named xdb.collection.

The xConnect Client API can also be used outside of Sitecore. The following example demonstrates how to use the xConnect Client API from a console application:

using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Schema;
using System;
using System.Threading.Tasks;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.Xdb.Common.Web;
using Sitecore.XConnect.Collection.Model;
using System.Collections.Generic;
using Sitecore.Xdb.Common.Web;

namespace Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
        }

        private static async Task MainAsync(string[] args)
        {
            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync();

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            using (var client = new XConnectClient(cfg))
            {
            }
        }
    }
}

The xConnect collection model

The collection model defines the CLR types, facets, and events that make up the structure of experience data. Sitecore ships with a default collection model that defines facets such as PersonalInformation and event types such as Goal and Outcome. You can define any number of additional models with your own facets and events.

All models are defined entirely in code. The following model defines one contact facet and one event:

using System;
using Sitecore.XConnect.Operations;
using Sitecore.XConnect.Schema;

namespace Sitecore.XConnect.Sample.Model
{
    public static class CollectionModel
    {
        public static XdbModel Model { get; } = BuildModel();

        static XdbModel BuildModel()
        {
            var builder = new XdbModelBuilder("Sitecore.XConnect.Sample.Model", new XdbModelVersion(1, 0));

            //Contact facets
            builder.DefineFacet<Contact, CustomFacet>(FacetKeys.CustomFacetKey);

            // Events
            builder.DefineEventType<CustomEvent>(false);

            return builder.BuildModel();
        }
    }

    public static class FacetKeys
    {
        public const string CustomFacetKey = nameof(CollectionModel.Avatar);
    }
}

In the context of a console application, the xConnect Client API can be initialized with the custom model:

var cfg = new XConnectClientConfiguration(
    new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

Alternatively, the custom model can be added to configuration in Sitecore. When the application starts, a runtime model is assembled from all models listed in configuration and is available when you request the xConnect client:

using (Sitecore.XConnect.Client.XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())
{
    // Core collection model and custom model both available from configuration
}

The xConnect service must have a JSON representation of any model that a client application wishes to use:

Tools exist to serialize the model into JSON. The JSON and C# versions of a model must match, otherwise the xConnect Client API will fail to initialize. xConnect only needs the JSON representation of a model - there is no need to deploy DLLs.

Extending xConnect

As a developer, you can extend xConnect in the following ways:

Tracking and xConnect

The tracker is used on Sitecore Content Delivery servers to track a contact during a session. The tracker has its own data model which is converted into a format for xConnect by the XConnectDataAdapter:

The reason for this architecture is to limit the number of breaking changes between 8.2 and 9.0. With the tracker, you are able to:

  • Load contact facets from xConnect in read-only mode - changes to facets must be sent directly to xConnect
  • Trigger events - these are converted into xConnect events before session end
  • Save interaction facets - these are converted into an xConnect format on session end
  • Add additional identifiers to a contact - 9.0 adds support for multiple identifiers

Note

Contact locking does not exist in 9.0. Contact data is read-only in the context of the tracker - to update facets, you must use the xConnect Client API directly. This means that other sources can update a contact with an ongoing session.

Tutorials: xConnect

The following tutorials assume that you have installed the complete Sitecore Experience Platform, including xConnect and the xDB.

Tutorial #1: Create a contact and an interaction

In the following tutorial you will:

  • Create a contact with a known identifier
  • Create an interaction with a single event
  1. Open Visual Studio 2015 (run as administrator).
  2. Create a new Console Application as shown:
  1. Add the followign references:

    • Sitecore.XConnect.dll
    • Sitecore.XConnect.Client.dll
    • Sitecore.XConnect.Collection.Model.dll
    • Sitecore.Xdb.Common.Web.dll
    • Microsoft.Extensions.Configuration.Abstractions.dll
    • System.Interactive.Async.Providers.dll
    • Sitecore.Framework.Conditions.dll
    • Newtonsoft.Json.dll
    • Sitecore.XConnect.Search.dll
    • System.Net.Http.Formatting.dll
  2. Paste the following code sample into Program.cs file and replace the highlighted lines with the host name of your xConnect instance and your client certificate thumbprint:

Note

You can find the client certificate thumbprint in the c:\path\to\xconnect\App_Config\AppSettings.config configuration file.

using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @"            ______                                                       __     ",
                        @"           /      \                                                     |  \    ",
                        @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                        @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                        @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                        @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                        @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                        @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                        @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                    };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {

                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 to run the console - if the configuration initializes successfully, the console application will print xConnect:
  1. Create and submit a contact with a known identifier as demonstrated by the highlighted section of the following sample:
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @"            ______                                                       __     ",
                        @"           /      \                                                     |  \    ",
                        @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                        @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                        @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                        @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                        @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                        @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                        @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                    };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    // Identifier for a 'known' contact
                    var identifier = new ContactIdentifier[]
                    {
                                new ContactIdentifier("twitter", "myrtlesitecore" + Guid.NewGuid().ToString("N"), ContactIdentifierType.Known)
                    };

                    // Print out the identifier that is going to be used
                    Console.WriteLine("Identifier:" + identifier[0].Identifier);

                    // Create a new contact with the identifier
                    Contact knownContact = new Contact(identifier);

                    client.AddContact(knownContact);

                    // Submit contact and interaction - a total of two operations
                    await client.SubmitAsync();

                    // 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);
                    }

                    Console.ReadLine();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Amend your code to create an interaction with a single event - in this example, a goal. Be aware of the following:

    • All events (including goals and outcomes) must be instantiated with a definition ID - definitions are created in Sitecore and deployed to the Reference Data Service. xConnect is not aware of these definitions and will let you pass in a random GUID that does not represent a definition. You are responsible for keeping your data clean.
    • All interactions require a channel GUID - again, these are defined in Sitecore and again, xConnect will allow you to pass in a random GUID.
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @"            ______                                                       __     ",
                        @"           /      \                                                     |  \    ",
                        @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                        @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                        @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                        @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                        @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                        @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                        @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                    };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    var offlineGoal = Guid.Parse("ad8ab7fe-ab48-4ea9-a976-ae7a268ae2f0"); // "Watched demo" goal
                    var channelId = Guid.Parse("110cbf07-6b1a-4743-a398-6749acfcd7aa"); // "Other event" channel

                    // Identifier for a 'known' contact
                    var identifier = new ContactIdentifier[]
                    {
                                new ContactIdentifier("twitter", "myrtlesitecore" + Guid.NewGuid().ToString("N"), ContactIdentifierType.Known)
                    };

                    // Print out the identifier that is going to be used
                    Console.WriteLine("Identifier:" + identifier[0].Identifier);

                    // Create a new contact with the identifier
                    Contact knownContact = new Contact(identifier);

                    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();

                    // 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);
                    }

                    Console.ReadLine();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }

            }
        }
    }
}
  1. Press F5 to run the console - if the configuration initializes successfully, notice that two operations are now being executed - one to add the contact and another to add the interaction:

Tutorial #2: Set contact and interaction facets

In the following tutorial you will:

  • Set facets on a new contact
  • Set facets on an interaction

Be aware of the following:

  • Default facets are listed in the collection model reference topic
  • If a facet has any required properties, they must be passed in via the facet constructor - if the property is not in the constructor, it is not mandatory
  1. Update the code from Tutorial #1 - populate the PersonalInformation facet as shown:
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                cfg.Initialize(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                            @"            ______                                                       __     ",
                            @"           /      \                                                     |  \    ",
                            @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                            @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                            @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                            @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                            @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                            @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                            @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                        };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    var offlineGoal = Guid.Parse("ad8ab7fe-ab48-4ea9-a976-ae7a268ae2f0"); // "Watched demo" goal
                    var channelId = Guid.Parse("110cbf07-6b1a-4743-a398-6749acfcd7aa"); // "Other event" channel

                    // Identifier for a 'known' contact
                    var identifier = new ContactIdentifier[]
                    {
                                    new ContactIdentifier("twitter", "myrtlesitecore" + Guid.NewGuid().ToString("N"), ContactIdentifierType.Known)
                    };

                    // Print out the identifier that is going to be used
                    Console.WriteLine("Identifier:" + identifier[0].Identifier);

                    // Create a new contact with the identifier
                    Contact knownContact = new Contact(identifier);

                    PersonalInformation personalInfoFacet = new PersonalInformation();

                    personalInfoFacet.FirstName = "Myrtle";
                    personalInfoFacet.LastName = "McSitecore";
                    personalInfoFacet.JobTitle = "Programmer Writer";

                    client.SetFacet<PersonalInformation>(knownContact, PersonalInformation.DefaultFacetKey, personalInfoFacet);

                    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();

                    // 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);
                    }

                    Console.ReadLine();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 to run the console - if the configuration initializes successfully, notice that three operations are now being executed - setting a facet is a separate operation from adding a contact:
  1. Add a facet to the interaction - an example of a built-in interaction facet is IpInfo, which is used in a tracking context.
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                cfg.Initialize(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                            @"            ______                                                       __     ",
                            @"           /      \                                                     |  \    ",
                            @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                            @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                            @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                            @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                            @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                            @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                            @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                        };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    var offlineGoal = Guid.Parse("ad8ab7fe-ab48-4ea9-a976-ae7a268ae2f0"); // "Watched demo" goal
                    var channelId = Guid.Parse("110cbf07-6b1a-4743-a398-6749acfcd7aa"); // "Other event" channel

                    // Identifier for a 'known' contact
                    var identifier = new ContactIdentifier[]
                    {
                                    new ContactIdentifier("twitter", "myrtlesitecore" + Guid.NewGuid().ToString("N"), ContactIdentifierType.Known)
                    };

                    // Print out the identifier that is going to be used
                    Console.WriteLine("Identifier:" + identifier[0].Identifier);

                    // Create a new contact with the identifier
                    Contact knownContact = new Contact(identifier);

                    PersonalInformation personalInfoFacet = new PersonalInformation();

                    personalInfoFacet.FirstName = "Myrtle";
                    personalInfoFacet.LastName = "McSitecore";
                    personalInfoFacet.JobTitle = "Programmer Writer";

                    client.SetFacet<PersonalInformation>(knownContact, PersonalInformation.DefaultFacetKey, personalInfoFacet);

                    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);

                    IpInfo ipInfo = new IpInfo("127.0.0.1");

                    ipInfo.BusinessName = "Home";

                    client.SetFacet<IpInfo>(interaction, IpInfo.DefaultFacetKey, ipInfo);

                    // Add the contact and interaction
                    client.AddInteraction(interaction);

                    // Submit contact and interaction - a total of two operations
                    await client.SubmitAsync();

                    // 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);
                    }

                    Console.ReadLine();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 to run the console - if the configuration initializes successfully, notice that four operations are now being executed - setting an interaction facet is a separate operation from creating an interaction.

Tutorial #3: Get contacts

  1. Execute the code from Tutorial #2 to create a new contact - take note of the identifier that is used to create the contact. For example: myrtlesitecore9d1f652848f247219be9e1dbea4dd346

  2. Modify your code or create a new console application - use the known identifier to retrieve a contact that was created previously. Be aware of the following:

    • External systems should not store contact IDs, therefore it is unlikely that you will use the .Get<Contact> overload that accepts a contact ID.
    • To retrieve a facet, you must explicitly request it as part of the ContactExpandOptions as shown in the example.
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                cfg.Initialize(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                            @"            ______                                                       __     ",
                            @"           /      \                                                     |  \    ",
                            @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                            @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                            @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                            @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                            @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                            @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                            @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                        };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    // Get a known contact
                    IdentifiedContactReference reference = new IdentifiedContactReference("twitter", "myrtlesitecore9d1f652848f247219be9e1dbea4dd346");

                    Contact existingContact = await client.GetAsync<Contact>(reference, new ContactExpandOptions(new string[] { PersonalInformation.DefaultFacetKey }));

                    PersonalInformation existingContactFacet = existingContact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey);

                    Console.WriteLine("Contact ID: " + existingContact.Id.ToString());
                    Console.WriteLine("Contact Name: " + existingContactFacet.FirstName);

                    Console.ReadLine();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 to run the console - you should get a single contact and the associated PersonalInformation facet. Try to run this code without any expand options - the facet will be null and you will get an exception when you try to use it.

  2. To retrieve a contact’s interactions, you must set the Interactions property of ContactExpandOptions as shown in the following example. Be aware of the following:

    • To retrieve all interactions, set the StartDateTime and EndDateTime as shown - interactions between these two dates are returned.
    • You can also specify a Limit if you only want the top X number of interactions.
    • Notice that you must specify which interaction facets to return.
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                cfg.Initialize(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                            @"            ______                                                       __     ",
                            @"           /      \                                                     |  \    ",
                            @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                            @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                            @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                            @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                            @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                            @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                            @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                        };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    // Get a known contact
                    IdentifiedContactReference reference = new IdentifiedContactReference("twitter", "myrtlesitecore9d1f652848f247219be9e1dbea4dd346");

                    Contact existingContact = await client.GetAsync<Contact>(reference, new ContactExpandOptions(new string[] { PersonalInformation.DefaultFacetKey })
                    {
                        Interactions = new RelatedInteractionsExpandOptions(IpInfo.DefaultFacetKey)
                        {
                            StartDateTime = DateTime.MinValue,
                            EndDateTime = DateTime.MaxValue
                        }
                    });

                    PersonalInformation existingContactFacet = existingContact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey);

                    Console.ReadLine();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 to run the console - in addition to a single contact and contact facet, you should now get that contact’s interaction and be able to read the IpInfo facet of those interactions:

Getting interactions

To retrieve an interaction, you need the contact ID and the interaction ID. You can also pass in InteractionExpandOptions that will retrieve the related contact and any contact facets you specify:

var contactId = Guid.NewGuid();
var interactionId = Guid.NewGuid();

InteractionReference interactionReference = new InteractionReference(contactId, interactionId);

Interaction interaction = client.Get<Interaction>(interactionReference, new InteractionExpandOptions(new string[] { IpInfo.DefaultFacetKey })
{
    Contact = new RelatedContactExpandOptions(new string[] { PersonalInformation.DefaultFacetKey })
});

Tutorial #4: Search contacts and interactions

The followoing tutorial demonstrates how to search contact and interaction data. Keep the following in mind when searching:

  • By default, anonymous contacts are not indexed and therefore not searchable
  • If a facet or property is marked PIISensitive, it is not indexed. This means you cannot search using this facet or property. Customers decide if they need to enforce PII compliance - xConnect only gives them the mechanism to do so. Note that you can still return PII sensitive data (such as a contact’s first name) even though you cannot search for a contact by their first name.
  • Experience data is indexed, but not stored - only IDs and sync tokens are stored. When you use expand options to return facets as part of a query, that data is coming from the collection database - not from the index.
  1. Create a new console application or use the application from Tutorial #3.
  2. To search contacts, use client.Contacts followed by your query. To search interactions, use client.Interactions:
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using System;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                cfg.Initialize(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                            @"            ______                                                       __     ",
                            @"           /      \                                                     |  \    ",
                            @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                            @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                            @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                            @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                            @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                            @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                            @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                        };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    var results0 = client.Contacts.ToEnumerable().Count();

                    Console.WriteLine("Total contacts: " + results0.ToString());

                    // Use InteractionsCache instead of client.Contacts.Where(x => x.Interactions.Any()) as not all search providers support joins
                    var results = await client.Contacts.Where(c => c.InteractionsCache().InteractionCaches.Any()).GetBatchEnumerator();

                    Console.WriteLine("Contacts with interactions: " + results.TotalCount);

                    var results2 = await client.Contacts.Where(c => c.LastModified > DateTime.UtcNow.AddHours(-10)).GetBatchEnumerator();

                    Console.WriteLine("Updated 10hrs ago: " + results2.TotalCount);

                    var results3 = await client.Contacts.Where(c => c.GetFacet<PersonalInformation>().JobTitle == "Programmer Writer").GetBatchEnumerator();

                    Console.WriteLine("Programmer Writers: " + results3.TotalCount);

                    var results4 = await client.Interactions.Where(i => i.EndDateTime > DateTime.UtcNow.AddHours(-10)).GetBatchEnumerator();

                    Console.WriteLine("Interactions < 10hrs old: " + results4.TotalCount);

                    Console.ReadKey();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 - your numbers will be different depending on when and how many times you ran the program from tutorials #1 - #3:
  1. You can use expand options to customize what data is returend, just as you did with .Get(). In the following example, ContactExpandOptions and InteractionExpandOptions are being used to return additional data with each contact and interaction:
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace Sitecore.Documentation
{
    public class Program
    {
        private static void Main(string[] args)
        {
            MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("");
            Console.WriteLine("END OF PROGRAM.");
            Console.ReadKey();
        }

        private static async Task MainAsync(string[] args)
        {
            Console.WriteLine(" ");

            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);
            try
            {
                cfg.Initialize(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                            @"            ______                                                       __     ",
                            @"           /      \                                                     |  \    ",
                            @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                            @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                            @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                            @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                            @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                            @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                            @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                        };
                Console.WindowWidth = 160;
                foreach (string line in arr)
                    Console.WriteLine(line);

            }
            catch (XdbModelConflictException ce)
            {
                Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    var results0 = client.Contacts.ToEnumerable().Count();

                    Console.WriteLine("Total contacts: " + results0.ToString());

                    var results = await client.Contacts.Where(c => c.InteractionsCache().InteractionCaches.Any()).WithExpandOptions(new ContactExpandOptions(PersonalInformation.DefaultFacetKey)
                    {
                        Interactions = new RelatedInteractionsExpandOptions(IpInfo.DefaultFacetKey)
                        {
                            EndDateTime = DateTime.MaxValue,
                            StartDateTime = DateTime.MinValue
                        }
                    })
                    .GetBatchEnumerator();

                    Console.WriteLine("Contacts with interactions: " + results.TotalCount);

                    var results2 = await client.Contacts.Where(c => c.LastModified > DateTime.UtcNow.AddHours(-10)).GetBatchEnumerator();

                    Console.WriteLine("Updated 10hrs ago: " + results2.TotalCount);

                    var results3 = await client.Contacts.Where(c => c.GetFacet<PersonalInformation>().JobTitle == "Programmer Writer").GetBatchEnumerator();

                    Console.WriteLine("Programmer Writers: " + results3.TotalCount);

                    var results4 = await client.Interactions.Where(i => i.EndDateTime > DateTime.UtcNow.AddHours(-10))
                            .WithExpandOptions(new InteractionExpandOptions(new string[] { IpInfo.DefaultFacetKey })
                            {
                                Contact = new RelatedContactExpandOptions(PersonalInformation.DefaultFacetKey)
                            })
                            .GetBatchEnumerator();

                    Console.WriteLine("Interactions < 10hrs old: " + results4.TotalCount);

                    Console.ReadKey();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Press F5 - it will output the Name of the first contact with a PersonalInfo facet and the Business name of the first interaction with an IpInfo facet:

Tutorial #5: Create custom facets and events

In the following tutorial, you will be extending the collection model. You are Sitecore Cinema, and you want to collect the following information from your visitors:

Class Purpose Notes
CinemaInfo Interaction facet Information about the cinema visited during the interaction.
CinemaVisitorInfo Contact facet Information about the visitor - such as loyalty ID.
WatchMovie Custom outcome Triggered from the PoS system when the contact buys a movie ticket.
BuyConcessions Custom outcome Triggered from the PoS system when the contact buys concessions.
UseSelfService Custom event Triggered from the self service ticket machine when the contact uses them.

This information relies on visitors using their loyalty card each time they interaction with one of your cinemas. You offer free popcorn after 10 usages of the loyalty card as incentive.

  1. Create a new solution and Class Library project named SitecoreCinema.
  2. Create a class for your collection model as shown:
namespace SitecoreCinema.Model.Collection
{
    public class SitecoreCinemaModel
    {
        // Your model here later
    }
}
  1. Create a class named CinemaInfo as shown:
using Sitecore.XConnect;

namespace SitecoreCinema.Model.Collection
{
    [FacetKey(DefaultFacetKey)]
    public class CinemaInfo : Facet
    {
        public const string DefaultFacetKey = "CinemaInfo";
        public int CinemaId { get; set; } // e.g. SC123567 - all cinemas have a unique identifier
    }
}
  1. Create a class named CinemaVisitorInfo as shown:
using Sitecore.XConnect;

namespace SitecoreCinema.Model.Collection
{
    [FacetKey(DefaultFacetKey)]
    public class CinemaVisitorInfo : Facet
    {
        public const string DefaultFacetKey = "CinemaVisitorInfo";

        public string FavoriteMovie { get; set; } // Plain text; e.g. "Apocalypse Now" or "Legally Blonde"
    }
}
  1. Create a class named WatchMovie as shown. This steps assume that you have created a matching outcome in Sitecore and copied the GUID into this class.
using Sitecore.XConnect;
using System;

namespace SitecoreCinema.Model.Collection
{
    public class WatchMovie : Outcome
    {
        public static Guid EventDefinitionId { get; } = new Guid("0b7e9e7e-f7ac-4ac2-8f8e-26a9c8644b23");

        public WatchMovie(DateTime timestamp, string currencyCode, decimal monetaryValue) : base(EventDefinitionId, timestamp, currencyCode, monetaryValue) { }
        public string EIDR { get; set; } // Entertainment Identifier Registry number - e.g. 10.5240/24D3-956D-0575-0244-CB28-I
    }
}
  1. Create a class named BuyConcessions as shown. This steps assume that you have created a matching outcome in Sitecore and copied the GUID into this class.
using Sitecore.XConnect;
using System;

namespace SitecoreCinema.Model.Collection
{
    public class BuyConcessions : Outcome
    {
        public static Guid EventDefinitionId { get; } = new Guid("2a7e9e7e-a7ac-2ac2-8a8e-26a9c8644b23");

        public BuyConcessions(DateTime timestamp, string currencyCode, decimal monetaryValue) : base(EventDefinitionId, timestamp, currencyCode, monetaryValue) { }

        public bool BoughtAlcoholicDrink { get; set; }
    }
}
  1. Create a class named UseSelfService as shown. This steps assume that you have created a matching event in Sitecore and copied the GUID into this class.
using Sitecore.XConnect;
using System;

namespace SitecoreCinema.Model.Collection
{
    public class UseSelfService : Event
    {
        public static Guid EventDefinitionId { get; } = new Guid("9a7e9e7e-27ac-2ac2-8a8e-26a9c8644b23");
        public UseSelfService(DateTime timestamp) : base(EventDefinitionId, timestamp) { }
    }
}
  1. Go back to the model you created in step #1 and register your facets and events as shown. Notice that we are referencing Sitecore’s core collection model:
using Sitecore.XConnect;
using Sitecore.XConnect.Schema;

namespace SitecoreCinema.Model.Collection
{
    public class SitecoreCinemaModel
    {
        public static XdbModel Model { get; } = BuildModel();

        private static XdbModel BuildModel()
        {
            XdbModelBuilder modelBuilder = new XdbModelBuilder("SitecoreCinemaModel", new XdbModelVersion(1, 0));

            modelBuilder.DefineFacet<Contact, CinemaVisitorInfo>(FacetKeys.CinemaVisitorInfo);
            modelBuilder.DefineFacet<Interaction, CinemaInfo>(FacetKeys.CinemaInfo);
            modelBuilder.DefineEventType<WatchMovie>(false);
            modelBuilder.DefineEventType<BuyConcessions>(false);
            modelBuilder.DefineEventType<UseSelfService>(false);

            modelBuilder.ReferenceModel(Sitecore.XConnect.Collection.Model.CollectionModel.Model);

            return modelBuilder.BuildModel();
        }
    }

    public class FacetKeys
    {
        public const string CinemaInfo = "CinemaInfo";
        public const string CinemaVisitorInfo = "CinemaVisitorInfo";
    }
}
  1. Create a new console application project in your solution. This app will generate the JSON version of the model, which must be manually copied into your xConnect instance. Reference the following DLLs:

    • Sitecore.XConnect.dll
    • Sitecore.XConnect.Client.dll
    • Sitecore.XConnect.Collection.Model.dll
  2. Copy the following code into Program.cs:

using System.IO;

namespace SitecoreCinema.Console.Deploy
{
    public class Program
    {
        public static void Main(string[] args)
        {
            System.Console.WriteLine("Generating your model...");

            var model = SitecoreCinema.Model.Collection.SitecoreCinemaModel.Model;

            var serializedModel = Sitecore.XConnect.Serialization.XdbModelWriter.Serialize(model);

            File.WriteAllText("c:\\temp\\" + model.FullName + ".json", serializedModel);

            System.Console.WriteLine("Press any key to continue! Your model is here: " + "c:\\temp\\" + model.FullName + ".json");
            System.Console.ReadKey();
        }
    }
}
  1. Modify the file path as required, then run the application by choosing the correct startup project and pressing the green Start button:

  2. Copy the JSON file into the xConnect model folder - for example: C:\inetpub\wwwrootR217_xconnect\Website\App_data\Models. You do not need to copy the model DLL.

  3. Create a second console application that will simulate your contact’s journey through the cinema. This journey will include:

    • Using self service to buy a ticket
    • Buying some popcorn and a drink
    • Scanning ticket to get into the movie
  4. Copy the following code into Program.cs. This program simulates 4 interactions of someone visiting the cinema:

using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.WebApi;
using Sitecore.XConnect.Collection.Model;
using Sitecore.XConnect.Schema;
using Sitecore.Xdb.Common.Web;
using SitecoreCinema.Model.Collection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Xdb.Common.Web;

namespace SitecoreCinema.Console.Journey
{
    public class Program
    {
        public static string Identifier { get; set; }

        private static void Main(string[] args)
        {

            // Welcome to...
            //   _____ _ __                            _______
            //  / ___/(_) /____  _________  ________  / ____(_)___  ___  ____ ___  ____ _
            //  \__ \/ / __/ _ \/ ___/ __ \/ ___/ _ \/ /   / / __ \/ _ \/ __ `__ \/ __ `/
            // ___/ / / /_/  __/ /__/ /_/ / /  /  __/ /___/ / / / /  __/ / / / / / /_/ /
            // ____/_/\__/\___/\___/\____/_/   \___/\____/_/_/ /_/\___/_/ /_/ /_/\__,_/

            //   ____            _     _
            //  |  _ \ ___  __ _(_)___| |_ ___ _ __
            //  | |_) / _ \/ _` | / __| __/ _ \ '__|
            //  |  _ <  __/ (_| | \__ \ ||  __/ |
            //  |_| \_\___|\__, |_|___/\__\___|_|
            //             |___/

            // You decide to register for the loyalty card scheme on the website - apparently you
            // get a free popcorn for every 10 times you swipe your card.

            Task.Run(async () => { await Register(); }).Wait();

            //   ___       _                      _   _               _  _   _
            //  |_ _|_ __ | |_ ___ _ __ __ _  ___| |_(_) ___  _ __  _| || |_/ |
            //   | || '_ \| __/ _ \ '__/ _  |/ __| __| |/ _ \| '_ \|_  ..  _| |
            //   | || | | | ||  __/ | | (_| | (__| |_| | (_) | | | |_      _| |
            //  |___|_| |_|\__\___|_|  \__,_|\___|\__|_|\___/|_| |_| |_||_| |_|

            // You cycle to the nearest Sitecore Cinema (which has great bicycle storage facilities, by the way)
            // and use a self service machine to buy a ticket. You swipes your loyalty card - the machine
            // immediately sends this interaction to xConnect. Because you're a loyalty card member
            // you don't even pay at this point!

            Task.Run(async () => { await SelfServiceMachine(); }).Wait();

            //   ___       _                      _   _               _  _  ____
            //  |_ _|_ __ | |_ ___ _ __ __ _  ___| |_(_) ___  _ __  _| || ||___ \
            //   | || '_ \| __/ _ \ '__/ _  |/ __| __| |/ _ \| '_ \|_  ..  _|__) |
            //   | || | | | ||  __/ | | (_| | (__| |_| | (_) | | | |_      _/ __/
            //  |___|_| |_|\__\___|_|  \__,_|\___|\__|_|\___/|_| |_| |_||_||_____|

            // You decides to buy some candy and a bottle of water (gotta stay balanced)
            // You use your loyalty card - that's 2 swipes already! The machine sends this interaction to xConnect.

            Task.Run(async () => { await BuyCandy(); }).Wait();

            //   ___       _                      _   _               _  _  _____
            //  |_ _|_ __ | |_ ___ _ __ __ _  ___| |_(_) ___  _ __  _| || ||___ /
            //   | || '_ \| __/ _ \ '__/ _  |/ __| __| |/ _ \| '_ \|_  ..  _||_ \
            //   | || | | | ||  __/ | | (_| | (__| |_| | (_) | | | |_      _|__) |
            //  |___|_| |_|\__\___|_|  \__,_|\___|\__|_|\___/|_| |_| |_||_||____/

            // Finally, you scan your ticket and head in - your loyalty card details
            // are embedded in the barcode of your ticket. At this point, the system takes payment.

            Task.Run(async () => { await WatchAMovie(); }).Wait();

            System.Console.ForegroundColor = ConsoleColor.DarkGreen;
            System.Console.WriteLine("");
            System.Console.WriteLine("END OF PROGRAM.");
            System.Console.ReadKey();
        }

        private static async Task Register()
        {
            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(SitecoreCinemaModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @"  ____            _     _               ",
                        @"  |  _ \ ___  __ _(_)___| |_ ___ _ __   ",
                        @"  | |_) / _ \/ _` | / __| __/ _ \ '__|  ",
                        @"  |  _ <  __/ (_| | \__ \ ||  __/ |     ",
                        @"  |_| \_\___|\__, |_|___/\__\___|_|     ",
                        @"             |___/                      ",
                        };
                System.Console.WindowWidth = 160;
                foreach (string line in arr)
                    System.Console.WriteLine(line);
                System.Console.WriteLine(); // Extra space
            }
            catch (XdbModelConflictException ce)
            {
                System.Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    ContactIdentifier identifier = new ContactIdentifier("SitecoreCinema", "L94564543543543534" + Guid.NewGuid(), ContactIdentifierType.Known);

                    System.Console.WriteLine("We will generate an ID for you and print it onto a cool card!");
                    System.Console.WriteLine("Alright, your ID is - drumroll please... - " + identifier.Identifier + "! Congratulations. Ctrl+C, Ctrl+V that number into your brain.");

                    // Let's just save this for later
                    Identifier = identifier.Identifier;

                    Contact contact = new Contact(new ContactIdentifier[] { identifier });

                    System.Console.WriteLine("What is your first name?");

                    var firstname = System.Console.ReadLine();

                    System.Console.WriteLine("What is your last name?");

                    var lastname = System.Console.ReadLine();

                    PersonalInformation personalInfo = new PersonalInformation()
                    {
                        FirstName = firstname,
                        LastName = lastname, // It's an ancient family name
                    };

                    System.Console.WriteLine("Favourite movie?");

                    var favouriteMovie = System.Console.ReadLine();

                    CinemaVisitorInfo visitorInfo = new CinemaVisitorInfo()
                    {
                        FavoriteMovie = favouriteMovie
                    };

                    client.AddContact(contact);
                    client.SetFacet<CinemaVisitorInfo>(contact, CinemaVisitorInfo.DefaultFacetKey, visitorInfo);
                    client.SetFacet<PersonalInformation>(contact, PersonalInformation.DefaultFacetKey, personalInfo);

                    var offlineChannel = Guid.NewGuid(); //
                    var registrationGoalId = Guid.NewGuid();

                    Interaction interaction = new Interaction(contact, InteractionInitiator.Brand, offlineChannel, String.Empty);

                    interaction.Events.Add(new Goal(registrationGoalId, DateTime.UtcNow));

                    client.AddInteraction(interaction);

                    await client.SubmitAsync();

                    // var waitForSearch = await client.Contacts.Any();

                    System.Console.WriteLine("Great! See you at the cinema. :) Press any key to continue.");
                    System.Console.ReadKey();
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }

        private static async Task SelfServiceMachine()
        {
            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(SitecoreCinemaModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @" _____ _      _        _   ",
                        @"|_   _(_) ___| | _____| |_ ",
                        @"  | | | |/ __| |/ / _ \ __|",
                        @"  | | | | (__|   <  __/ |_ ",
                        @"  |_| |_|\___|_|\_\___|\__|",
                        };
                System.Console.WindowWidth = 160;
                foreach (string line in arr)
                    System.Console.WriteLine(line);
                System.Console.WriteLine(); // Extra space
            }
            catch (XdbModelConflictException ce)
            {
                System.Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    System.Console.ForegroundColor = ConsoleColor.Cyan;
                    System.Console.WriteLine("You swipe your loyalty card.");

                    System.Console.ForegroundColor = ConsoleColor.White;

                    var identifier = new IdentifiedContactReference("SitecoreCinema", Identifier);

                    var contact = await client.GetAsync<Contact>(identifier, new ContactExpandOptions(PersonalInformation.DefaultFacetKey, CinemaVisitorInfo.DefaultFacetKey));


                    if (contact != null)
                    {
                        var presonalInfo = contact.GetFacet<PersonalInformation>();
                        var cinemaInfo = contact.GetFacet<CinemaVisitorInfo>();

                        System.Console.WriteLine("Why hello there " + presonalInfo.FirstName + " " + presonalInfo.LastName + ", whose favorite film is... " + cinemaInfo.FavoriteMovie + ". Wow, really? OK, to each their own I guess.");

                        var interaction = new Interaction(contact, InteractionInitiator.Contact, Guid.NewGuid(), ""); // GUID should be from a channel item in Sitecore

                        client.SetFacet<CinemaInfo>(interaction, CinemaInfo.DefaultFacetKey, new CinemaInfo() { CinemaId = 22 });
                        interaction.Events.Add(new UseSelfService(DateTime.UtcNow));

                        client.AddInteraction(interaction);

                        await client.SubmitAsync();

                        System.Console.WriteLine("Here's your ticket - we'll charge you when you use it, in case you have some sort of emergency between here and movie.");
                        System.Console.WriteLine("It's just one of those courtesies we offer loyalty card members! Press any key to go buy some candy.");
                        System.Console.ReadKey();
                    }
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }

        }
        private static async Task BuyCandy()
        {
            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(SitecoreCinemaModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @"  ____                _       ",
                        @" / ___|__ _ _ __   __| |_   _ ",
                        @"| |   / _` | '_ \ / _` | | | |",
                        @"| |__| (_| | | | | (_| | |_| |",
                        @" \____\__,_|_| |_|\__,_|\__, |",
                        @"                        |___/ ",
                        };
                System.Console.WindowWidth = 160;
                foreach (string line in arr)
                    System.Console.WriteLine(line);
                System.Console.WriteLine(); // Extra space
            }
            catch (XdbModelConflictException ce)
            {
                System.Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    System.Console.ForegroundColor = ConsoleColor.Cyan;
                    System.Console.WriteLine("You swipe your loyalty card.");

                    System.Console.ForegroundColor = ConsoleColor.White;

                    var identifier = new IdentifiedContactReference("SitecoreCinema", Identifier);

                    var contact = await client.GetAsync<Contact>(identifier, new ContactExpandOptions(PersonalInformation.DefaultFacetKey, CinemaVisitorInfo.DefaultFacetKey));

                    if (contact != null)
                    {
                        var presonalInfo = contact.GetFacet<PersonalInformation>();
                        var cinemaInfo = contact.GetFacet<CinemaVisitorInfo>();

                        System.Console.WriteLine("Why hello again " + presonalInfo.FirstName + "!");
                        System.Console.WriteLine("Candy? You got it.");


                        var interaction = new Interaction(contact, InteractionInitiator.Contact, Guid.NewGuid(), ""); // GUID should be from a channel item in Sitecore
                        client.SetFacet<CinemaInfo>(interaction, CinemaInfo.DefaultFacetKey, new CinemaInfo() { CinemaId = 22 });

                        interaction.Events.Add(new BuyConcessions(DateTime.UtcNow, "DKK", 150m));

                        client.AddInteraction(interaction);

                        await client.SubmitAsync();

                        System.Console.WriteLine("Enjoy the movie! Press any key to continue.");
                        System.Console.ReadKey();
                    }
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }

        private static async Task WatchAMovie()
        {
            CertificateWebRequestHandlerModifierOptions options =
            CertificateWebRequestHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");

            var certificateModifier = new CertificateWebRequestHandlerModifier(options);

            List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
            var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
            clientModifiers.Add(timeoutClientModifier);

            var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
            var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });

            var cfg = new XConnectClientConfiguration(
                new XdbRuntimeModel(SitecoreCinemaModel.Model), collectionClient, searchClient, configurationClient);

            try
            {
                await cfg.InitializeAsync(); // cfg.InitializeAsync();

                // Print xConnect if configuration is valid
                var arr = new[]
                {
                        @" __  __            _      ",
                        @"|  \/  | _____   _(_) ___ ",
                        @"| |\/| |/ _ \ \ / / |/ _ \",
                        @"| |  | | (_) \ V /| |  __/",
                        @"|_|  |_|\___/ \_/ |_|\___|",

                        };
                System.Console.WindowWidth = 160;
                foreach (string line in arr)
                    System.Console.WriteLine(line);
                System.Console.WriteLine(); // Extra space
            }
            catch (XdbModelConflictException ce)
            {
                System.Console.WriteLine("ERROR:" + ce.Message);
                return;
            }

            // Initialize a client using the validated configuration
            using (var client = new XConnectClient(cfg))
            {
                try
                {
                    System.Console.ForegroundColor = ConsoleColor.Cyan;
                    System.Console.WriteLine("You scan your ticket - the bar code has your loyalty card information embedded in it.");

                    System.Console.ForegroundColor = ConsoleColor.White;

                    var identifier = new IdentifiedContactReference("SitecoreCinema", Identifier);

                    var contact = await client.GetAsync<Contact>(identifier, new ContactExpandOptions(PersonalInformation.DefaultFacetKey, CinemaVisitorInfo.DefaultFacetKey));

                    if (contact != null)
                    {
                        var presonalInfo = contact.GetFacet<PersonalInformation>();
                        var cinemaInfo = contact.GetFacet<CinemaVisitorInfo>();

                        System.Console.WriteLine("Enjoy your movie, " + presonalInfo.FirstName + "!");
                        System.Console.WriteLine("Since you're a loyalty card holder, we'll take payment for your ticket now.");

                        var interaction = new Interaction(contact, InteractionInitiator.Contact, Guid.NewGuid(), ""); // GUID should be from a channel item in Sitecore

                        interaction.Events.Add(new WatchMovie(DateTime.UtcNow, "DKK", 100m) { EIDR = "10.5240/08B0-F8C2-5525-BF22-BA07-4" });
                        client.SetFacet<CinemaInfo>(interaction, CinemaInfo.DefaultFacetKey, new CinemaInfo() { CinemaId = 22 });
                        client.AddInteraction(interaction);

                        await client.SubmitAsync();

                        System.Console.WriteLine("Before you go - do you want to see the data we collected about you today? :)");

                        var contactAgain = await client.GetAsync<Contact>(identifier, new ContactExpandOptions(PersonalInformation.DefaultFacetKey, CinemaVisitorInfo.DefaultFacetKey)
                        {
                            Interactions = new RelatedInteractionsExpandOptions(new string[] { CinemaInfo.DefaultFacetKey })
                            {
                                StartDateTime = DateTime.Today
                            }
                        });

                        if (contactAgain != null)
                        {
                            var details = contactAgain.GetFacet<PersonalInformation>();
                            var movie = contactAgain.GetFacet<CinemaVisitorInfo>();

                            System.Console.WriteLine(String.Format("Your name is {0} {1} and your favorite movie is {2}", details.FirstName, details.LastName, movie.FavoriteMovie));

                            System.Console.WriteLine("Today you have had " + contactAgain.Interactions.Count + " interactions with us.");

                            var i = 0;

                            foreach (var interactionsToday in contactAgain.Interactions)
                            {
                                i++;

                                var cinemaId = interactionsToday.GetFacet<CinemaInfo>(CinemaInfo.DefaultFacetKey);

                                System.Console.WriteLine("");

                                System.Console.WriteLine("Interaction #" + i + (cinemaId != null ? " at Cinema #" + cinemaId.CinemaId : String.Empty));
                                System.Console.Write("Events: ");

                                foreach (var evv in interactionsToday.Events) { System.Console.Write(evv.GetType().ToString() + ""); };

                                System.Console.WriteLine("");
                            }
                        }


                        System.Console.ReadKey();
                    }
                }
                catch (XdbExecutionException ex)
                {
                    // Deal with exception
                }
            }
        }
    }
}
  1. Run the application and fill in your details: