LibrarySites.Banner

Investigating the Pipeline Based Item Provider in the Sitecore ASP.NET CMS

This blog post contains some notes I wrote while investigating the potential for pipeline-based item providers in the Sitecore ASP.NET web Content Management System (CMS) and Experience Platform (XP). Pipeline-based item providers have some similarity to but are not the same as Sitecore data providers.

Sitecore 8 introduces the concept of pipeline-based item providers. From my perspective, you can use the pipeline-based item provider to override the default item provider, which retrieves data from a Sitecore database.

You can see how this works in methods of the PipelineBasedItemProvider class in the Sitecore.Data.Managers namespace specified by the <add> element named pipelineBased within the <providers> element within the /configuration/sitecore/itemManager element in the /web.config file and configured as the default item manager by the defaultProvider attribute of that itemManager element. Many methods in the PipelineBasedItemProvider class invoke its ExecuteAndReturnResult() method. For example, the MoveItem() method is responsible for moving an item, whether by invoking the moveItem pipeline or the default provider.

If the Handled property of the argument passed to the pipeline invoked by the ExecuteAndReturnResults() method is true, the method returns the Result property of that argument. If none of the processors in the pipeline set the Handled property to true, then the ExecuteAndResturnResults() method invokes the method in the fallback provider corresponding to the name of the pipeline, such as MoveItem(). The fallbackProvider attribute of the <add> element named pipelineBased within the /configuration/sitecore/itemManager/providers/add element named pipelineBased specifies the fallback provider. All of the default pipeline implementations are empty, meaning that Sitecore always falls back to the default provider by default.

You can see definitions for the relevant pipelines at the end of the /configuration/sitecore/pipelines element in the /web.config file. There, a new <group> element contains a nested <pipelines> element that in turn contains the relevant pipeline definitions and a note indicating from what base class processors in each should derive.

  • addFromTemplate
  • addVersion
  • blobStreamExists
  • copyItem
  • createItem
  • deleteItem
  • getBlobStream
  • getChildren
  • getContentLanguages
  • getItem (accessing an item from a processor in this pipeline can result in an infinite loop)
  • getParent
  • getRootItem
  • getVersions
  • hasChildren
  • moveItem
  • removeBlobStream
  • removeData
  • removeVersion
  • removeVersions
  • resolvePath
  • saveItem
  • setBlobStream

There is a namespace for each pipeline, and each namespace contains an abstract base class for processors in that pipeline. For example, processors in the moveItem pipeline should implement Sitecore.Pipelines.ItemProvider.MoveItem.MoveItemProcessor. This class contains a single method named Process that accepts an argument of type Sitecore.Pipelines.ItemProvider.MoveItem.MoveItemArgs. This is class contains the Handled property mentioned earlier as well as a FallbackProvider property.

For example, to add some diagnostics around move operations, we can implement a MoveItem pipeline processor that logs the operation and then invokes the corresponding method in the fallback provider.

namespace SitecoreJohn.Pipelines.ItemProvider.MoveItem
{
  using Sitecore.Diagnostics;
  using Sitecore.Pipelines.ItemProvider.MoveItem;
 
  public class MyMoveItemProcessor : MoveItemProcessor
  {
    public override void Process(MoveItemArgs args)
    {
      Assert.ArgumentNotNull(args, "args");
      Assert.ArgumentNotNull(args.Item, "args.Item");
      Assert.ArgumentNotNull(args.Destination, "args.Destination");
      Assert.IsFalse(args.Handled, "args.Handled");
      Log.Info(
        this + " : move " + args.Item.Paths.FullPath + " to " + args.Destination.Paths.FullPath,
        this);
      args.FallbackProvider.MoveItem(
        args.Item,
        args.Destination,
        args.SecurityCheck);
      args.Handled = true;
    }
  }
}

We can use a web.config include file such as the following to enable this processor:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <group name="itemProvider" groupName="itemProvider">
        <pipelines>
          <moveItem>
            <processor type="SitecoreJohn.Pipelines.ItemProvider.MoveItem.MyMoveItemProcessor,SitecoreJohn" />
          </moveItem>
        </pipelines>
      </group>
    </pipelines>
  </sitecore>
</configuration>

And yes it appears that we can play tricks like this:

namespace SitecoreJohn.Pipelines.ItemProvider.SaveItem
{
  using System;
 
  using Sitecore;
  using Sitecore.Pipelines.ItemProvider.SaveItem;
 
  public class MySaveItemProcessor : SaveItemProcessor
  {
    public override void Process(SaveItemArgs args)
    {
      if (args != null
        && (!args.Handled)
        && args.Item != null
        && args.Item.Fields["Title"] != null
        && (!args.Item["Title"].StartsWith("Ja! "))
        && Context.User != null
        && new Random().Next() % 3 == 1
        && Context.User.LocalName.Equals("susan", StringComparison.InvariantCultureIgnoreCase))
      {
        args.Item["Title"] = "Ja! " + args.Item.Fields["Title"];
      }
    }
  }
}

If you need to interact with the user, I think that may be best to work at a higher level. For example, I would use the uiMoveItems pipeline rather than the moveItem pipeline if possible. One advantage of the moveItem pipeline in this context is that I believe that any API calls could trigger the moveItem pipeline, where only UI move operations trigger the uiMoveItems pipeline.

Resources