External Catalog – Chapter 1: The Catalog Content Provider

This is Chapter 1 of our series about implementing a "Real time catalogue" in Episerver commerce. In this post we dive deeper into the implementation of the custom CatalogContentProvider. If you like to start reading from the introduction start reading the series here


Injection Catalog Content Provider

When you have written a content provider before you are aware that you have to have to point it to a location where to ‘start’ from. For a CatalogContentProvider we have to add some code to support this way of working as well. This will give you the freedom to fill a catalog node with custom data from an external catalog by injecting nodes with a specific ProviderKey and thus let this content provider handle this content.

Implementing CatalogContentProvider

The ‘injection’ provider essentially only overwrites 3 functions to make this possible, below we will show an abstracted version of our implementation:

LoadChildrenReferencesAndTypes

This function ‘injects’ nodes below a defined catalog node that is defined in the configuration. When the defined node is being processed then the root nodes of the external service that is identified by the same catalog node identifier are ‘injected’. The identity service used in this function is responsible for generating mapped identities based on a unique external identifier (Uri) and returning it’s ContentReference.

/// <summary>Loads the children references and types.</summary>
/// The content link to the parent.
/// The language ID.
/// Output parameter which will be assigned false because catalog child listings are the same for all languages.
protected override IList LoadChildrenReferencesAndTypes(ContentReference contentLink, string languageID, out bool languageSpecific)
{
    languageSpecific = true;

    var childrenReferencesAndTypes = base.LoadChildrenReferencesAndTypes(contentLink, languageID, out languageSpecific);

    // Check if the current node is one of the nodes where we need to 'hook into'.
    if (_catalogConnectConfigurationSection.CatalogNodeIdentifiers.Contains(contentLink.ID) &amp;&amp;
        contentLink.ProviderName.StartsWith(Providers.CatalogContentProviderKey, StringComparison.InvariantCultureIgnoreCase))
    {
        var catalogConnectService = _catalogConnectServices.First(x =&gt; x.CatalogClient.CatalogNodeId == contentLink.ID);
        var categoryNodeReferences = catalogConnectService.GetCategoryNodes();
        var rootCategoryNodes = categoryNodeReferences.Where(c =&gt; !c.ParentCategoryNodeId.HasValue).Select(r =&gt;
            new GetChildrenReferenceResult
            {
                ContentLink = _catalogConnectIdentityService.CreateCategoryContentReference($"{Providers.CatalogConnectProviderPrefix}-{contentLink.ID}", r.CategoryNodeId, r.SourceId, true),
                ModelType = typeof(CatalogConnectCategory),
            }).ToList();

        foreach (var rootCategoryNode in rootCategoryNodes)
        {
            childrenReferencesAndTypes.Add(rootCategoryNode);
        }
    }

    return childrenReferencesAndTypes;
}

LoadContents

This function needs to be overwritten because when items at the top level are being loaded the wrong content provider get’s assigned. This is prevented by checking if the ProviderName of the ContentReference is the same as the current ProviderKey of the CatalogContentProvider. If so, the base implementation gets called. If not the correct ContentProvider is retrieved from the ContentProviderManager and the data is processed by this ContentProvider.

/// <summary>
/// Loads multiple catalog content instances from their references.
/// </summary>
/// The content references.
/// The language selector.
protected override IEnumerable LoadContents(IList contentReferences, ILanguageSelector selector)
{
    foreach (var contentReference in contentReferences)
    {
        if (!string.Equals(contentReference.ProviderName, ProviderKey, StringComparison.OrdinalIgnoreCase))
        {
            var provider = _contentProviderManager.GetProvider(contentReference.ProviderName);
            yield return provider.Load(contentReference, selector);
        }
        else
        {
            yield return base.LoadContents(new List
            {
                contentReference
            }, selector).FirstOrDefault();
        }
    }
}

GetDescendentReferences

This function needs to be overwritten because of the same reason as the LoadContents function; check if the current node is of the correct provider type, if not then retrieve the correct CatalogContentProvider and let it process the data.

/// <summary>
/// Get's all the descendent references from a specific content link.
/// </summary>
/// 
/// 
public override IList GetDescendentReferences(ContentReference contentLink)
{
    if (_catalogConnectConfigurationSection.CatalogNodeIdentifiers.Contains(contentLink.ID) &amp;&amp;
        contentLink.ProviderName.StartsWith(Providers.CatalogContentProviderKey, StringComparison.InvariantCultureIgnoreCase))
    {
        var provider = _contentProviderManager.GetProvider($"{Providers.CatalogConnectProviderPrefix}-{contentLink.ID}");
        return provider.GetDescendentReferences(contentLink);
    }

    return base.GetDescendentReferences(contentLink);
}

(Catalog)ContentProvider(s) for external data

With the above implementation in place you have the freedom to add your custom CatalogContentProvider in your catalog tree. This is basically how you are used to work with a ContentProvider in a normal content tree (see Episerver world for more information). Ofcourse building such a providers comes with all kind of challenges which differs per use-case. When you like to start playing around with it, do not hesitate to contact us. We have an example repository in place where we have implement a CatalogContentProvider based on a json file. This can be a baseline to start with.

However, when starting with implementing such a provider keep in mind that the ProviderKey of each external content provider is a string compiled in the following format: {Providers.CatalogConnectProviderPrefix}-{} and thus the ContentReference handled by this content provider have this value in its ProviderName property. In this way the correct ContentProvider can be retrieved from the IOC container by using it’s named instance.

Next

Tomorrow we are going to talk about references and Identities in our next chapter.

%d bloggers like this: