At Sagepath, we offer a variety of different services to our clients - development, creative, analytics, etc.
We showcase these on our website, but we wanted to take advantage of Sitecore's personalization features to more effectively target our users. As a first step, we simply wanted to reorder the services so that the ones most relevant to the user are presented first. A developer should see Development first, an Executive should see Strategy first, and so on.
We have five services we want to showcase:
- User Experience
We also have four different types of users:
Let's create a personalization profile accordingly. We'll create profile keys for each of the services, and we'll create profile and pattern cards for each of the user types. Once we've created each, we set the appropriate weighting on the profile and pattern cards - Designers are more interested in Creative and User Experience than Strategy, for example. You can refer to the Sitecore documentation if you want more information on setting up profile keys, profile cards, or pattern cards.
Once we've set up our personalization profile, we can associate our profile cards with some content items to provide for predictive personalization. As the user moves through the site, viewing the content items, Sitecore uses the associated profile cards to build a personalized profile for the user. Again, more information on associating items with profile cards is available in the Sitecore documentation.
Now, we just need to personalize the user experience. We open the page in the Experience Editor, and click the personalize button for our Services component. And this is where we find our problem.
We don't have an option to reorder our datasource. We don't have an option to set any rendering parameters. We have only three options:
- Don't show the component at all
- Show a different component
- Use a different datasource for our component
The first option doesn't help us, obviously. The second option is probably something we could work with, but we'd have to create a new component for each of the different types of users. It's a solution, but not a very elegant one. The third option is also something we could probably make work, but would require keeping some amount of duplicate information in our content tree, which isn't ideal either.
We really just want to sort our existing datasource. As the user browses the site and a predictive profile is built up, Sitecore automatically associates them with a pattern card, and this pattern card tells us what the weighting for each Service is. So we have everything we need to order our data properly, but no way to configure it in Sitecore.
Instead of any of these three choices, we're going to modify our component to use the personalization features that are already built in to sort our data. We're going to try to do this in a simple way that we can then apply to other components, too.
So to begin, we have a Services component, which has a datasource that points to a specific item in the content tree - a "ServicesFolder". Under that item are each of the Services.
In our component view, we just iterate over the list of services and render them to the screen. Something like this:
foreach (Service service in Model.ServiceList)
<p><span>@Editable(service, x => x.ServiceHeadline)</span></p>
<h4>@Editable(service, x => x.ServiceHeadline)</h4>
@Editable(service, x => x.ServiceCopy)
@Editable(service, x => x.ServiceTasks)
Model.ServiceList is a custom property in our model. We take the datasource on the component, and translate it to a typed list so that we can iterate over it easily.
public IList<Service> ServiceList
List<Service> services = new List<Service>();
// get the datasource
SitecoreContext glassContext = new SitecoreContext();
string datasourceId = RenderingContext.Current.Rendering.DataSource;
Item dataSource = glassContext.GetItem<Item>(new Guid(datasourceId));
if (dataSource != null)
// if the datasource is pointing to a servicesfolder
if (dataSource.TemplateName == "ServicesFolder")
// add all the services
foreach (Item child in dataSource.Children)
So now we want to modify our basic implementation to reorder these services based on the weighting in the pattern card. We're going to make a generic helper class so that we can do it in a reusable fashion. Our class is going to take the type of object we want to sort and the name of the profile we're going to use for the weighting. The constructor will
- Find the profile
- Make sure the pattern is up-to-date so that the correct pattern card is identified
- Build a dictionary of weighted values.
| User Experience
We'll also create a Sort method, which will take a list of objects and the name of the property on those objects that correspond to the profile key. It will use that to compare the list to the pattern dictionary and sort it for us.
Here's the full class implementation:
public class PersonalizationHelper<T>
private readonly Profile _profile;
private readonly Item _patternCard;
private readonly Dictionary<object, float> _patternDictionary;
public PersonalizationHelper(string profileName)
// look to see if we have a profile
if (Tracker.Current == null || !Tracker.Current.Interaction.Profiles.ContainsProfile(profileName)) return;
_profile = Tracker.Current.Interaction.Profiles[profileName];
// if there has been any activity, force the pattern to update
if (_profile.Total > 0)
// look to see if there is an active pattern card
Guid? patternId = _profile.PatternId;
if (patternId == null) return;
// get the active pattern card
_patternCard = Context.Database.GetItem(new ID(patternId.Value));
// get the data for this pattern card
XmlField xmlData = _patternCard.Fields["Pattern"];
// get the keys for this pattern card
XmlDocument xmlDoc = xmlData.Xml;
XmlNodeList patternKeys = xmlDoc.GetElementsByTagName("key");
// build a dictionary of the pattern keys
_patternDictionary = new Dictionary<object, float>();
foreach (XmlNode patternKey in patternKeys)
float keyValue = 0;
if (patternKey.Attributes == null) continue;
float.TryParse(patternKey.Attributes["value"].Value, out keyValue);
public IList<T> Sort(IList<T> list, string profileKey)
if (_patternDictionary != null && _patternDictionary.Count > 0)
// reorder the list based on the weights of the pattern keys
return list.OrderByDescending(s => _patternDictionary.ContainsKey(s.GetType().GetProperty(profileKey).GetValue(s)) ?
_patternDictionary[s.GetType().GetProperty(profileKey).GetValue(s)] : 0).ToList();
Now we can very simply sort our list. Back in our model, once we've built our services list, we call our PersonalizationHelper class to sort the list for the current user. "ProfileKey" is the property on our Service class that contains the word Analytics, Strategy, Development, etc. - corresponding to the profile key. This profile key is just defaulted from the name of the content item.
// do a personalized sort for the Services profile
PersonalizationHelper<Service> ph = new PersonalizationHelper<Service>("Services");
services = (List<Service>)ph.Sort(services, "ProfileKey");
So now we have a helper that we can use to execute a personalized sort on any datasource. The items in the datasource just need a property that corresponds to the profile key to tie the two together. This is working quite well on our website. We show the services on our About page, and as you view the different projects and pages on our site, we customize the way those services are displayed to you.
You can also download the PersonalizationHelper directly if you'd like to use it in your own projects.