Sitecore for Developers
List Manager: Segmented List From Custom Rules Using Custom Facet Fields
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
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
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
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
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.