Create and register a patcher

A common way to extend Avensia Storefront’s integration is with a patcher. A patcher runs as a step in a pipeline. Its main objective is to “patch” data from one object to another. We refer to the object we’re patching from as the source and the object that we’re patching to as the target.

If you’re modifying a standard Avensia Storefront pipeline the source will be a dto (data transfer object) and the target will often be an Episerver base class (or your implementation of it). Let’s consider the interface IPatcher.

public interface IPatcher<TSource, TTarget>
{
    IEnumerable<TransformedItem<TSource, TTarget>> Patch(
        ImportContext context,
        IEnumerable<TransformedItem<TSource, TTarget>> transformedItems,
        IRuntimeProfilingItem profilingItem);
}

If you look at the interface declaration you will see that it defines two generic “parameters”: TSource and TTarget. You need to specify these when you implement your own patcher. You need to use the same pattern as the pipeline that you’re trying to extend is using. We are going to extend the product pipeline. Its registration looks like this:

m.AddMultiLanguagePipelineTask<ProductDto, ProductToSave<Product, Variation>>(…);

ProductDto is the source and ProductToSave<Product, Variation> is the target.

The interface only defines one method, Patch, with three parameters. The first parameter, context, contains runtime information. The second, transformedItems, is a collection where each item holds a source object and a target object. The third, profilingItem, can be used to measure how long the execution is taking. We use that information to display an ETA so it’s good to use it. The methods return value looks the same as the second parameter. The idea is that you path each “TransformedItem<TSource, TTarget>” and pass it along to the next patcher.

Let’s start creating our own patcher. We need to start with the class declaration. Since we want to extend the product pipeline we need to follow the same pattern.

public class ExamplePatcher : IPatcher<ProductDto, ProductToSave<Product, Variation>>

Some editor auto-implement-magic will give us an implementation that looks something like this:

public IEnumerable<TransformedItem<ProductDto, ProductToSave<Product, Variation>>> Patch(
    ImportContext context,
    IEnumerable<TransformedItem<ProductDto, ProductToSave<Product, Variation>>> transformedItems,
    IRuntimeProfilingItem profilingItem)
{
    throw new NotImplementedException();
}

Notice that TSource has been replaced by ProductDto and TTarget by ProductToSave<Product, Variation>. The next step is to replace NotImplementedException by an actual implementation. The simplest implementation possible (that lets us do something with the data) looks like this:

foreach (var transformedItem in transformedItems)
{
    yield return transformedItem;
}

We want to patch something before we yield return. For this example, we’ll do something simple. We’ll add ":)" to the name of each product.

foreach (var transformedItem in transformedItems)
{
    var productDto = transformedItem.Source;
    var product = transformedItem.Target.Product;

    product.DisplayName = productDto.ProductName + ":)";

    yield return transformedItem;
}

We can improve the implementation by adding profiling. To measure the execution time we need to use profilingItem. It has a method called MonitorStep that can be used to measure how long it takes to process a single item, also called step. The same code with profiling looks like this:

return profilingItem.MonitorStep(() =>
{
    var productDto = transformedItem.Source;
    var product = transformedItem.Target.Product;

    product.DisplayName = productDto.ProductName + ":)";

    return transformedItem;
});

We are done with the patcher but we still need to register it. It won’t run unless we do. Registrations are done in Bootstrapper.cs. Pipelines are registered in RegisterModules. We need to add a row to the product pipeline registration. It should look something like this:

m.AddMultiLanguagePipelineTask<ProductDto, ProductToSave<Product, Variation>>(
    t =>
    {
        t.Name("Product");
        t.UseReader<ProductReader>();
        t.AddValidator<ProductNameValidator>();
        t.UseTransformer<ProductTransformer<ProductDto, Product, Variation>>();
        t.AddPatcher<ProductParentCategoryPatcher<ProductDto, Product, Variation, Category>>();
        t.AddPatcher<ProductNamePatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<VariationNamePatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<ProductAutoMappingPatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<VariationAutoMappingPatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<VariationDimensionPatcher<ProductDto, Product, Variation>>();
        t.UseWriter<ProductWriter<ProductDto, Product, Variation>>();
        t.AddReporter<StagingReporter<ProductDto, ProductToSave<Product, Variation>>>();
        t.AddReporter<ListingsReporter<ProductDto, ProductToSave<Product, Variation>>>();
        t.AddReporter<PublishedItemsReporter<ProductDto, ProductToSave<Product, Variation>>>();
    });

You add a patcher with t.AddPatcher<ImplementationName>(). Let’s add our patcher as the last patcher. Sine patchers run in the order they’re registered we can be sure that no other patcher will overwrite our patcher if we add it last. The line should look like this:

t.AddPatcher<ExamplePatcher>();

The full registration should look like this:

m.AddMultiLanguagePipelineTask<ProductDto, ProductToSave<Product, Variation>>(
    t =>
    {
        t.Name("Product");
        t.UseReader<ProductReader>();
        t.AddValidator<ProductNameValidator>();
        t.UseTransformer<ProductTransformer<ProductDto, Product, Variation>>();
        t.AddPatcher<ProductParentCategoryPatcher<ProductDto, Product, Variation, Category>>();
        t.AddPatcher<ProductNamePatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<VariationNamePatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<ProductAutoMappingPatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<VariationAutoMappingPatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<VariationDimensionPatcher<ProductDto, Product, Variation>>();
        t.AddPatcher<ExamplePatcher>();
        t.UseWriter<ProductWriter<ProductDto, Product, Variation>>();
        t.AddReporter<StagingReporter<ProductDto, ProductToSave<Product, Variation>>>();
        t.AddReporter<ListingsReporter<ProductDto, ProductToSave<Product, Variation>>>();
        t.AddReporter<PublishedItemsReporter<ProductDto, ProductToSave<Product, Variation>>>();
    });

You’re all done! Run the solution and test your new patcher!