I am using Sitecore 9.0.1 and I am trying to insert custom tokens to email in EXM. I followed Sitecore's documentation and spent hours investigating possible solutions but somehow my tokens did not get filled.

Here are the steps that I took:

  1. Created Facet,
  2. Created Model for facet and then deployed model (JSON to xConnect, this should work for xconnect side),
  3. Added new fields to /sitecore/client/Applications/List Manager/Dialogs/ImportWizardDialog/PageSettings/TabControl Parameters/Map/ImportModel named them FacetName_FieldName,
  4. Created FacetMapper and PropertyTokenMap and configured them.

I checked multiple times for possible configuration error, but I can't get tokens to fill.

Can someone help me debug this?

Here is the code reference :

Facet:

 namespace Sitecore.Feature.EmailConfiguration.Facets{
[Serializable]
[FacetKey(DefaultFacetKey)]
public class EmailUser:Sitecore.XConnect.Facet
{
    public const string DefaultFacetKey = "EmailUser";
    public string Username { get; set; }
    public string Password { get; set; }
    public EmailUser()
    {

    }
}
}

Model:

namespace Sitecore.Feature.EmailConfiguration.Models
    {
    public  class EmailUserModel
    {
        public static XdbModel Model { get; } = EmailUserModel.BuildModel();

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

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

            return modelBuilder.BuildModel();
        }
    }
}

FacetMapper:

namespace Sitecore.Feature.EmailConfiguration.Mappers
{
    public class EmailUserFacetMapper : IFacetMapper
    {
        private readonly PreferredEmailFacetMapper mapper;

        public EmailUserFacetMapper(): this(CollectionModel.FacetKeys.AddressList)
        {
        }
        public EmailUserFacetMapper(string facetName)
        {
            Assert.ArgumentNotNull(facetName, nameof(facetName));

            this.FacetName = facetName;
        }

        public string FacetName { get; }

        public MappingResult Map(string facetKey, Facet facet, ContactMappingInfo mappings, string[] data)
        {
            using (EventLog eventLog = new EventLog("Application"))
            {
                eventLog.Source = "Application";
                eventLog.WriteEntry("EmailUserFacetMapperCalled facetKey:" +facetKey+" data"+ string.Join("; ", data));
            }

            if (facetKey != this.FacetName)
            {
                return new NoMatch(facetKey);
            }

            var emailUser = new EmailUser();

            var username = mappings.GetValue("EmailUser_Username", data);
            var password = mappings.GetValue("EmailUser_Password",data);

            if (!string.IsNullOrEmpty(username)) { emailUser.Username = username; }
            if (!string.IsNullOrEmpty(password)) { emailUser.Password = password; }

            return (MappingResult)new FacetMapped(facetKey, (Facet) emailUser);
        }
    }
}

PropertyTokenMap:

namespace Sitecore.Feature.EmailConfiguration.TokenMaps
{
    public class EmailUserPropertyTokenMap : DefaultRecipientPropertyTokenMap
    {
        protected static readonly MethodInfo GetUsernameValue = typeof(FacetExtensions).GetMethod(nameof(FacetExtensions.GetUsernameValue), new[] { typeof(EmailUser) });
        protected static readonly MethodInfo GetPasswordValue = typeof(FacetExtensions).GetMethod(nameof(FacetExtensions.GetPasswordValue), new[] { typeof(EmailUser) });




        static EmailUserPropertyTokenMap()
        {
            if (TokenBindings == null)
            {
                TokenBindings = new Dictionary<Token, RecipientPropertyTokenBinding>();
            }
            RecipientPropertyTokenBinding usernameTokenBinding = RecipientPropertyTokenBinding.Build<EmailUser>(new Token("username"), null, GetUsernameValue);
            RecipientPropertyTokenBinding passwordTokenBinding = RecipientPropertyTokenBinding.Build<EmailUser>(new Token("password"), null, GetPasswordValue);

            TokenBindings.Add(usernameTokenBinding.Token, usernameTokenBinding);
            TokenBindings.Add(passwordTokenBinding.Token, passwordTokenBinding);
        }
    }
    public static class FacetExtensions
    {
        public static string GetUsernameValue(this EmailUser facet)
        {
            return facet.Username;
        }

        public static string GetPasswordValue(this EmailUser facet)
        {
            return facet.Password;
        }
    }
}

