Sitecore conventions – build your own pipelines

This post is a follow up the post on using workflow I wrote a little while ago. You can read it here.

When you look deeper under the hood you’ll see that Sitecore does quite a lot of things via pipelines. A brief scan of the web.config will show a wide selection ranging from logging in, begin request, rendering fields and so on. These pipelines make it very simple to define a process and to support customisation and patching of that process. As a consequence its something that can be quite useful to include in our own code when we need to perform our own custom steps.

In the previous article, I wrote about how I was talking with another developer about sending newsletters from Sitecore. The original third party had done this as a publish step and checkbox. So the editor would check the box, publish and then the custom code would uncheck the box, save the item and then send the email. However I discussed how there were a number of issues with this approach. In this article, we’ll build on this and see how using pipelines can help.

Assuming we now have a workflow in place, we have a few options regarding how we send the email. As this is potentially a long running process that integrates with an external system, we don’t want to affect the publish process in Sitecore. By using workflow we have already removed the need for handling the checkbox on publish and ideally we should remove the need to send the email in our custom publish code as well. Here, I recommended we had a custom action which initiated a custom pipeline.

By having a custom action in our workflow, we can re-use this across other workflows. By having a pipeline, we can push the responsibility of creating and sending the email to a configurable process. An example pipeline would look like:

  • Validate item – check that the item can be turned into a newsletter and its data is ok
  • Build email message – dependant on your bulk email service – this might be a mailmessage instance or it might be a library object
  • Collect recipients – again dependant on your bulk email service
  • Send email
  • Send notification – optional. Sends internal email to the editing team that the newsletter has been sent successfully or has failed.

Obviously the above is a little contrived because it does depend on how the recipient list is maintained and the requirements of the bulk email service. However, it gives an idea of the kind of items you could have.

Implementing a custom pipeline

The steps required to create a custom pipeline are very straightforward. We need to create our own pipeline configuration, a class for each step and an arguments class that is passed between each step. The configuration file should go into a patch file in the App_Config/Include folder so that we keep our web.config as clean as possible. Using our example, the configuration could look like:

<SendNewsletter>
   <processor type="CustomEmailPipeline.ValidateItem, Newsletters" />
   <processor type="CustomEmailPipeline.BuildMessage, Newsletters" />
   <processor type="CustomEmailPipeline.AllocateRecipients, Newsletters" />
   <processor type="CustomEmailPipeline.SendEmail, Newsletters" />
   <processor type="CustomEmailPipeline.SendNotifications, Newsletters" />
</SendNewsletter>

I’ve assumed that the code is in an assembly called SendNewsLEtter and all our classes are in the namespace ‘CustomEmailPipeline’. Our argument class that gets passed between each step must subclass PipelineArgs and it will contain whatever properties we need. A brief example could look like the following:

public class NewsLetterPipelineArgs : PipelineArgs
{
   public bool Valid {get; set;}

   public String HtmlBody {get; set;}

   public String TextBody {get; set;}

   public String Subject {get; set;}

   public Item NewsLetterItem {get; set;}

   public CustomPipelineArgs(Item item)
   {
      NewsLetterItem = item;
   }
}

Its worth remembering that the same instance gets passed to each step so generally you’ll have some properties that get used for some steps only. Its up to you how to handle this and you could just ignore that or add child objects or something else entirely.

The actual body of each step depends on how you will be sending an email. For example, if the recipient list is held with a third party then you may only need to allocate a recipient list identifier to the pipeline arguments and pass that in the final send email step.

To complete the circle, lets look at a sample pipeline step for sending an email via a fictional third party webservice. Some parts of the code have been omitted for brevity but this does provide a useful starting point:

public class SendEmail
{
   public void Process(NewsLetterPipelineArgs args)
   {
      // DI here may require service location to work
      FictionalEmailService service = new FictionalEmailService();

      bool result = service.SendEmail(
         args.RecipientListId,
         args.Subject,
         args.HtmlBody,
         args.TextBody
      );

      if(!result)
      {
         // Log here. May need to wrap Sitecore.Diagnostics.Log if you want unit tests
      }

      // We don't abort the pipeline yet, other steps may want
      // to handle the failure to send emails. Such as the notification
      // step.
      args.EmailSentSuccessfully = result;
   }
}

Obviously a lot of detail has been omitted but hopefully the purpose of this task is clear. You can see that it is quite focused in that it does one job only. To send an email and record the result. If we use dependency injection, we could inject a fake email service and test that this individual step works as expected with different argument values. This ties in neatly with the post on unit testing I wrote previously. We can easily write very focused tests for each pipeline step provided we write them in a way that lets us inject mocks or similar.

The final piece in the puzzle is to run the pipeline. In our example, this would be done via a workflow action but this can just as easily be done in other ways. The code would look something like:

NewsLetterPipelineArgs args = new NewsLetterPipelineArgs(item);
CorePipeline.Run("SendNewsletter", args);
if (!args.Valid)
{
  // Execute code here to deal with failed validation. Valid is a custom property of our args object!
}

As the comment states, the Valid property is custom. There are other ways of handling errors and aborting pipelines so I encourage you to investigate this for yourself :).

Summary

Breaking down a complex process into a sequence of steps and describing them as units of work is a useful tool to have in designing software regardless of whether you use pipelines or not. However, if you need configurable steps or know that you can re-use some steps across similar ‘jobs’ then having a custom pipeline makes a lot of sense. As I highlighted in the code, you might need to add wrappers to some of the Sitecore classes if you are using them. Sitecore.Diagnostics.Log is one. That said, if you are using dependency injection then you are probably doing this kind of thing already.

Advertisements

3 thoughts on “Sitecore conventions – build your own pipelines

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s