Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 4

In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 1, I showed you how you can import contacts from a CSV file into a list in Sitecore. In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 2, I showed you how you can add to the list of out of the box supported fields when importing contacts into Sitecore. In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 3, I showed you how you can create your own custom facets and populate them for your contacts as you import them. If you haven’t already read them, head over and read them first.

In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 4, I’m now going to demonstrate how you can update the Experience Profile for a Contact by Add a new Tab into the User Interface and displaying your Custom Facet information.

Experience Profile Express Tab

Instead of developing all of this code from scratch, we’re going to use a package available in NuGet specifically written for this purpose. No point reinventing the wheel when someone else has already done all the hard work for you.

Experience Profile Express Tab is a package developed to streamline the creation of new tabs in the Experience Profile.

The first step we’ll take is to add the EPExpressTab to our project using NuGet. This will install two packages. EPExpressTab.Core and EXExpressTab. At the time of writing, version 2.0.2 was the latest released version.

To use EP Express Tab, we need to do the following:

  • Create a custom ViewModel that will represent the custom data we wish to display in the Experience Profile Tab
  • Create a custom EPExpressViewModel that will act as a Controller of sorts that will be called by EPExpressTab to populate our custom View Model and pass it to our view
  • Create a View to render the content for out custom tab in Experience Profile

First, let’s create our custom view model. In this example, this will contain just the new Employee fields the same as out custom Facet. Nothing special here. It’s just a standard class to hold our view model.

using System;

namespace Aceik.Feature.EXM.Models
{
    public class EmployeeViewModel
    {
        public string EmployeeId { get; set; }
        public string Department { get; set; }
        public DateTime? StartDate { get; set; }
        public string EmployeeType { get; set; }
    }
}

Next we need to create our EPExpressTab “Controller” class. This class is called by EPExpressTab and is used to generate the instance of our view model and pass it to the view.

using EPExpressTab.Data;
using EPExpressTab.Repositories;
using System;

namespace Aceik.Feature.EXM.Models
{
    public class EPCustomModel : EpExpressViewModel
    {
        public override string TabLabel => "Employee";
        public override string Heading => "Employee Details";

        public override object GetModel(Guid contactId)
        {
            Sitecore.XConnect.Contact model = EPRepository.GetContact(contactId, new string[] { EmployeeInfo.DefaultFacetKey });

            var employeeInfo = model.GetFacet<EmployeeInfo>();

            return new EmployeeViewModel
            {
                EmployeeId = employeeInfo?.EmployeeId,
                Department = employeeInfo?.Department,
                StartDate = employeeInfo?.StartDate,
                EmployeeType = employeeInfo?.EmployeeType
            };
        }

        public override string GetFullViewPath(object model)
        {
            return "/views/Aceik/Feature/EXM/Employee.cshtml";
        }
    }
}

The GetModel method is the action that is called to create the view model instance that will be passed to the view.

The GetFullViewPath method is as described. The method to get the path to the view.

The view itself it pretty straight forward with some inline styles defined, and the html tags etc to render the required custom data.

@model Aceik.Feature.EXM.Models.EmployeeViewModel
<style>
    .employee-border {
        padding-top: 10px !important;
        float: left;
        width: 100%;
    }

    .employee-text {
        display: block;
        float: left;
        width: 150px;
        color: #707070;
        clear: both;
    }

    .employee-value {
        clear: unset;
        padding-left: 95px;
        width: calc(100% - 150px);
        color: #121212;
    }
</style>
<div class="row">
    <div class="col-md-6">
        <span class="sc-text sc-text-value">Employee</span>
        <div class="sc-border employee-border">
            <span class="sc-text employee-text">Employee Id</span>
            <span class="sc-text employee-text employee-value">@Model.EmployeeId</span>
        </div>
        <div class="sc-border employee-border">
            <span class="sc-text employee-text">Department</span>
            <span class="sc-text employee-text employee-value">@Model.Department</span>
        </div>
        <div class="sc-border employee-border">
            <span class="sc-text employee-text">Start Date</span>
            <span class="sc-text employee-text employee-value">
                @if (Model.StartDate.HasValue)
                {
                    @Model.StartDate.Value.ToLocalTime().ToString("dd/MM/yyyy")
                }
            </span>
        </div>
        <div class="sc-border employee-border">
            <span class="sc-text employee-text">Employee Type</span>
            <span class="sc-text employee-text employee-value">@Model.EmployeeType</span>
        </div>
    </div>
</div>

Once this is deployed, along with the added EpExpressTab.config file that is added to the project with the inclusion of the NuGet package, you can now view the “Employee” tab in the Experience Profile that now shows the custom EmployeeInfo Facet data.

So you can examine the solution for yourself, please see below a link to the zip file containing the full Visual Studio solution, CSV files and a Sitecore package for the Import Map configuration items.

AceikEXM.zip

Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 3

In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 1, I showed you how you can import contacts from a CSV file into a list in Sitecore. In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 2, I demonstrated how you can add to the list of out of the box supported fields when importing contacts into Sitecore. If you haven’t read these as yet, best to start with them first.

In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 3, I’m going to share with you how you can create your own custom facets and populate them for your contacts as you import them.

To do this, you are going to need to complete the following tasks:

  • Create a custom facet that will describe the additional data you wish to store for your contacts
  • Build a model for your custom facet that will be used by Xdb to store your custom facet data
  • Serialize your custom model and publish it so that Xdb will recognise your model
  • Create a custom Import mapper class that will be used by the Import process to read your custom fields from the import data and write it to your custom facet
  • Update the Import map configuration so that you can map the fields of your CSV file on import
  • Create the required Sitecore configuration settings for your facet and facet import mapper
  • Deploy the updated/new files to Sitecore and XConnect

Create a custom facet

We are going to store Employee information for each of our contacts. To support that, the first thing we are going to need to do is to create our own custom Facet.

Using Sitecore Helix principles and guidelines, I’ve created a new Feature project in Visual Studio that will contain all the custom code to support our custom EXM requirements. In this project, I’ve started with adding a new Facet class called EmployeeInfo:

using System;
using Sitecore.XConnect;

namespace Aceik.Feature.EXM.Models
{
    public class EmployeeInfo : Facet
    {
        public const string DefaultFacetKey = "EmployeeInfo";
        public string EmployeeId { get; set; }
        public string Department { get; set; }
        public DateTime? StartDate { get; set; }
        public string EmployeeType { get; set; }
    }
}

My EmployeeInfo facet, contains 4 properties that I want to be able to populate for my contacts:

  • EmployeeId
  • Department
  • StartDate
  • EmployeeType

Build a model for your custom Facet

The next thing I need to do is to create an XdbModel for my custom Facet. This is required to be deployed into our XConnect instance and will allow XConnect to know about our custom Facet and be able to store the Facet details for each contact.

using Sitecore.XConnect;
using Sitecore.XConnect.Schema;

namespace Aceik.Feature.EXM.Models
{
    public class EmployeeModel
    {
        public static XdbModel Model { get; } = EmployeeModel.BuildModel();

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

            modelBuilder.ReferenceModel(Sitecore.XConnect.Collection.Model.CollectionModel.Model);
            modelBuilder.DefineFacet<Contact, EmployeeInfo>(EmployeeInfo.DefaultFacetKey);

            return modelBuilder.BuildModel();
        }
    }
}

Note: Pay attention to the XdbModelBuilder parameters. This instructs the model with our custom Facet details.

We now need to run a build on our new project in Visual Studio so that the assembly can be generated with our classes.

Serlialize your custom model

XConnect requires a JSON representation of your XdbModel. We will now create a console application that can be used to generated the required JSON file.

First, Add a new project to your solution in Visual Studio, selecting to use the Console App template.

You will need to add a reference to your Feature project into the Console application as well as Nuget reference to the Sitecore.XConnect package.

Update the program.cs file to look like below:

using Sitecore.XConnect.Serialization;
using System.IO;

namespace Aceik.Feature.EXM.Serialize
{
    class Program
    {
        static void Main(string[] args)
        {
            // File name will be "Aceik.Feature.Mail.Models.EmployeeModel, 1.0.json
            var json = XdbModelWriter.Serialize(Models.EmployeeModel.Model);
            File.WriteAllText("Aceik.Feature.EXM.Models." + Models.EmployeeModel.Model.FullName + ".json", json);
        }
    }
}

Build and execute the console application.

As per the code comments, this will generate a file called “Aceik.Feature.Mail.Models.EmployeeModel, 1.0.json”. This will vary based on your custom model name and versioning you assigned in your XdbModel.

Note: If after deploying your model to XConnect, you need to update it, you will need to update the XdbModel version and deploy the updated JSON file before XConnect will recognise the new properties of your custom facet.

Create a custom import mapper

When a contact is imported via CSV, various ImportMapper classes are executed to process the contact and to populate the various Facets with the data from the CSV.

To populate our custom Facet, we need to create our own custom ImportMapper class to do this.

To process the contacts employee fields, I created my custom import mapper class called EmployeeInfoFacetMapper.

using System;
using System.Globalization;
using Sitecore.Diagnostics;
using Sitecore.ListManagement.Import;
using Sitecore.ListManagement.XConnect.Web.Import;
using Sitecore.XConnect;
using Aceik.Feature.EXM.Models;

namespace Aceik.Feature.EXM.Import
{
    public class EmployeeInfoFacetMapper : IFacetMapper
    {
        public EmployeeInfoFacetMapper() : this(EmployeeInfo.DefaultFacetKey)
        {
        }

        public EmployeeInfoFacetMapper(string facetName)
        {
            Assert.ArgumentNotNull(facetName, nameof(facetName));
            this.FacetName = facetName;
        }

        public string FacetName { get; }

        public MappingResult Map(
          string facetKey,
          Facet source,
          ContactMappingInfo mappings,
          string[] data)
        {
            if (facetKey != this.FacetName)
                return new NoMatch(facetKey);

            var facet = source as EmployeeInfo ?? new EmployeeInfo();

            // EmployeeInfo Properties
            facet.EmployeeId = SetTextField(FacetName, mappings, data, "EmployeeId");
            facet.Department = SetTextField(FacetName, mappings, data, "Department");
            facet.StartDate = SetDateTimeField(FacetName, mappings, data, "StartDate", "dd/MM/yyyy");
            facet.EmployeeType = SetTextField(FacetName, mappings, data, "EmployeeType");

            return new FacetMapped(facetKey, facet);
        }

        private string SetTextField(string facetName, ContactMappingInfo mappings, string[] data, string fieldName) => mappings.GetValue($"{facetName}_{fieldName}", data);

