List Manager: Segmented List From Custom Rules Using Custom Facet Fields

Image

Versions

Sitecore 8 Update 2

EXM 3 rev 150223

Tasks

Create a custom facet with fields on a contact

Segment a List Manager Segmented List with custom rules using the custom facet fields

DetailsIn Sitecore, using the List Manager, you can create Segmented Lists segmented with rules.  These segmented lists can be used with EXM (formerly known as ECM) to send email messages.Sitecore List Manager Segmented List builder with rules

SegmentedListRules

We'll start by creating a custom facet with fields with a custom program to import a recipient list and create or update contacts for the list as described here.Create custom facet field

Adam Conn describes how to add the custom contact facet and fields

http://www.sitecore.net/learn/blogs/technical-blogs/getting-to-know-sitecore/posts/2014/09/introducing-contact-facets.aspx

Here's a code snippet to add the facet with fields we'll use for segmentation rules

[sourcecode language="text"]// Custom Facet fields: contactType, salutation, agevar eXMContactFacetNew = newContact.GetFacet<IEXMContact>(EXMContactConstants.FACET_NAME);eXMContactFacetNew.ContactType = "Employer";eXMContactFacetNew.Salutation = "Ms.";eXMContactFacetNew.Age = 22;[/sourcecode]

Facet Type

[sourcecode language="text"] public interface IEXMContact : IFacet { string ContactType { get; set; } string Salutation { get; set; } int Age { get; set; } }[/sourcecode]

Facet in Mongo Db Contact

FacetInMongo

To use the custom facet fields in a rule action, we need to index the fields.  Here is the configuration and computed index code to do that:

/App_config/include/EXMCustomContactData.config

[sourcecode language="text"]<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <model> <elements> <element interface="LaunchSitecore.Models.EXM.IEXMContact, LaunchSitecore" implementation="LaunchSitecore.Models.EXM.EXMContact, LaunchSitecore" /> </elements> <entities> <contact> <facets> <facet name="EXMContact" contract="LaunchSitecore.Models.EXM.IEXMContact, LaunchSitecore" /> </facets> </contact> </entities> </model> <!--Custom index field definition--> <contentSearch> <configuration type="Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch"> <indexes hint="list:AddIndex"> <index id="sitecore_analytics_index" type="Sitecore.ContentSearch.LuceneProvider.LuceneIndex, Sitecore.ContentSearch.LuceneProvider"> <param desc="name">$(id)</param> <param desc="folder">$(id)</param> <param desc="propertyStore" ref="contentSearch/indexConfigurations/databasePropertyStore" param1="$(id)" /> <configuration ref="contentSearch/indexConfigurations/defaultLuceneIndexConfiguration"> <fieldMap ref="contentSearch/indexConfigurations/defaultLuceneIndexConfiguration/fieldMap"> <fieldNames hint="raw:AddFieldByFieldName"> <field fieldName="contact.EXMContact.Age" type="System.Int32" storageType="YES" indexType="TOKENIZED" vectorType="WITH_POSITIONS_OFFSETS" boost="1f" emptyString="_EMPTY_" nullValue="_NULL_" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" /> <field fieldName="contact.EXMContact.ContactType" type="System.String" storageType="YES" indexType="TOKENIZED" vectorType="WITH_POSITIONS_OFFSETS" boost="1f" emptyString="_EMPTY_" nullValue="_NULL_" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" /> <field fieldName="contact.EXMContact.Salutation" type="System.String" storageType="YES" indexType="TOKENIZED" vectorType="WITH_POSITIONS_OFFSETS" boost="1f" emptyString="_EMPTY_" nullValue="_NULL_" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" /> </fieldNames> </fieldMap> <fields hint="raw:AddComputedIndexField"> <field fieldName="contact.EXMContact.Age" type="LaunchSitecore.Models.EXM.Indexing.EXMContact_Fields_Indexing_Age, LaunchSitecore" matchField="type" matchValue="contact"/> <field fieldName="contact.EXMContact.ContactType" type="LaunchSitecore.Models.EXM.Indexing.EXMContact_Fields_Indexing_ContactType, LaunchSitecore" matchField="type" matchValue="contact"/> <field fieldName="contact.EXMContact.Salutation" type="LaunchSitecore.Models.EXM.Indexing.EXMContact_Fields_Indexing_Salutation, LaunchSitecore" matchField="type" matchValue="contact"/> </fields> </configuration> </index> </indexes> </configuration> </contentSearch> <!--End of custom index field definition--> </sitecore></configuration>[/sourcecode]

This includes an example of an integer ("Age") and strings ("ContactType,"Salutation").

The computed index code example from "Age" with "ContactType" and "Salutation" similar:

[sourcecode language="text"]public class EXMContact_Fields_Indexing_Age : IComputedIndexField { public object ComputeFieldValue(IIndexable indexable) { var contactIndexable = indexable as ContactIndexable; if (contactIndexable != null) { ContactRepositoryBase contactRepositoryBase = Factory.CreateObject("contactRepository", true) as ContactRepositoryBase; if (contactRepositoryBase != null) { var contact = contactRepositoryBase.LoadContactReadOnly((Guid)contactIndexable.Id.Value); var contactData = contact.GetFacet<IEXMContact>(EXMContactConstants.FACET_NAME); return contactData.Age; } } return null; } public string FieldName { get; set; } public string ReturnType { get; set; } }[/sourcecode]The segmented rule is added to /sitecore/system/Settings/Rules/Definitions/Elements/Segment Builder

SegmentRule

Age

Text

where the contact age [operatorid,Operator,,compares to] [Age,PositiveInteger,defaultValue=&validationText=Please enter a valid age.,age]

Type

LaunchSitecore.Extensions.EXM.Rules.ContactAgeCondition, LaunchSitecore

(your type)

Contact Type

Text

where the contact type [operatorid,StringOperator,,compares to] [value,,,specific contacttype]

Type

 Here's the code for the rules demonstrating the integer Age and string Contact Type[sourcecode language="text"] public class ContactAgeCondition<T> : TypedQueryableOperatorCondition<T, IndexedContact> where T : VisitorRuleContext<IndexedContact> { public ContactAgeCondition() { this.Age = Int32.MinValue; } protected override Expression<Func<IndexedContact, bool>> GetResultPredicate(T ruleContext) { if (this.Age != Int32.MinValue) { // This is a GEM of a snippet to get the integer value return base.GetCompareExpression<int>(c => (int)c[(ObjectIndexerKey)"contact.exmpbscontact.age"], this.Age); } return c => false; } //Properties public int Age { get; set; } } public class ContactTypeCondition<T> : TypedQueryableStringOperatorCondition<T, IndexedContact> where T: VisitorRuleContext<IndexedContact> { protected override Expression<Func<IndexedContact, bool>> GetResultPredicate(T ruleContext) { return base.GetCompareExpression(c => c["contact.exmpbscontact.contacttype"], base.Value); } }[/sourcecode]And that's all you need to do to use the new rules to segment a list by custom facet fields in a contact.