If you’re maintaining an e-commerce-enabled site in EPiServer and you’re using the built-in catalog, then
you have probably found the need to incorporate external data into the catalog, perhaps from various sources.
Such is the case for me on an EPiServer site I’ve been working on over the last (almost) four years. The primary
data for the catalog comes into EPiServer from an external PIM system. But, then there is all sorts of ancillary
data that needs to calculated from existing fields or pulled from external sources. These external data sources
don’t really provide a way to incrementally apply deltas for the data they provide. The only option to make updates is to walk
through the entire catalog and see if anything needs updating when matched against the external data I’ve pulled.
This external data is stored in the catalog as custom EPiServer MetaFields. So,
a need arose to easily walk through the entire catalog, pick up every MetaObject (to update the custom MetaFields)
in every available catalog culture and make the appropriate updates.
Below are the iterators I wrote to accomplish this. They let you walk the entire catalog tree (in a breadth-first manner)
starting from the top as if you were iterating a simple C# array or list.
Because all we’re doing is writing iterators, the high-level usage looks just like you were interating through
an IEnumerable in C# like an array or a List<T>.
The iterators are implemented as extension methods on the ICatalogSystem interface. So, iterating through all
of the catalog entry MetaObjects looks like this.
I implemented the separate iterators for NodeContent MetaObjects and the EntryContent
MetaObjects. You easily use them in combination to iterate through the MetaObjects of the entire catalog tree.
Note a couple of things from the example code above:
You have to give the iterator the MetaDataContext. As we’ll see in the iterator code, it will copy this MDC for each catalog language so that you get a MetaObject for every culture present in your catalog.
Because of #1 above, the current MetaDataContext is passed to you during iteration so that you know which catalog culture this MetaObject is for.
Extension Method Code
The extension method code for AllCatalogSystemEntryMetaObjects is pretty simple. It uses four iterators in nested foreach loops to loop through
All meta objects
I broke the iteration down into separate iterators in this fashion so that I could easily iterate through all the entries (for example) in a single node, if I knew from the beginning which node I wanted to start at. If that was the case, I could easily write another extension method that took a node ID (or node code) as a parameter, use the ICatalogSystem to find that node and then pass the node ID to the CatalogSystemEntries iterator to iterate just the entries under that node.
We have four iterators in use:
CatalogSystemCatalogs - iterates through all catalogs
CatalogSystemNodes - iterates through all nodes, in a breadth-first fashion, in a catalog
CatalogSystemEntries - iterates through all entries in a node
CatalogSystemEntryMetaObjects - iterates through all MetaObjects for an entry
The code is pretty simple. We implement an IEnumerable which uses our implementation of an IEnumerator.
The IEnumerator simply gets all the catalogs from the ICatalogSystem and, as you call MoveNext() (usually done for you by the foreach loop), increments an index value to give you the current CatalogDto.CatalogRow to work with.
For the nodes iterator, things get a little more complicated. Nodes can be nested, so we have tree structure we have to navigate. We use a Queue to walk through the nodes in a breadth-first fashion.
I like to use as many functional programming techniques as I can in C# these days. I use the great LanguageExt library for this. This library is where the Que<T>, Option<T> and Lst<T> data structures come from. I don’t want to go in depth about what they are, but to understand the code below, you should now this: Que<T> is an immutable Queue<T>, Lst<T> is an immutable List<T> and Option<T> is basically the functional way to do the null-object pattern (instead of dealing with nulls, use an object to represent “I don’t have a value”).
If you’re not familiar with breadth-first tree traversals, it’s not too complicated. What we do first is get all the nodes at the top level (in our case, all the nodes that are direct children of the catalog we pass in) and put them in the queue. Then, we iterate through each of the nodes we put in the queue and add their child nodes to the queue. We do this until we run out of nodes to process in the queue which indicates we’ve gone through all nodes in the tree.
Since entries cannot be arrange in a nested fashion, this iterator is a little less complicated than the nodes iterator. All we do is take in a catalogId and a catalogNodeId and retrieve all the entries below the given node ID, passing them to you one at a time as you call MoveNext().
The only mildly complex thing here is that we use a Lazy<T> so that we’re not retrieving the entries immediately when you instanstiate the IEnumerator. We only load the entries when you MoveNext() and access Current which indicates you really do want some entries.
The MetaObjects iterator is a different than the other iterators in that it doesn’t really iterate through a list or tree of MetaObjects. Instead, it iterates through all fo the MetaObject instances for a particular entry–one instance for reach culture available for the catalog.
So, as you’ll see in the code below, one of the first things we do is grab all of the languages for the catalog so that we know what we need to interate through.
Even though the above code will give you the convenience of iterating through your entire EPiServer catalog(s), be warned! You could easily forget what the IEnumerators really do above and do something like this:
I hope you see the danger of this. If you have a large catalog, you’re loading the ENTIRE set of MetaObjects into memory in your allTheMetaObjects list. This probably won’t be good for your application. If you have a small catalog, this might not be a big deal. But, keep this in mind!