External Catalog – Chapter 2: References, Relations and Identities

This is Chapter 2 of our series about implementing a "Real time catalogue" in Episerver commerce. In this post we dive deeper into the ReferenceConverter. If you like to start reading from the start, please go here and the previous chapter here.

As explained in the introduction of this series implementing a custom Catalog is not only overriding / replacing the CatalogContentProvider. In this chapter we are dive deeper into the the realted classes which do need some attention.

Implementing ReferenceConverter

In the ReferenceConverter we have overwritten the functions string GetCode(ContentReference contentLink) and ContentReference GetContentLink(string code). To map the primary key of the CatalogContentProvider (the property Code) through an Episerver primary key which is always a ContentReference (see the introduction chapter of this series). We have introduced a practice which we call the IdentityService that connects all data together. More on the IdentityService in the next paragraph.


Another important function that has been overwritten is CatalogContentType GetContentType(ContentReference contentLink) This function returns the CatalogContentType of each content reference. The original implementation of the ReferenceConverter determines the type by bit shifting the identifier. In our implementation we return CatalogNode when the content item that is being processed is a ‘category’. By changing this implementation the RelationRepository behaves different. Below an example of GetContentType

/// <summary>
/// Gets the type of the commerce object. Parse the content ID to take two most significant bits to
/// determine CatalogContentType.
/// </summary>
/// The content link.
/// The type of commerce object.
/// <code />
public override CatalogContentType GetContentType(ContentReference contentLink)
    if (contentLink.ProviderName.StartsWith(Providers.CatalogConnectProviderPrefix,
        var identity = _identityMappingService.Service.Get(contentLink);
        var metaData = _catalogConnectIdentityService.Service.ParseExternalIdentifier(identity.ExternalIdentifier);
        var type = metaData[Keys.Type];

        switch (type)
            case Nodes.Root:
                return CatalogContentType.Root;
            case Nodes.Catalog:
                return CatalogContentType.Catalog;
            case Nodes.Category:
                return CatalogContentType.CatalogNode; // Return a catalog node here, in the original ReferenceConverter this returns CatalogEntry.
            case Nodes.Product:
                return CatalogContentType.CatalogEntry;
            case Nodes.Variant:
                return CatalogContentType.CatalogEntry;
                return base.GetContentType(contentLink);

    return base.GetContentType(contentLink);

Implementing IdentityService

As described multiple times during this series the IIdentityService is responsible of knitting everything together. Essentially this service is responsible for generating ContentReferences, and it’s external identifiers in the table tblMappedIdentity in the database. This is done through the MappedIdentity and IdentityMappingService class. Both classes are EpiServer implementations.

The idea behind this service is to store all information we need about categories, products and variants and the relation between each other in the external identifier for use later on. We have defined 3 different patterns for external keys to use for each type so we can parse data from the external key when needed. Below are the 3 external identifiers we have defined for the use-case, but of course you can define your own for your own use-case:

public static readonly string VariantPattern = $"epi.cms.identity://{Providers.CatalogConnectProviderPrefix.ToLower()}-(.*)/variant/(.*)/(.*)/product/(.*)/(.*)";

public static readonly string ProductPattern = $"epi.cms.identity://{Providers.CatalogConnectProviderPrefix.ToLower()}-(.*)/product/(.*)/(.*)";

public static readonly string CategoryPattern = $"epi.cms.identity://{Providers.CatalogConnectProviderPrefix.ToLower()}-(.*)/category/(.*)/(.*)";

The variant pattern stores the items (from left to right):

  1. Catalog node identifier; this is part of the ProviderKey of the content provider and is also the named instance of the external ContentProviders and the services used for interacting with the web API endpoints. This makes it easy for getting the correct services that talk to different endpoints via the ContentReference.
  2. The identifier of the variant in the API endpoint.
  3. The identifier of the variant in the PIM, this is also the value of the Code property on the variant. And makes it possible to make a mapping from the code property to the identifier in the API endpoint.
  4. The identifier of the parent product in the API endpoint.
  5. The identifier of the parent product in the PIM, this is also the value of the Code property on the product.

Each other pattern contains the same data only in a slimmed down form. The product pattern contains only information about the product, in the same order, as is for the category pattern.

namespace Marvelous.CatalogConnect.Services
    public interface ICatalogConnectIdentityService
        bool TryGetVariantContentProviderName(string code, out string providerKey);
        bool TryGetVariantContentReference(string code, out ContentReference contentReference);
        bool TryParseCatalogNodeIdFromProviderName(string providerKey, out int catalogId);
        CatalogConnectMapping this[string variantCode] { get; }
        ContentReference CreateCategoryContentReference(string providerKey, Guid categoryNodeId, string sourceId, bool createIfMissing = false);
        ContentReference CreateProductContentReference(string providerKey, Product productItem, bool createIfMissing = false);
        IEnumerable CreateProductContentReferences(string providerKey, IEnumerable productItems, bool createIfMissing = false);
        IEnumerable CreateVariantContentReferences(string providerKey, Guid productId, string productSourceId, IEnumerable articles, bool createIfMissing = false);
        IEnumerable GetMappedIdentities(IEnumerable externalIdentifiers, bool createMissingMappings = false);
        int ParseCatalogNodeIdFromProviderName(string providerKey);
        NameValueCollection ParseExternalIdentifier(Uri externalIdentifier);
        string CreateSHA1(string input);
        Uri ConstructExternalCategoryIdentifier(string providerKey, Guid categoryNodeId, string sourceId);
        Uri ConstructExternalProductIdentifier(string providerKey, Guid productId, string sourceId);
        Uri ConstructExternalVariantIdentifier(string providerKey, Guid productId, string productSourceId, Guid articleId, string articleSourceId);

In al the samples above we have removed our caching implementation, be aware that you will need to implement this yourself.

We also have written a blog post about Unmapped? ContentProviders this is not the kind of implementation we need here because we need to store more data than is available in a Guid. A Guid can store 22 characters of data in a form of a string, and this can be converted back to a Guid. If your external identifier contains <= 22 characters than you can probably use the implementation in the blog post and you don’t need to store your data in the tblMappedIdentity table. Omitting a lot of overhead and database transactions, and thus boosting performance. If you’re interested please read it.


Tomorrow we are going to talk about relations, pricing and inventories, in our next chapter.

%d bloggers like this: