How to Create a Custom Publish Queue in Sitecore

  • Daniel DeLay
  • November 22, 2013

Welcome to the first entry in our new series: Specific Solutions for Uncommon Problems.  The goal of this series is to present real-world issues that came up during web design and development projects and demonstrate our solutions to them, without attempting to generalize for a larger audience.  While this raw form might not exactly match problems you are running into, perhaps it can lead to that critical piece of the puzzle you were looking for.

The Uncommon Problem

Your site regularly imports content from another datasource into a folder structure in Sitecore and this content needs to be published immediately at the end of the import process. With the possibility of hundreds of items in each import, you want to execute this in a single publish job rather than a separate publish for each imported item.  The parent folder cannot be published because it may have other content in it that is manually created and not ready to be published.

So, in summary: How do we execute a single publish operation which only contains the items that our script created/updated?

Complication: It would seem that the best path forward is to create a custom publish queue - a list of items that we keep track of and ask Sitecore to publish.  Unfortunately, the concept of a custom publish queue only exists in my imagination; it doesn't exist in Sitecore.

The Specific Solution

Publish a Trigger Item. I just conjured up that term as well but it works well for this conversation.  Here’s my definition:

The Trigger Item is a single node placed in the content tree.  When this specific Trigger Item is published, we can expect some custom code to be executed in the Publish Pipeline because it is the root of the publish.
 

In simpler terms, when our import finishes we will programmatically publish our Trigger Item.  Then a custom pipeline step will detect that the root node of this publish is our Trigger Item.  When this happens we will execute custom code that loads the list of items we just created or updated into the publish queue, giving us the result we wanted.

The Details

  1. While the import script is executing, keep track of items that need to be published in a list somewhere, like a cache.
  2. When the import finishes, execute the PublishItem method on the Trigger Item to kick things off.
  3. Implement AddItemsToPublishQueue by having it look for the Trigger Item as the publish root.  When found, retrieve the list of items to publish and add those to the Publish Queue.
  4. Patch AddItemsToPublishQueue into the Publish Pipeline.

The Raw Code

Execute the publish on the Trigger Item like this:

// get the trigger item
Item triggerItem = Factory.GetDatabase("master").GetItem(new ID(/* guid of Trigger Item */));
// publish the trigger item
PublishManager.PublishItem(triggerItem, new[] { Factory.GetDatabase("web") }, new[] { item.Language }, true, true);

Implement the AddItemsToPublishQueue class like this:

/// <summary>
/// This processor should only run when the root item is our Trigger Item. ///
/// The purpose of this processor is to publish 'only' the product items that have been added/edited during the import.
/// Publishing the parent folder could publish unintended items.
/// The added/edited items are added to a cache during the import/update.
/// </summary>
public class AddItemsToPublishQueue : PublishProcessor
{
    protected static readonly ID TriggerItemId = new ID(/* guid of Trigger Item */);

    public override void Process(PublishContext context)
    {
        Assert.ArgumentNotNull(context, "context");
        // get the root item of the publish
        Item rootItem = context.PublishOptions.RootItem;

        // check to see if the root item is our Trigger Item
        if (rootItem != null && rootItem.ID == TriggerItemId)
        {
            // get all products in in cache
            IEnumerable<PublishingCandidate> publishingCandidates = this.GetProductsFromCache(context.PublishOptions);

            // add the publishing candidates to the publish queue
            context.Queue.Add(publishingCandidates);
        }
    }

    /// <summary>
    /// Get the products that were imported/updated and which were added to a cache.
    /// </summary>
    /// <param name="options"></param>
    /// <returns></returns>
    private IEnumerable<PublishingCandidate> GetProductsFromCache(PublishOptions options)
    {
        // get products from application cache
        IList<ID> idsToPublish = PublishCacheHelper.GetIds();

        // clear the cache
        PublishCacheHelper.ClearCache();

        // if no ids to publish, return an empty list
        if (idsToPublish == null)
        {
            return new List<PublishingCandidate>();
        }

        // transform the IDs into PublishingCandidates
        return idsToPublish.Where(x => x != (ID)null).Select(x => new PublishingCandidate(x, options));
    }
}

* note: PublishCacheHelper is simply a wrapper around the Application Cache.  Feel free to write your own! Lastly, patch in the AddItemsToPublishQueue class like this:

<pipelines>
    <publish>
        <processor patch:before="*[@type='Sitecore.Publishing.Pipelines.Publish.ProcessQueue, Sitecore.Kernel']" type="Your.Project.CustomSitecore.Pipelines.AddItemsToPublishQueue, Your.Project" />
    </publish>
</pipelines>

Thanks for reading.  I’d like to give a very special thanks to the Sitecore Partner Support Team for their incredibly high level of support and responsiveness to my queries!  Well versed in the ways of Sitecore, these ninjas are, and they provide great off-the-cuff code examples to support and exemplify any approaches they suggest.