Xml config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set">
  <sitecore>
    <exm>
      <dispatchTask type="Sitecore.Feature.EmailConfiguration.DispatchTasks.EmailUserDispatchTask, Sitecore.Feature.EmailConfiguration" patch:instead="*[@type='Sitecore.EmailCampaign.Cm.Dispatch.DispatchTask, Sitecore.EmailCampaign.Cm']">
        <param ref="exm/dispatchFailedTaskPool"/>
        <param desc="recipientValidator" ref="exm/recipientValidator" />
        <param desc="contactService" ref="exm/contactService" />
        <param desc="dataProvider" ref="exm/dataProvider" />
        <param desc="itemUtil" ref="exm/itemUtil" />
        <param desc="eventDataService" ref="exm/eventDataService" />
        <param desc="dispatchManager" ref="exm/dispatchManager" />
        <param desc="emailAddressHistoryManager" ref="exm/emailAddressHistoryManager" />
        <param desc="recipientManagerFactory" ref="exm/recipientManagerFactory" />
        <param desc="sentHistoryManager" ref="exm/sentHistoryManager" />
      </dispatchTask>
    </exm>
    <recipients>
      <recipientPropertyTokenMap type="Sitecore.Feature.EmailConfiguration.TokenMaps.EmailUserPropertyTokenMap, Sitecore.Feature.EmailConfiguration" singleInstance="true" patch:instead="*[@type='Sitecore.Modules.EmailCampaign.Core.Personalization.DefaultRecipientPropertyTokenMap, Sitecore.EmailCampaign']" />
    </recipients>

    <import>
      <facetMappers hint="list:Add">
      <facetMapper type="Sitecore.Feature.EmailConfiguration.Mappers.EmailUserFacetMapper,Sitecore.Feature.EmailConfiguration" />
        </facetMappers>
    </import>
    <settings>
      <setting name="ListManagement.Import.FacetsToMap">
        <set:attribute name="value">Emails|Personal|Addresses|EmailUser</set:attribute>
      </setting>
    </settings>

 <xconnect>
      <runtime type="Sitecore.XConnect.Client.Configuration.RuntimeModelConfiguration,Sitecore.XConnect.Client.Configuration">
        <schemas hint="list:AddModelConfiguration">
              <schema name="EmailUserModel" type="Sitecore.XConnect.Client.Configuration.StaticModelConfiguration,Sitecore.XConnect.Client.Configuration">
                  <param desc="modeltype">Sitecore.Feature.EmailConfiguration.Models.EmailUserModel,Sitecore.Feature.EmailConfiguration</param>
              </schema>
        </schemas>
      </runtime>
    </xconnect>

  </sitecore>
</configuration>

Core database import wizard map

Update: I forgot to put Dispatch task, also i tried debuging this and it loads only 4 other facets without my facet. My test case is load csv i list Manager and then i send test tokens including default and my tokens. Default tokens work but my tokens are empty.

DispatchTask:

    namespace Sitecore.Feature.EmailConfiguration.DispatchTasks
{
    public class EmailUserDispatchTask :DispatchTask
    {
        private IContactService _contactService;
        public EmailUserDispatchTask(ShortRunningTaskPool taskPool, IRecipientValidator recipientValidator, IContactService contactService, EcmDataProvider dataProvider, ItemUtilExt itemUtil, IEventDataService eventDataService, IDispatchManager dispatchManager, EmailAddressHistoryManager emailAddressHistoryManager, IRecipientManagerFactory recipientManagerFactory, SentMessageManager sentMessageManager)
        : base(taskPool, recipientValidator, contactService, dataProvider, itemUtil, eventDataService, dispatchManager, emailAddressHistoryManager, recipientManagerFactory, sentMessageManager)
        {
            _contactService = contactService;
        }
        protected override IReadOnlyCollection<IEntityLookupResult<Contact>> GetContacts(List<DispatchQueueItem> dispatchQueueItems)
        {
            var test= _contactService.GetContacts(dispatchQueueItems.Select(x => x.ContactIdentifier), PersonalInformation.DefaultFacetKey, EmailAddressList.DefaultFacetKey, ConsentInformation.DefaultFacetKey, PhoneNumberList.DefaultFacetKey, ListSubscriptions.DefaultFacetKey, EmailAddressHistory.DefaultFacetKey, EmailUser.DefaultFacetKey);
            return test;
        }
    }

}

Here is my Json generated for adding model to xconnect to locations xconnect_root/App_data/Models and xconnect_root\App_data\jobs\continuous\IndexWorker\App_data\Models

{
  "Name": "EmailUserModel",
  "Version": "0.1",
  "References": [
    {
      "Name": "XConnect",
      "Version": "1.0"
    },
    {
      "Name": "Sitecore.XConnect.Collection.Model",
      "Version": "9.0"
    }
  ],
  "Types": {
    "Sitecore.Feature.EmailConfiguration.Facets.EmailUser": {
      "Type": "Facet",
      "BaseType": "Sitecore.XConnect.Facet",
      "ClrType": "Sitecore.Feature.EmailConfiguration.Facets.EmailUser, Sitecore.Feature.EmailConfiguration, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "Properties": {
        "Username": {
          "Type": "String"
        },
        "Password": {
          "Type": "String"
        }
      }
    }
  },
  "Facets": [
    {
      "Target": "Contact",
      "Name": "EmailUser",
      "Type": "Sitecore.Feature.EmailConfiguration.Facets.EmailUser"
    }
  ]
}

Update 2: Seems that my Model does not get registered for some reason. I found information that if facet is registered than it will appear in database {siteprefix}_Xdb.Collection.Shard0].[xdb_collection].[ContactFacets] . Is there working example somewhere of adding model to xconnect?

up vote 3 down vote accepted

The problem is most likely with your "EmailUserDispatchTask".

You'll need to specify your facet key in the override of the GetContacts method i.e.

protected override IReadOnlyCollection<IEntityLookupResult<Contact>> GetContacts(List<DispatchQueueItem> dispatchQueueItems)
{
    return _contactService.GetContacts(dispatchQueueItems.Select(x => x.ContactIdentifier), PersonalInformation.DefaultFacetKey, EmailAddressList.DefaultFacetKey, ConsentInformation.DefaultFacetKey, PhoneNumberList.DefaultFacetKey, ListSubscriptions.DefaultFacetKey, EmailAddressHistory.DefaultFacetKey, ExmKeyBehaviorCache.DefaultFacetKey, EmailUser.DefaultFacetKey);
}

Your custom code is otherwise correct, and works just fine on my end.

It may be that your facet is not imported correctly. You can try to manually create a contact in order to verify. First, create an empty contact list in List Manager and copy its id, then programatically create a contact e.g.

using (IXdbContext client = SitecoreXConnectClientConfiguration.GetClient())
{
    var contact = new Sitecore.XConnect.Contact(new ContactIdentifier("ListManager", "someemail@domain.com", ContactIdentifierType.Known));

    client.AddContact(contact);

    client.SetEmails(contact, new EmailAddressList(new EmailAddress("someemail@domain.com", false), "Preferred"));

                client.SetListSubscriptions(contact, new ListSubscriptions()
                {
                    Subscriptions = new List<ContactListSubscription>()
                    {
                        new ContactListSubscription(DateTime.UtcNow, true, listId)
                    }
                });

    client.SetEmailUser(contact, new EmailUser()
                {
                    Password = "password",
                    Username = "username"
                });

    client.SetPersonal(contact, new PersonalInformation
                {
                    FirstName = "first name",
                    LastName = "last name"
                });

    client.Submit();
}

Where SetEmailUser looks like:

public static SetFacetOperation<EmailUser> SetEmailUser(this IXdbContext context, IEntityReference<Contact> contact, EmailUser facet)
{
    return context.SetFacet(new FacetReference(contact, Facets.EmailUser.DefaultFacetKey), facet);
}
  • Thx for the answer i think i implemented it correct, but _contactService.GetContacts returns only other 4 facets not my facet. I added my DispatchTask code – Senad M. Aug 18 at 11:17
  • 1
    @SenadM. See the update to the answer. Also, try to browse to http://<instance-name>/sitecore/admin/showconfig.aspx and ensure that your configuration is patched correctly – Jacob Nielsen Aug 18 at 11:50
  • i made console application and tested it using your code and i get error "The type of this instance does not correspond to any type in the model" , seems that my model is not loaded from json, i created json from EmailUserModel and copied to App_Data/models and App_data/jobs/.../App_data/Models in root of xconnect. As for configuration facetmappers tag is repeated again idk if this is ok. It adds my facetmapper to list and then i get another one in web.config below like this prntscr.com/kk5tws everything else looks ok including model declaration in xconnect runtime. – Senad M. Aug 18 at 16:02
  • i've added Json for model in post, did you use my xml config on your end? – Senad M. Aug 18 at 16:08
  • 1
    Thank you for help, problem in model deploy was my understanding of "Create a configuration file named sc.Sample.CustomModel.xml" i tought that this was xml config file, but it was xml.config . Also, there is error in FacetMapper where i'm inserting wrong FacetName in constructor, and that's why my Dispatch task wasn't returning Facet . – Senad M. Aug 19 at 22:36

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.