        private DateTime? SetDateTimeField(string facetName, ContactMappingInfo mappings, string[] data, string fieldName, string dateFormat)
        {
            DateTime? fieldValue = null;
            var fieldText = mappings.GetValue($"{facetName}_{fieldName}", data);
            DateTime fieldDate;

            if (DateTime.TryParseExact(fieldText, dateFormat, new CultureInfo("en-AU"), DateTimeStyles.None, out fieldDate))
                fieldValue = fieldDate.ToUniversalTime();

            return fieldValue;
        }
    }
}

This class is instantiated for each contact. First, it verifies that the data being passed to it is for the EmployeeInfo facet. Once that is confirmed, it populates the EmployeeInfo facet properties from the CSV data that is passed into it.

As you can see, I’ve used a couple of private methods to extract and set the string and DateTime values passed in.

Notice that when extracting the values from the mappings data passed in, it uses a pattern to extract the data for the correct field: “{facetName}_{fieldName}”. This pattern matches the Import mappings that we will setup in the next step.

Import Mapping Fields

As per Part 2, we need to configure the custom Import Mapping fields. Once again, switch to the Core database in Sitecore and navigate to this path: “/sitecore/client/Applications/List Manager/Dialogs/ImportWizardDialog/PageSettings/TabControl Parameters/Map/ImportModel”.

Select one of the existing mapping fields and duplicate it giving it a name of “Employee Info – Employee Id”. Populate the FieldName and DataField fields as per below. Note that the DataField value is the value used in the Import Mapper classes to identify the field so these need to be carefully assigned to match your custom Import Mapper class.

Do the same 3 more times to create the custom import mapping fields for:

  • Employee Info – Department
  • Employee Info – Start Date
  • Employee Info – Employee Type

Create the Sitecore configuration

In your project, you will need to create configuration files for the following purposes:

  • Patch the ListManagement.Import.FacetsToMap setting so that it includes your custom Facet name
  • Add your custom FacetMapper to the list of Import Facet Mappers defined in Sitecore
  • Add your schema to the list of schemas defined for XConnect
  • Add your projects path to the Layer configuration so that your configuration files are recognized

In my EXM Helix project, I’ve added the following configuration files:

  • ListManagement.config
  • sc.Aceik.Feature.EXM.Models.EmployeeModel.xml
  • Layers.config
ListManagement.config
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <settings>
      <setting name="ListManagement.Import.FacetsToMap" set:value="Emails|Personal|Addresses|EmployeeInfo" />
    </settings>

    <import>
      <facetMapper type="Sitecore.ListManagement.XConnect.Web.Import.CompositeFacetMapperCollection, Sitecore.ListManagement.XConnect.Web">
        <param resolve="true" type="Sitecore.Abstractions.BaseLog, Sitecore.Kernel"/>
        <facetMappers hint="list:Add">
          <facetMapper type="Aceik.Feature.EXM.Import.EmployeeInfoFacetMapper, Aceik.Feature.EXM" />
        </facetMappers>
      </facetMapper>
    </import>

    <xconnect>
      <runtime type="Sitecore.XConnect.Client.Configuration.RuntimeModelConfiguration,Sitecore.XConnect.Client.Configuration">
        <schemas hint="list:AddModelConfiguration">
          <!-- value of 'name' property must be unique -->
          <schema name="employeemodel" type="Sitecore.XConnect.Client.Configuration.StaticModelConfiguration,Sitecore.XConnect.Client.Configuration" patch:after="schema[@name='collectionmodel']">
            <param desc="modeltype">Aceik.Feature.EXM.Models.EmployeeModel, Aceik.Feature.EXM</param>
          </schema>
        </schemas>
      </runtime>
    </xconnect>

  </sitecore>
</configuration>
sc.Aceik.Feature.EXM.Models.EmployeeModel.xml
<?xml version="1.0" encoding="utf-8"?>
<Settings>
  <Sitecore>
    <XConnect>
      <Services>
        <XConnect.Client.Configuration>
          <Options>
            <Models>
              <EmployeeModel>
                <TypeName>Aceik.Feature.EXM.Models.EmployeeModel, Aceik.Feature.EXM</TypeName>
                <PropertyName>Model</PropertyName>
              </EmployeeModel>
            </Models>
          </Options>
        </XConnect.Client.Configuration>
      </Services>
    </XConnect>
  </Sitecore>
</Settings>
Layers.config
<?xml version="1.0" encoding="utf-8"?>
<layers>
  <layer name="Sitecore" includeFolder="/App_Config/Sitecore/">
    <loadOrder>
      <add path="ContentSearch.Azure" type="Folder" />
      <add path="Reporting" type="Folder" />
      <add path="Marketing.Xdb.Sql.Common" type="Folder" />
      <add path="CMS.Core" type="Folder" />
      <add path="AntiCSRFModule" type="Folder" />
      <add path="Contact.Enrichment.Services.Client" type="Folder" />
      <add path="ContentSearch" type="Folder" />
      <add path="Buckets" type="Folder" />
      <add path="DeviceDetection.Client" type="Folder" />
      <add path="DetectionServices.Location" type="Folder" />
      <add path="ItemWebApi" type="Folder" />
      <add path="Owin.Authentication" type="Folder" />
      <add path="Owin.Authentication.IdentityServer" type="Folder" />
      <add path="TransientFaultHandling" type="Folder" />
      <add path="Messaging" type="Folder" />
      <add path="Processing.Tasks.Messaging.Xmgmt" type="Folder" />
      <add path="Update" type="Folder" />
      <add path="XConnect.Client.Configuration" type="Folder" />
      <add path="Marketing.Xdb.MarketingAutomation.Operations" type="Folder" />
      <add path="Marketing.Xdb.MarketingAutomation.Reporting" type="Folder" />
      <add path="Marketing.Xdb.ReferenceData.Core" type="Folder" />
      <add path="Marketing.Xdb.ReferenceData.Client" type="Folder" />
      <add path="Marketing.Operations.xMgmt" type="Folder" />
      <add path="Marketing.Segmentation.xMgmt" type="Folder" />
      <add path="Marketing.Xdb.MarketingAutomation.Locators" type="Folder" />
      <add path="Marketing.Xdb.ReferenceData.Service" type="Folder" />
      <add path="Marketing.Xdb.ReferenceData.SqlServer" type="Folder" />
      <add path="Marketing.Operations.Xdb.ReferenceData" type="Folder" />
      <add path="Marketing.xDB" type="Folder" />
      <add path="Mvc" type="Folder" />
      <add path="Services.Client" type="Folder" />
      <add path="ExperienceContentManagement.Administration" type="Folder" />
      <add path="Speak.Integration" type="Folder" />
      <add path="Marketing.Tracking" type="Folder" />
      <add path="Tracking.Web.MVC" type="Folder" />
      <add path="Tracking.Web.RobotDetection" type="Folder" />
      <add path="Marketing.Assets" type="Folder" />
      <add path="Marketing.Xdb.MarketingAutomation.Tracking" type="Folder" />
      <add path="SPEAK" type="Folder" />
      <add path="Speak.Applications" type="Folder" />
      <add path="LaunchPad" type="Folder" />
      <add path="Experience Editor" type="Folder" />
      <add path="ContentTagging" type="Folder" />
      <add path="ListManagement" type="Folder" />
      <add path="Marketing.Client" type="Folder" />
      <add path="MVC.ExperienceEditor" type="Folder" />
      <add path="MVC.DeviceSimulator" type="Folder" />
      <add path="Personalization" type="Folder" />
      <add path="ContentTesting" type="Folder" />
      <add path="ExperienceProfile" type="Folder" />
      <add path="ExperienceExplorer" type="Folder" />
      <add path="SPEAK.Components" type="Folder" />
      <add path="ExperienceAnalytics" type="Folder" />
      <add path="CampaignCreator" type="Folder" />
      <add path="ExperienceForms" type="Folder" />
      <add path="ExperienceForms" type="Folder" />
      <add path="FederatedExperienceManager" type="Folder" />
      <add path="Marketing.Automation.Client" type="Folder" />
      <add path="Marketing.Automation.ActivityDescriptors.Client" type="Folder" />
      <add path="EmailExperience" type="Folder" />
      <add path="PathAnalyzer" type="Folder" />
      <add path="UpdateCenter" type="Folder" />
    </loadOrder>
  </layer>
  <layer name="Modules" includeFolder="/App_Config/Modules/" />
  <layer name="Custom" includeFolder="/App_Config/Include/">
    <loadOrder>
      <add path="Foundation" type="Folder" />
      <add path="Feature" type="Folder" />
      <add path="Project" type="Folder" />
    </loadOrder>
  </layer>

  <layer name="Aceik" includeFolder="/App_Config/Aceik/">
    <loadOrder>
      <add path="Foundation" type="Folder" />
      <add path="Feature" type="Folder" />
      <add path="Project" type="Folder" />
    </loadOrder>
  </layer>

  <layer name="Environment" includeFolder="/App_Config/Environment/" />
</layers>

Deploy to Sitecore and XConnect

Deploy the EXM helix project to Sitecore using your usual method. In my case, I used the publish option in Visual Studio and deployed the files to the website folder for my Sitecore instance.

To deploy the files to XConnect, this was not so simple. I manually deployed the following files to these locations in my XConnect instance:

  • sc.Aceik.Feature.EXM.Models.EmployeeModel.xml => \App_Data\jobs\continuous\AutomationEngine\App_Data\Config\sitecore
  • Aceik.Feature.EXM.dll => \App_Data\jobs\continuous\AutomationEngine
  • Aceik.Feature.EXM.Models.EmployeeModel, 1.0.json => \App_Data\Models

After deploying the files to XConnect, you will need to go and restart the XConnect services:

  • Marketing Automation Service
  • Processing Engine Service
  • Index Worker

Test the Import

Now that you have deployed the code and configuration changes to both Sitecore and XConnect, applied the Import Mapper configuration changes, you can now import your “Employees” from a CSV file. I have amended the CSV used in the Part 2 adding in the additional Employee specific columns:

