The greatness of Episerver is that it is extendable in almost every aspect. The way IOC is using does give you a lot of flexibility and you can write for almost every service your own implementation. In the current world a CMS system doesn’t stand on it’s own. Especially not when you are in marketing / commerce related landscape. The CMS is not the only place where content is served from. A modern website does present content from different sources like; Video platforms, DAM systems, PIM systems or even other "content networks". We do consult on a couple of integrations where we have the need to deliver from these kind of sources. Content Providers to the rescue. In this post I am going to share some challenges we have experienced down the road, especially in relation on the IdentityMappingService.
Where to start
The concept it self is far from new. A great post to start with is; Content Providers 101. And another great source is the Youtube content provider. So I advice to start from these 2 sources.
Episerver Find
Keep in mind that the CMS library of Episerver find is using the Content Provider extensively when mapping items back to IContent. This especially is the case when you make use of the powerfull; .GetContentResult().
Identity Mapping
Identity Mapping is an important concept which is used in both examples mentioned earlier. It basically does map a unique identifier from an external object to an Episerver unique integer after which you can create content references (which are more or less the Primary keys for Episerver content). It also saves a Guid (ContentGuid) with this mapping. In the database these things are stored in "tblMappedIdentity".
Performance
Sounds great though? Yes, but keep in mind that this can result in an extensible load on your database. For example every time an Episerver content data is build from your external source a db query is executed for the Identity Mapper (See f.e. the "Episerver Find Paragraph") This can become a performance bottleneck when dealing with big datasets (or resultsets) and especially when these datasets need to be refreshed regularly because your data does change fast (f.e. stock exchange rates, dynamic prices, product stocks). There are a couple of things you can keep in mind when mapping identities;
Build mappings in bulk
Where possible try to use IdentityMappingService.List
instead of IdentityMappingService.Get
. Getting mappings in bulks lower down database roundtrips tremendously.
Optimize first, caching last?
The next thing you are probably think about is to create a decent cache strategie. However in my opinion you have to think about this very close to the last stage in this case. A couple of thoughts from my side;
- Try to keep your cache as small as possible.
- There is always a request that builds the cache (so someone or something has to wait).
- The bigger the cache the more it’s going to be a repository on its own. Don’t we have epi find for superfast indexed entities?
Of course caching is great, but first make sure the execution path is as optimized as possible.
The "unmapped" alternative
Another thing you can think about is to not use the IdentityMapper at all. This (maybe out the of the box thinking) has to be proven fast on a couple of cases we have worked on lately.
When keeping these in mind;
- A guid is used as an overall unique identifier inside Episerver.
- A ContentReference consists of a; unique integer per provider, a version number, the providerkey
- There is a relation between the guid and the ContentReference
In case of using the idenity mapping the last rule is handled by the service out of the box. Look at the example I referred to in the first paragraph.
MappedIdentity mappedContent = IdentityMappingService.Service.Get(externalId, true);
The above line of code does return a ContentGuid and a ContentReference (ContentLink). IdentityMappingService.Get has 4 overloads. This allows you the get a mapped identity by a Guid, the External Id or the ContentReference. But when NOT using the mappging service you have to take care yourself on the relation between the Guid and a unique ContentReference. This for example can be done by forcing the same Guid is used foreach unique integer. While this can be done in different ways (f.e. building custom indexes, tables or taking care of this in your external datasource, etc) I like to share a simpele but really fast solution for this;
Step 1 – Create Guid to string conversion and vice versa;
public static string AsString(this Guid value)
{
var bytes = value.ToByteArray();
var stringValue = Encoding.UTF8.GetString(bytes);
var result = stringValue.Replace("\0", string.Empty);
return result;
}
public static Guid ToGuid(this string value)
{
var bytes = Encoding.UTF8.GetBytes(value);
Array.Resize(ref bytes, 16);
return new Guid(bytes);
}
Step 2 – Create a Guid based on a unique string pattern;
c.ContentLink = new ContentReference(externalId, providerKey);
c.ContentGuid = $"KEY-{externalId}".ToGuid();
note; The Guid has to be different over all providers. A unique prefiex per provider can force this in this example I use "KEY-" as the unique prefix.
Step 3 – Map from Guid to ContentReference;
protected override ContentResolveResult ResolveContent(Guid contentGuid)
{
var uniqueString = contentGuid.AsString();
var id = uniqueString.Remove(0, 4); // Remove the prefix from the identifier
var externalId = 0;
if (int.TryParse(id, out externalId) && uniqueString.StartsWith("KEY-"))
{
var type = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
var reference = new ContentReference(externalId, 0, this.ProviderKey);
return new ContentResolveResult
{
ContentLink = reference,
UniqueID = contentGuid,
ContentUri = ConstructContentUri(type.Load<YourCustomType>().ID, reference, contentGuid)
};
}
return null;
}
Verdict
This blog does give some detailed insight on how you can create a content provider with performance in mind and whats behind the Identity Mapping. The alternative of the "unmapped" provider is an example case to show the dependencies on guids and ids and how this can be part of the performance optimization. Hopefully this blog post has given you some insights to come up with other state of the art alternatives.