Personalizing Sitecore pages from form data, Part 1: the old way with a custom facet
Recently, we received a requirement from the client that their marketing team can promote the marketing campaign whenever they need to, on the fly; they can create forms, and they wanted to build a personalization rule on that data and show different pages and components to different visitors. And all of this should happen without a developer and without a new release each time.
They wanted all the information the user selected from the campaign form to be stored as user preferences in the xDB contact database, so that the marketer can see details of the user and personalize the content for them.
A simple example is a booking form with a "Dine preferences" field. A visitor picks Pasta. The next time that visitor comes, the home page shows a pasta offer. The marketer did all of it alone.
This is a two-part post. In this part, we will see how this normally works with a custom facet, and why it is hard. In part 2, we will see how the Generic Personalization module (also called the codeless schema module) makes it easy, and how to install it.
When someone submits a Sitecore form, the answers can go to two places.
The first place is the Forms database. The "Save Data" action does this. Every field is saved, and we can see the entries in the Forms app. But the data belongs to the submission and is not added to the contact. Sitecore's personalization cannot use it.
The second place is the contact in xDB. This is what Sitecore remembers about a visitor. Personalization rules, segments, and Experience Profile all read from here.
Sitecore stores contact data as facets. There are out-of-the-box facets available, like personal info, email, and phone. If we want to have our custom data based on the business requirement, like a dine preference, there is no out-of-the-box facet for it. A developer has to create one.
Let's build the dine preference facet to store the data in user contact to xDB
They wanted all the information the user selected from the campaign form to be stored as user preferences in the xDB contact database, so that the marketer can see details of the user and personalize the content for them.
A simple example is a booking form with a "Dine preferences" field. A visitor picks Pasta. The next time that visitor comes, the home page shows a pasta offer. The marketer did all of it alone.
This is a two-part post. In this part, we will see how this normally works with a custom facet, and why it is hard. In part 2, we will see how the Generic Personalization module (also called the codeless schema module) makes it easy, and how to install it.
When someone submits a Sitecore form, the answers can go to two places.
The first place is the Forms database. The "Save Data" action does this. Every field is saved, and we can see the entries in the Forms app. But the data belongs to the submission and is not added to the contact. Sitecore's personalization cannot use it.
The second place is the contact in xDB. This is what Sitecore remembers about a visitor. Personalization rules, segments, and Experience Profile all read from here.
Old Way of implementing this approach, but this needs developers.
Sitecore stores contact data as facets. There are out-of-the-box facets available, like personal info, email, and phone. If we want to have our custom data based on the business requirement, like a dine preference, there is no out-of-the-box facet for it. A developer has to create one.
Let's build the dine preference facet to store the data in user contact to xDB
Facet Creation
[FacetKey(DefaultFacetKey)]
public class DinePreferenceFacet : Facet
{
public const string DefaultFacetKey = "DinePreference";
public string Cuisine { get; set; }
public string Location { get; set; }
public int Budget { get; set; }
}
After the facet is created, it needs to be registered to XConnect as a model Model builder
public static class DinePreferenceModel
{
private static readonly XdbModel model = BuildModel();
public static XdbModel Model { get { return model; } }
private static XdbModel BuildModel()
{
var builder = new XdbModelBuilder("Demo.DinePreference", new XdbModelVersion(1, 0));
builder.ReferenceModel(CollectionModel.Model);
builder.DefineFacet<Contact, DinePreferenceFacet>(DinePreferenceFacet.DefaultFacetKey);
return builder.BuildModel();
}
}Now we need to make XConnect aware of this facet.
XConnect does not read our C# code. It reads a JSON file, which we can generate from the builder. The JSON file looks something like this. Console application to generate JSON file Creation
var model = DinePreferenceModel.Model; // the model from your builder class
var json = XdbModelWriter.Serialize(model); // turns it into JSON text
File.WriteAllText(model.FullName + ".json", json); // saves "Demo.DinePreference, 1.0.json"
JSON file
{
"Name": "Demo.DinePreference",
"Version": "1.0",
"References": [
{ "Name": "XConnect", "Version": "1.0" },
{ "Name": "Sitecore.XConnect.Collection.Model", "Version": "10.4" }
],
"Types": {
"Demo.DinePreference.Model.DinePreferenceFacet": {
"Type": "Facet",
"BaseType": "Sitecore.XConnect.Facet",
"ClrType": "Demo.DinePreference.Model.DinePreferenceFacet, Demo.DinePreference.Model, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"Properties": {
"Cuisine": { "Type": "String" },
"Location": { "Type": "String" },
"Budget": { "Type": "Int32" }
}
}
},
"Facets": [
{ "Target": "Contact", "Name": "DinePreference", "Type": "Demo.DinePreference.Model.DinePreferenceFacet" }
]
}
CM Config file patch for facet<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<xconnect>
<runtime type="Sitecore.XConnect.Client.Configuration.RuntimeModelConfiguration, Sitecore.XConnect.Client.Configuration">
<schemas hint="list:AddModelConfiguration">
<schema name="dinePreferenceModel" patch:after="schema[@name='collectionmodel']" type="Sitecore.XConnect.Client.Configuration.StaticModelConfiguration, Sitecore.XConnect.Client.Configuration">
<param desc="modeltype">Demo.DinePreference.Model.DinePreferenceModel, Demo.DinePreference.Model </param>
</schema>
</schemas>
</runtime>
</xconnect>
</sitecore>
</configuration> Automation XML file
<Settings>
<Sitecore>
<XConnect>
<Services>
<XConnect.Client.Configuration>
<Options>
<Models>
<DinePreferenceModel>
<TypeName>Demo.DinePreference.Model.DinePreferenceModel, Demo.DinePreference.Model</TypeName>
</DinePreferenceModel>
</Models>
</Options>
</XConnect.Client.Configuration>
</Services>
</XConnect>
</Sitecore>
</Settings>
| File | Location | Path |
|---|---|---|
| Model JSON Demo.DinePreference, 1.0.json |
xConnect | C:\inetpub\wwwroot\sc105xconnect.dev.local\App_Data\Models |
| Index worker | C:\inetpub\wwwroot\sc105xconnect.dev.local\App_Data\jobs\continuous\IndexWorker\App_Data\Models |
|
| Processing engine | C:\inetpub\wwwroot\sc105xconnect.dev.local\App_Data\jobs\continuous\ProcessingEngine\App_Data\Models |
|
| Model DLL Demo.DinePreference.Model.dll |
||
| CM/CD | <CM site>\bin |
|
| Automation engine | C:\inetpub\wwwroot\sc105xconnect.dev.local\App_Data\jobs\continuous\AutomationEngine\Demo.DinePreference.Model.dll |
|
| Config file | CM/CD | <CM site>\App_Config\Include\Demo.DinePreference.XConnect.config |
| Automation engine | C:\inetpub\wwwroot\sc105xconnect.dev.local\App_Data\jobs\continuous\AutomationEngine\App_Data\Config\Sitecore\XConnect\sc.XConnect.Client.DinePreference.xml |
Once all the setup is ready, the developer has to create a custom submit action for submitting and storing the form fields to the contact based on the facet we just created, like below,
Submit action
int budget;
int.TryParse(Get(ctx, "Budget"), out budget);
var facet = new DinePreferenceFacet
{
Cuisine = Get(ctx, "Cuisine"),
Location = Get(ctx, "Location"),
Budget = budget
};
client.SetFacet(contact, DinePreferenceFacet.DefaultFacetKey, facet);
client.Submit();
The problem with the old way
Now think about the marketer. They want to add one more field to the form, like "Allergies". Nothing gets saved until a developer adds a new property, adds a new mapping line, rebuilds the model, creates the JSON again, deploys everything again, and restarts.
Every new field becomes a ticket and a release. That does not fit our requirements. Let's see how we can achieve this with the codeless schema module (Generic Personalization module) in part 2.
Every new field becomes a ticket and a release. That does not fit our requirements. Let's see how we can achieve this with the codeless schema module (Generic Personalization module) in part 2.
https://doc.sitecore.com/xp/en/developers/91/sitecore-experience-platform/deploy-a-custom-model.html#deploy-the-model-to-core-roles
Let's learn and grow together, happy programming 😊
Comments
Post a Comment