email, firstname, middlename, lastname, gender, jobtitle, address1, address2, city, postcode, state, country,Employee ID,Department,Start Date,Employee Type
alberte@gmail.com,Albert,Basil,Einstein,Male,Scientist,Level 12,100 New York Lane, New York,25438, NY, US,FT0195,Science,23/01/1869,Fulltime
blaise@gmail.com,Blaise,Patricia,Pascal,Male,Mathematician,125 The Pond,,Paris,7321AE,PA, FR,CA0125,Mathematics,08/10/1915,Casual
cherschel@yahoo.com,Caroline,Jenny,Herschel,Female,Astronomer,34 The Strait,,Hanover,123BCF,Lower Saxony, DE,PT4567,Astronomy,05/05/1979,Permament Part-time
dorothyhodgkin@live.com,Dorothy,Anne,Hodgkin,Female,Chemist,87 Downing Street,,Westminister,SW1A,London,UK,FT0099,Science,13/08/1940,Fulltime
edmond145@facebook.com,Edmond,William,Halley,Male,Astronomer,114 ChittingHam Way,,Greenwich,SW1A,Kent,UK,CA0087,Astronomy,01/10/1985,Casual

I then imported these “Employees” using the same process as previously, with the only difference being that I now mapped the additional Employee fields.

Import from CSV file
Imported Employees

As you can see the Employees imported in the same manner as previously. Now, when I open the details of one of the Employees, I see no difference to before.

Contact Details

In the last and final part of this series, I’ll show you how to update the Experience Profile with a new Tab that you can use to display your custom Facet details. Click here for Part 4.

Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 2

In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 1, I showed you how you can import contacts from a CSV file into a list in Sitecore EXM. Head over to Part 1 if you haven’t read that one as yet.

The properties of a contact that we are able to populate out-of-the-box are configured to the following fields:

  • Email
  • First name
  • Last name

Sitecore can be configured to support importing additional properties. In this article, I will show you how to update the configuration to support importing additional contact properties such as:

  • Middle name
  • Gender
  • Job Title
  • Address

Configure the Import Model

We need to first update the configuration of the import model. The import model is used to define the list of mapping fields that are displayed in the Contact Import dialog.

Start by launching the Sitecore Desktop from the Sitecore Launchpad. From there click on the master database in the lower right hand corner and select “core”. This switches the context to the core database. You can now launch the Content Editor by clicking on the Sitecore start button and selecting “Content Editor”.

Launch Sitecore Desktop
Switch to the Core database
Launch the Content Editor

In the Content Editor, navigate to the following path:

/sitecore/client/Applications/List Manager/Dialogs/ImportWizardDialog/PageSettings/TabControl Parameters/Map/ImportModel

Here you will see the list of configured field mappings. Will we now add to the list of field mappings by using the predefined available list of additional fields. Rich-Click on the “Import Model” node and using the Insert submenu, choose to add the following additional fields:

  • Personal – Middle name
  • Personal – Gender
  • Personal – Job title
  • Preferred Address – Address Line 1
  • Preferred Address – Address Line 2
  • Preferred Address – City
  • Preferred Address – Postal code
  • Preferred Address – State or Province
  • Preferred Address – Country code

The ordering of the fields is not mandatory but it’s worth putting into a logical order as this will be the order they are displayed in, in the contact import dialog.

Updated Import Model configuration

Now, go through the same process to create a new list and import the contacts from a CSV file. This time, when you reach the step of mapping the fields, you will notice there is additional fields to map. They are the new fields we just added to the Import Model is the previous step. Complete the mapping and the import as you did in the previous article.

Updated list of mapping fields
Updated list of mapping fields
Updated list of mapping fields

Now, when you open one of the imported contacts in Experience Profile, you will see the additional properties that have been imported:

Newly populated fields from the updated Import Model

In the Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 3, I will show you how to add custom properties to a Contact and have those properties populated via the Contact Import process.

Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 1

Are you a Sitecore developer looking to import contacts into Sitecore? Do you need to store properties for your contacts that are not supported out of the box by Sitecore?

In this multi-part series of posts, I will show you how you can import your contacts from a text file. I’ll show you how you can update the out of the box configuration to include importing other standard properties for your contacts. I’ll also show you how you can add your own custom properties for your contacts. And lastly, I’ll show you how you can update the Experience Profile UI so that your custom properties can be displayed for your contacts.

Contacts and Lists

Sitecore EXM supports the concept of both Contacts and Lists.

A contact itself is pretty self-explanatory and contains all the properties for the contact. Here are some of examples of the properties stored for a contact:

  • Email addresses
  • First, middle and last names
  • Gender
  • Birthday
  • Addresses
  • Job title

A list is simply a collection of contacts. A contact can be a member or a subscriber to multiple lists. Lists can be either static (a contact has been added to the list) or a list can be dynamic. Dynamic lists are populated based on a set of rules that uses the contact properties to determine if they should be a member of the list.

Importing Contacts

Out-of-the-box, Sitecore supports importing contacts from a CSV file. Your list of contacts to be imported need to have a minimum of the following fields:

  • Email address
  • First name
  • Last name

Sample CSV file

email, firstname, lastname
alberte@gmail.com,Albert,Einstein
blaise@gmail.com,Blaise,Pascal
cherschel@yahoo.com,Caroline,Herschel
dorothyhodgkin@live.com,Dorothy,Hodgkin
edmond145@facebook.com,Edmond,Halley

To import the contacts, go to the Sitecore Launchpad and click List Manager.

Once you are in the List Manager, follow these steps to import your contact list:

  • Click on the “Create” button
  • Select “Create list from file”
  • Click on the “Browse for a CSV file” button and select your csv file containing your contacts. Note: Make sure your file is named with a .csv extension
  • Enter a name for your list
  • Click on the “Upload file” button
Create a new list
Create the new list from a CSV file
Give your new list a name and upload the CSV file

