Creating and running custom pipelines in Sitecore
A lot of what happens when you request a page in Sitecore is handled by pipelines. Actually, Sitecore makes extensive use of pipelines all the way from handling page requests, to uploading files and saving items through the UI.
So what is a pipeline? A pipeline is a discrete set of steps, executed in order. It is an architectural design you can employ for certain cases in your applications. If I have a series of tasks which need to be performed to accomplish a task, then a pipeline may be the way to go. A pipeline will execute the steps in order, so I could use a pipeline for my ordered tasks as well as my unordered tasks (as the order doesn't matter we don't care that it was executed in order).
So what can we use this for in the real world? Let's take validation as an example. Let's say we have a purchase order item. Before it can move through to the "ship it!" step in the workflow the order must pass validation. That validation usually contains many discrete steps such as billable amount is greater than 0, date is in the past, GST or other taxes have been calculated properly, etc. Each of these discrete steps can be handled in isolation by a separate method or class.
Sitecore uses a pipeline to generate and populate the Sitecore.Context class. This class contains information about the current user, the current site, the current item being requested, etc. Each step in the httpRequestBegin pipeline populates just a single item in the context. The Sitecore pipelines also allow passing of an object through each step. This object can be manipulated by any of the steps and passed through to the next step until the end is reached and the object can be used by whatever executed the pipeline in the first place.
I get many benefits from using a pipeline architecture, the most relevant of which are:
- Testability: Each step in the pipeline is usually very small and performs only a single task. This makes it much easier to test each smaller piece and pinpoint bugs rather than testing a big composite piece which might perform many steps in a single method and require more debugging to work through the bugs.
- Recomposition: Pipelines are generally configurable. The pipeline architecture employed by Sitecore allows us to define the pipeline in web.config. The pipeline definition includes what each step is, and the order of those steps. We could also reuse those steps in another pipeline.
So let's create our own custom pipeline using the Sitecore pipeline architecture. This example pipeline is going to check that an item is valid and passes a set of validation rules implemented in code. (This is just a demo. Please refer to Alexey Rusakov's awesome validation rules blog posts for how to properly do validation in Sitecore 6). All pipelines in Sitecore require the method being invoked to accept a subclass of Sitecore.Pipelines.PipelineArgs. So we will start by defining our own subclass of the PipelineArgs class that will hold relevant information for our pipeline and allow passing of that data from step to step.
using Sitecore.Data.Items;
using Sitecore.Pipelines;
namespace CustomPipeline
{
public class CustomPipelineArgs : PipelineArgs
{
private bool m_valid = false;
private string m_message = string.Empty;
private Item m_item = null;
public bool Valid
{
get { return m_valid; }
set { m_valid = value; }
}
public string Message
{
get { return m_message; }
set { m_message = value; }
}
public Item Item
{
get { return m_item; }
set { m_item = value; }
}
public CustomPipelineArgs(Item item)
{
m_item = item;
}
}
}
Each step in the pipeline is called a "processor". We need to make sure that each processor can accept the args we defined above. The easiest way to do this is to either use an base class or an interface. I'll go with an interface here because a base class would not be providing any functionality.
namespace CustomPipeline
{
interface ICustomPipelineProcessor
{
void Process(CustomPipelineArgs args);
}
}
This interface locks each processor into defining the "Process" method which will be called from the pipeline. Next we define each processor class and fill in the Process method to perform it's specific function. In this example I'll provide 2 processors.
namespace CustomPipeline
{
public class DateSet : ICustomPipelineProcessor
{
public void Process(CustomPipelineArgs args)
{
if (args.Item["date"] == string.Empty)
{
args.Valid = false;
args.Message = "Date has not been set";
}
}
}
public class TextSet : ICustomPipelineProcessor
{
public void Process(CustomPipelineArgs args)
{
if (args.Item["text"] == string.Empty)
{
args.Valid = false;
args.Message = "Text has not been set";
}
}
}
}
The DateSet class checks to make sure the date field of the item is not empty and the TextSet class checks to make sure the text field isn't empty.
Now that we have all the pieces of our pipeline defined we need to create the pipeline in web.config. Open up web.config and find the "pipelines" element. Add a new element before the closing tag containing the XML below.
<CustomPipeline>
<processor type="CustomPipeline.DateSet, CustomPipeline" />
<processor type="CustomPipeline.TextSet, CustomPipeline"/>
</CustomPipeline>
We don't need to define the Process method in the above processor elements as that is the name of the default method the pipeline processor will try to call. We can of course override that method by providing the method to call in the processor tag.
Now all we have to do is invoke the pipeline from somewhere in our code to run the validation over a given item. We need to create the CustomPipelineArgs object, populate it, then call into Sitecore to run our pipeline and pass in the arguments.
CustomPipelineArgs pipelineArgs = new CustomPipelineArgs(item);
CorePipeline.Run("CustomPipeline", pipelineArgs);
if (!pipelineArgs.Valid && !string.IsNullOrEmpty(pipelineArgs.Message))
{
// Execute code here to deal with failed validation
}
So with just a few lines of code and configuration we can start to reap the benefits of a pipeline architecture for performing our applications processes, leveraging the Sitecore pipeline processor.
Much needed post. Maybe you could tackle UI (Client) pipelines next? :-)