This article describes how to create a simple Sitecore facet consisting of a DateTime and a list of strings.
A contact is made up of facets. Here are all the facets Sitecore uses (you will find the facets in \App_Config\Include\Sitecore.Analytics.Model.Config):
<facets> <facet name="Personal" contract="Sitecore.Analytics.Model.Entities.IContactPersonalInfo, Sitecore.Analytics.Model" /> <facet name="Addresses" contract="Sitecore.Analytics.Model.Entities.IContactAddresses, Sitecore.Analytics.Model" /> <facet name="Emails" contract="Sitecore.Analytics.Model.Entities.IContactEmailAddresses, Sitecore.Analytics.Model" /> <facet name="Phone Numbers" contract="Sitecore.Analytics.Model.Entities.IContactPhoneNumbers, Sitecore.Analytics.Model" /> <facet name="Picture" contract="Sitecore.Analytics.Model.Entities.IContactPicture, Sitecore.Analytics.Model" /> <facet name="Communication Profile" contract="Sitecore.Analytics.Model.Entities.IContactCommunicationProfile, Sitecore.Analytics.Model" /> <facet name="Preferences" contract="Sitecore.Analytics.Model.Entities.IContactPreferences, Sitecore.Analytics.Model" /> </facets>
In this example I will add a facet that consists of a date and a list of strings. I will call it “AvailablePublishers“.
This is a real-life example where I needed to store a list of publishers that were available the last time the user was online. Each publisher is just an ID (a string) and I store these as a list on the Contact:
It sounds simple, and it is – but there is a lot of code involved. So hang on, lets code.
STEP 1: THE BASIC INTERFACES
The “AvailablePublishers” is a Facet, the list below consists of Elements. So I need to create a IFacet interface and a IElement interface.
Here is the IFacet:
using System; using Sitecore.Analytics.Model.Framework; namespace PT.AvailablePublishers { public interface IAvailablePublishersFacet : IFacet { IElementCollection<IAvailablePublishersElement> AvailablePublishers { get; } DateTime Updated { get; set; } } }
The IFacet contains a list (IElementCollection) of my IElement. Here is the IElement:
using Sitecore.Analytics.Model.Framework; namespace PT.AvailablePublishers { public interface IAvailablePublishersElement : IElement, IValidatable { string PublisherID { get; set; } } }
STEP 2: THE IMPLEMENTATION
Now we need concrete classes implementing IAvailablePublishersFacet and IAvailablePublishersElement:
Here is the AvailablePublishersFacet class:
using System; using Sitecore.Analytics.Model.Framework; namespace PT.AvailablePublishers { [Serializable] public class AvailablePublishersFacet : Facet, IAvailablePublishersFacet { public static readonly string _FACET_NAME = "AvailablePublishers"; private const string _UPDATED_NAME = "LastUpdated"; public AvailablePublishersFacet() { EnsureCollection<IAvailablePublishersElement>(_FACET_NAME); } public IElementCollection<IAvailablePublishersElement> AvailablePublishers { get { return GetCollection<IAvailablePublishersElement>(_FACET_NAME); } } public DateTime Updated { get { return GetAttribute<DateTime>(_UPDATED_NAME); } set { SetAttribute(_UPDATED_NAME, value); } } } }
and the AvailablePublishersElement class:
using System; using Sitecore.Analytics.Model.Framework; namespace PT.AvailablePublishers { [Serializable] public class AvailablePublishersElement : Element, IAvailablePublishersElement { private const string _PUBLISHERID = "PublisherID"; public AvailablePublishersElement() { EnsureAttribute<string>(_PUBLISHERID); } public string PublisherID { get { return GetAttribute<string>(_PUBLISHERID); } set { SetAttribute(_PUBLISHERID, value); } } } }
Both classes are serializable.
Getting and setting properties are done using GetAttribute and SetAttribute methods retrieved from Sitecore.Analytics.Model.Framework.Element and Sitecore.Analytics.Model.Framework.Facet.
Lists are done using IElementCollection or IElementDictionary, provided by Sitecore.
STEP 3: REGISTER FACET IN SITECORE CONFIGURATION
The facets and elements are registered in Sitecore. In the configuration you also register the IAvailablePublishersFacet as an element, even when it inherits from IFacet.
This is a simplified version of the \App_Config\Include\Sitecore.Analytics.Model.config where I have removed all other elements but my own:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <model> <elements> <element interface="PT.AvailablePublishers.IAvailablePublishersElement, MyDLL" implementation="PT.AvailablePublishers.AvailablePublishersElement, MyDLL" /> <element interface="PT.AvailablePublishers.IAvailablePublishersFacet, MyDLL" implementation="PT.AvailablePublishers.AvailablePublishersFacet, MyDLL" /> </elements> <entities> <contact> <facets> <facet name="AvailablePublishers" contract="PT.AvailablePublishers.IAvailablePublishersFacet, MyDLL" /> </facets> </contact> </entities> </model> </sitecore> </configuration>
So as you can see, both my interfaces are defined in <elements/>. The <elements/> describes the implementation of the interface, just like IOC would do it.
The facet is defined in <facets/> with a unique name. This unique name is the name you use to find the facet when you need to access it.
STEP 4: CALL THE FACET
The facet is now defined. Next step is to use the facet. To retrieve a facet you need a Contact. The contact is usually retrieved from the Sitecore Tracker:
Contact contact = Tracker.Current.Session.Contact;
This is a repository than can get the list of available publishers from a contact, and update the list of available publishers:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using PT.AvailablePublishers; using Sitecore.Analytics.Tracking; namespace PT.Forum.Domain.NejTakPlus.Model.Repositories { public class AvailablePublishersRepository { public IEnumerable<string> Get(Contact contact) { IAvailablePublishersFacet facet = contact.GetFacet<IAvailablePublishersFacet>(AvailablePublishersFacet._FACET_NAME); return facet.AvailablePublishers.Select(element => element.PublisherID); } public void Set(Contact contact, IEnumerable<string> availablePublisherKeys) { IAvailablePublishersFacet facet = contact.GetFacet<IAvailablePublishersFacet>(AvailablePublishersFacet._FACET_NAME); facet.Updated = DateTime.Now; while (facet.AvailablePublishers.Count > 0) facet.AvailablePublishers.Remove(0); foreach (string availablePublisherKey in availablePublisherKeys) { facet.AvailablePublishers.Create().PublisherID = availablePublisherKey; } } } }
Notice the contact.GetFacet<>() method. Here you specify the name of the facet you defined in the <facets/> section of the config file. Luckily you also define the same name in the code, so I can get the facet name from my AvailablePublishersFacet class.
Also notice the Set() method. If you wish to clear the list before inserting, you need to iterate through the list and remove them one by one. There is no Clear() method.
Remember that facets are not written to MongoDB before your session ends. So you cannot see your fact in Mongo before your session is abandoned. You can try calling:
Session.Abandon();
To force Sitecore to write the data to MongoDB.
That’s it. Happy coding.
MORE TO READ:
- Introducing contact facets by Adam Conn
- Sitecore 8 xDB and Experience Profile Simplified by Ian Graham
Reblogged this on hi my name is tim.
LikeLike
Reblogged this on Dinesh Ram Kali..
LikeLike
Brian, do you have an entry that shows how to display this custom data on the Contact card page?
LikeLike
No I haven’t made a new Speak part for displaying my new data. Maybe in a later post.
LikeLike
Great post Brian, one question though. Does your custom data persist ok if you read back the Contact using Tracker.Current.Session.Identify? My custom data seems to be erased when I read back the Contact on the next visit.
LikeLike
Custom data are stored in MongoDB when the user session expires. So you cannot see your data in MongoVue before the session expires.
But you should be able to see the data when requesting the contact from Tracker.Current.Serssion.Identify, because the data is stored in the session.
There is a bug running around at the moment where if you store data in a session-less environment (from a stateless API or from a webservice) that the Shared Session manager does not always have a success locking the contact. When this happens, it reads old data from MongoDB rather than throwing an exception.
If you use sessions, there should be no problem.
LikeLike
Brian, this is a very useful post. Thanks very much.
Does creating our own facet depend on if we are running xDB in the cloud or on-premises?
LikeLike
Not that I am aware of
LikeLike
Pingback: Sitecore Contacts – Create and save contacts directly to and from xDB (MongoDB) | Brian Pedersen's Sitecore and .NET Blog
Pingback: Sitecore List Manager – Add Contacts to EXM Lists | Brian Pedersen's Sitecore and .NET Blog
It is unclear to me exactly how to use the IElementCollection.Create method. In your example you have a simple list of strings so you can do facet.AvailablePublishers.Create().PublisherID = availablePublisherKey;. But what if I have a list of more complex objects instead of just strings? How would I create a complex object and set a number of properties?
LikeLike
Pingback: Sitecore MongoDB – What do we know about a contact? | Brian Pedersen's Sitecore and .NET Blog
Pingback: Sitecore Contacts: Part 2 – Custom Contact Facets – Sitecore Pursuit
Pingback: Sitecore Rule – Personalize based on any field in any facet in your Contact | Brian Pedersen's Sitecore and .NET Blog
Pingback: Sitecore Object of type ‘System.Runtime.Serialization.TypeLoadExceptionHolder’ cannot be converted to type ‘Sitecore.Analytics.Model.Framework.IFacet’. | Brian Pedersen's Sitecore and .NET Blog
Pingback: View custom facets values from within Sitecore Experience Profile – Debu's blog