Once the file has been uploaded, the process will prompt you to choose the mappings between the predefined Contact properties and the columns in your CSV file. If your CSV file contains columns you would like to use for the contact identifier and/or the identifier source, you can optionally check those and then be able to select the column mappings for those Contact properties.

Click “Next” to being the import process.

Nominate the field mappings

Once the import process has been completed, you will be shown a summary of what has been imported. When you click on the finish button, you will then be taken to your list that you have just imported. Here you can see the contacts that have been added to your new list.

Import Summary
Newly created list

You can also view the contact details by navigating back to the Sitecore Launchpad and launching the Experience Profile. There you can see the entire list of contacts. You can click into any of the contacts to see the details of the contact.

Experience Profile showing all your contacts
Contact details

There are additional properties that are shown in the experience profile that we can also populate via the CSV import process. To do that, we will need to customise the list of available properties that the importer allows to be mapped to.

In Sitecore Email Experience Manager (EXM), Importing Contacts and Custom Facets – Part 2, I will show you how you can customise that list of fields and import additional standard properties for each contact.

Sitecore 10 Content Serialization and best practices – Part 3

SCS is pretty new, and there are a few basic recommendations while doing the first setup, In this blog, we will discuss those settings and options.

This blog has split into three parts, and this is part 3.

  1. Configure the relative serialization path correctly. In Microsoft NTFS the maximum length is 240. In SCS the content serialization path size limit is defined in the settings, by default, the value is 100.
{
  "$schema": "./.sitecore/schemas/RootConfigurationFile.schema.json",
  "modules": [
    "src/*/*/*.module.json"
  ],
  "serialization": {
    "defaultMaxRelativeItemPathLength": 100,
    "defaultModuleRelativeSerializationPath": "items"
  }
}

The maximum relative item path length = the file system maximum path and file name length – (the base path length + the serialization path length). for more details, please follow this link – Configure the maximum relative item path length

2. Define the hash and alias for relative serialization path – Hashing to shorten paths and aliases and to make it human readable.

It’s super important for two purposes. One is to mitigate the risk of serialization path, and second is define the structure of the project like we can define common aliases for the site.

Relative path is combination of four section (Base path + serialization path +include path content item path) , for example

C:\Users\jsoni\Sitecore\Project\src\Sites\ +  serialization\ + content\ + Home\Products\P1\test.yml

define the alias

"rules": [
  {
    "path": "/Home/Products/P1/test",
    "alias": "alpath"
  }
]

3. Sequence is most important while defining the module.json – As a rule of thumb with Sitecore, all the dependencies should be defined first, for example before defining the content we have to define the templates, layout and rendering etc.

for example

{
  "namespace": "Feature.Hero",
  "items": {
    "includes": [
      {
        "name": "templates",
        "path": "/sitecore/templates/Feature/Hero",
        "allowedPushOperations": "createUpdateAndDelete",
        "scope": "itemAndDescendants"
      },
      {
        "name": "layouts",

        "path": "/sitecore/layout/Renderings/Feature/Hero",
        "allowedPushOperations": "createUpdateAndDelete",
        "scope": "itemAndDescendants"
      }
    ]
  }
}

4. Don’t forget first-match-wins principle while defining the IA or base principle of the content sync strategy – this rule says when a content item matches a rule, all subsequent rules are ignored:

As per the Sitecore recommendation, we need Keep the following things in mind when we configure rules:

  • No path overlapping, If you follow the Helix principles I’m sure it will be much easier, otherwise it can be very hard to identify any path overlaps.
  • Again, Parent rules will override the child rule, example if you have mentioned a rule on parent item node, It will override any child rule configuration.
  • Rule scopes cannot be more inclusive than the root scope. For example, if the root scope is ItemAndChildren, the rule scope cannot be ItemAndDescendants.
  • The alias property in a rule replaces the root name property (the folder name in your file system) for this particular rule.
  • If you have configured an alias property and a scope property with an ignored value, the scope is used. Content items scoped to be ignored are not influenced by aliases.

For more details, please follow below links

I hope this blog will help you to think of a few fundamental considerations while starting to work with SCS on Sitecore.

Sitecore 10 Content Serialization and best practices – Part 2

Unicorn was the most popular plugin for content serialization. It was very straightforward and well managed; all the documents were very descriptive and easy to understand. Now we have Sitecore Content Serialization (SCS), I think we must understand what has changed. Are we losing or gaining any functionality? what changes are required to our existing setup? Re there any changes in terminology etc?

This blog has been split into three parts, and this is part two.

Below is the high-level comparison between Unicorn and SCS.

  1. SCS has an additional Visual Studio plugin to manage Content.

VS plugin provides the below options –

1. Option to push and pull changes
2. Differences between disk and database content
3. Option for selected item sync

SCS has Sitecore command line (CLI) Interface and Unicorn has it’s own page.

SCS configuration is based on JSON files and Unicorn configuration is based on XML.

The configuration file naming convention is different. The SCS configuration file names contain modules for identification and Unicorn configuration contains serialization – Although this is configurable.

The initial setup is different. For SCS we have to setup the package and to enable the CLI it’s required to install the packages.

Difference in global/shared configuration

Difference in project level configuration.

Difference in feature/module level configuration.

SCS provides more flexibility for rule based configuration, for each feature we can define a rule and each rule has the option to define the path, scope and allowed PushOperations etc.

There are changes in the configuration.

