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) &&
contentLink.ProviderName.StartsWith(Providers.CatalogContentProviderKey, StringComparison.InvariantCultureIgnoreCase))
{
var catalogConnectService = _catalogConnectServices.First(x => x.CatalogClient.CatalogNodeId == contentLink.ID);
var categoryNodeReferences = catalogConnectService.GetCategoryNodes();
var rootCategoryNodes = categoryNodeReferences.Where(c => !c.ParentCategoryNodeId.HasValue).Select(r =>
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) &&
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.