When using Unicorn we used to define the dependencies and extends options

<configuration name="Feature.Demo" description="Feature Demo" dependencies="Foundation.*" extends="Helix.Feature">

With SCS, we need to handle it while defining the feature itself and any overlap, for example if there is any specific overlap we need to make sure to put the most specific rule first.

Rule of Parent’s path override rule of children’s and dependent path.

Overall, I can see the SCS configuration is much easier and more flexible. However, I’m still looking for an option for pattern-based formats in SCS.

I hope this comparison will provide a basis to understand the difference between Unicorn and SCS.

Sitecore 10 Content Serialization and best practices – Part 1

Sitecore has introduced Sitecore Content Serialization (SCS) as part of version 10. In this blog, I will explain the basic concept of serialization and compare all Unicorn features, followed by steps for the basic setup and best practices.

This blog has split into three parts, and this is part 1.

Part 1 – Sitecore 10 SCS- Basic concept

Let’s first try to understand the definition, Serialization is the process of translating a data structure or object state into a format that can be stored (for example, in a file or memory data buffer) or transmitted (for example, across a computer network) and reconstructed later (possibly in a different computer environment) – Wikipedia

Sitecore Content Serialization (SCS) is a system for serializing, sharing, and deploying content items, as well as keeping them in version control – Sitecore

Now, we know the definition of serialization. I remember with the Sitecore 6.X Serialization guide we had an option to serialize the item tree and restore it, there were additional options to configure the serialization based on the events like item saved, created, deleted etc. and we were using SVN to store those serialized files.

Growing of TDS (Team Development for Sitecore) foundation

TDS was beneficial to manage the project and item files. I used TDS a lot with Sitecore 6.X versions. We were also using this as part of the deployment process as the underlying TDS was using the MSBuild project file to group the Sitecore items into a deployable project. Additionally to that, it has the below features which are not part of SCS.

  • File deployment into a local Sitecore instance
  • Web Deployment Package (WDP) generation
  • Code generation
  • Validators
  • Environment Validation

Note – Paid license is required for TDS and the cost was around USD 400

In my opinion it’s the end of the TDS era.

Growing of Unicorn (/unicorn.aspx)

Unicorn is a Sitecore utility and it is open source. It solves the issue of moving templates, renderings, and other database items between Sitecore instances. TDS is a monolithic product with commercial support, and marketing does a lot more than just serialization. Unicorn is relatively simple, free and open-source, and does one thing well.

I prefer using Unicorn instead of TDS. Generating the TDS code was not that easy and always needed to take care of the partial class, unnecessary field generation, etc. We can set up the template class following the helix principals.

For more details about the setup, Here is my blog post – Sitecore – Unicorn setup, patterns and tips.

In my opinion Sitecore content serialization would be a replacement of Unicorn.

Sitecore Content Serialization – Future with Sitecore 10 and current focus

SCS will do the out-of-the-box item serialization that lets you automate the synchronization of item changes. It has two options one is using the command-line interface (CLI) and the second Sitecore for Visual Studio (SVS)

Note – for using SVS it’s required to get the license and TDS and SVS are both offered under the same license.

Basic Settings and configurations

Out of the box there are three options to serialize content, The default serialization format (.Item)

  • Manually serialize, update, and revert items in the Content Editor.
  • Use Sitecore event handlers to automatically serialize items.
  • Use the Sitecore service page to serialize, update, and revert a whole database.

Item format

YAML format – using Unicorn and Sitecore content serialization.

This article is the first part to explain the basics of serialization, in the next Part 2- Compare SCS with Unicorn, I will explain the difference between Unicorn vs SCS setup.

I hope this article will help you to understand the basic concept of serialization.

Sitecore Power with ASP.NET Core and why it’s important.

Sitecore has recently introduced a development SDK with ASP.NET Core. In this blog, I will explain why it’s super important for the Business to start thinking about it and how it will change the way of Sitecore development.

In my view, technology is an essential part of running a successful business, and it keeps changing for a better purpose. It’s tough for the Business to make the right decision to choose the right technology and plan for the investment as there are a lot of factors involved like cost, technology choice, stability, future, support, extension, security, machine learning, AI, robotics etc.

Off Couse, every Business would prefer to make a one-time investment and to reuse that investment to cater to any new technology.

Is it possible today to make a one-time investment? I think it’s not, but a company like Microsoft and Sitecore they have been putting all efforts to make it happen and the release of Sitecore with ASP.NET core SDK is a big step towards that dream. In my opinion, The secret of success is to make the right choice at the right time, and don’t devote all energy in fixing the old guys.

Before getting into more details, Let’s understand the key part of the puzzle..

Why Sitecore with ASP.NET Core is so important?

Microsoft has started moving all the focus from the previous development framework (.NET Framework) to .NET Core, Why?, Okay, because .NET Framework 4.8, which was announced on 18th April 2019 will be the last major version of .NET Framework. The visual studio which is the primary IDE for the most of the development is itself is built-in .NET Framework, Although Microsoft will continue to provide the support, as I said in my first few lines, It’s a matter of making the right decision at the right time. below are some key features and needs for the changes.

  • Cross-platform & container support
  • High performance
  • Asynchronous via async/await
  • Unified MVC & Web API frameworks
  • Multiple environments and development mode
  • Dependency Injection
  • WebSockets & SignalR and  Cross-Site Request Forgery (CSRF) Protection
  • “Self hosted” Web Applications
  • Globalization and Localization
  • Swagger OpenAPI built in

SitecoreASP.NET Core rendering SDK enables SItecore headless development with ASP.NET core. This rendering SDK is in addition to the already existing Javascript Service rendering SDKs of React, Angular, Vue and JavaScript libraries. below is the flow diagram and it makes Sitecore solutions faster and easier to develop, maintain, scale, and upgrade by splitting them up into a Sitecore backend and a ASP.NET rendering host frontend. And we can build rendering hosts with the Sitecore ASP.NET Rendering SDK. That’s a fantastic option. here is a high level flow diagram.

Microsoft has already planned roadmap until 2023 releases, so this shows how important it’s that Sitecore goes with the .Net Core platform.

Source – https://dotnet.microsoft.com/platform/support/policy/dotnet-core

The most important part is, there are a lot of front end technology like Angular, React, Vue.jS, Flutter, Microsoft has launched the new platform called Blazor which will allow C# developer (Sitecore technology) stack to write the web interactive UI through the same language, WOW that’s really amazing, Microsoft has already unified the infrastructure with Azure Cloud provider, development technology with .Net Standard and front end technology through Blazor, so now to develop any complex code that includes Azure, backed like Machine learning , AU and front end all can be done through the same technology and Sitecore has release official SDK for that, so that’s the reason is very important for the business as well as for the development.

Let’s talk about the development and architecture

There are two main parts, One is rendering host front end and second is Sitecore instance backend.

  • Rendering Host – The rendering host front end is a web app made up of the Sitecore ASP.NET Rendering SDK code and static resources. The job of the rendering host is to respond to visitor requests.
  • The Sitecore instance– exposes a set of endpoints like web based native rendering hosts or third party integration
Source – https://doc.sitecore.com/developers/100/developer-tools/en/sitecore-headless-development.html

1. Setting up the sample project-

Below are the prerequisites for the installation.

Follow these steps in case of any error, Please see the reference in Troubleshooting section in the below page.

Below are simple command to setup the local environment.

dotnet new -i Sitecore.DevEx.Templates --nuget-source https://sitecore.myget.org/F/sc-packages/api/v3/index.json (Install template)

dotnet new sitecore.aspnet.gettingstarted -n MyProject (Create a new project)

.\init.ps1 -InitEnv -LicenseXmlPath "<path to your license.xml file>" -AdminPassword "<your Sitecore administrator password>“  (Prepare the container)

.\up.ps1 – (Download the Sitecore Docker images and install the containers)
Docker-compose up –d and Docker ps -a

Once you follow above steps, You should be able to see the working Sitecore instance running inside docker with ASP.NET core.

2. Below are the steps to setup the JSON rendering.

Setup the template in the project and add the standard value, As example below, Ref – https://doc.sitecore.com/developers/100/developer-tools/en/walkthrough–creating-a-simple-rendering-with-a-data-source.html

Add JSON rendering and placeholder settings As mention below.

Add a new component in the solution, A new model, View and register it.

Verify Traefik

  1. Reverse proxy
  2. Traefik provides the friendly URL for each of the instances.
  3. Low configuration setup.
  4. SSL termination

Benefit of using ASP.NET core with Sitecore.

  • Super fast.
  • Fully integrated with Visual Studio.
  • Sitecore headless development is based on ASP.NET Core, there are also fewer problems when doing the native integration

Below features are not supported.

  • Horizon
  • Edit frames
  • Sitecore Forms
  • Invocation of xConnect events, goals and outcomes from c#
  • Managed Cloud Standard and Managed Cloud Premium do not currently offer headless topologies for rendering hosts.

Helper

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

Troubleshooting

  • First issue while the setup was ‘Invoke-WebRequest’ is not recognized as an internal or external command

Root cause analysis and Solution.

Need to update the default shell to PowerShell for dotnetsdk –

C:\Projects\RenderingSDK\MyProject\docker\build\dotnetsdk\Dockerfile

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
  • C:\Program Files\dotnet\sdk\5.0.100\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(241,5): error NETSDK1005: Assets file ‘C:\build\src\rendering\obj\project.assets.json’ doesn’t have a target for ‘netcoreapp3.1’. Ensure that restore has run and that you have included ‘netcoreapp3.1’ in the TargetFrameworks for your project. [C:\build\src\rendering\renderinghost.csproj]

Initially, I thought it’s related to version, As I had installed both dotnetcore SDK 3.1 and 5.0, but finally had to update above command.

Extension – In case if need any extension like xConnect events and to trigger a goals, there are always a way, We can Instantiate client in a non-Sitecore context, Reference Link and can add interaction like to trigger a goal

Finally, A few development tips.

  • How to check the files running inside the containers through the VIsual Studio.
  • Dotnet Sitecore Help command
 dotnet sitecore --help
  • Sitecore login command
  dotnet sitecore login 
  • Push the serialized items command – SCS (Sitecore content serialization)
 dotnet sitecore ser push 
  • SCS (Sitecore content serialization) Publish command
 dotnet sitecore publish

Git link for the ASP.NET core Helix

https://github.com/Sitecore/Helix.Examples/tree/master/examples/helix-basic-aspnetcore

I hope this will help to start the development with ASP.NET core with Sitecore 10

Video reference –

I have also presented in Sitecore User Group Pune – Nov 2020 and explain these details.