Using Sitecore Publishing Pipeline to Refresh External CDN Cache

by Max Slabyak

This is either one of those things you may never have to use, or you come across a requirement and realize this is exactly what you need and just didn’t know what it was called.  I fell into the latter category when I needed to programmatically refresh images in my media library that were cached in my company’s content delivery syndicator – Akamai.   Thankfully, @AlexShyba pointed me in the right direction.

Goal
Clear external image/CDN cache when a media item gets published.

Solution

The PublishProcessor object is located in the Sitecore.Publishing.Pipelines.Publish namespace. We will need to inherit from this object to your own class and override the Process(PublishContext) method with our own custom functionality.

The PublishContext that’s passed in by Sitecore’s publishing processor has a list of items being published.  All we have to do is iterate through the list and use the Sitecore Item’s Path.IsMediaItem property to determine if it’s a media library item or not.  If it is, we just pass it or its full path to a custom method and use whatever API is provided by your CDN of choice to force refresh on it.


This is all being done in a separate Visual Studio project, so when we are done, we get a nice assembly .dll file out of it that we attach to the publish processor in our web.config.

The Code


using Sitecore.Data;
using Sitecore.Data.Engines;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.Publishing.Pipelines.Publish;

namespace Sitecore.Custom
{
	public class CacheClearer : PublishProcessor
	{
		private readonly List _cacheQueue = new List();

		public override void Process(PublishContext context)
		{
			Assert.ArgumentNotNull(context, "context");
			ProcessPublishedItems(context);
		}

		protected virtual void ProcessPublishedItems(PublishContext context)
		{
			if (context == null || context.PublishOptions == null || context.PublishOptions.TargetDatabase == null)
			{
				Log.Error("Context and/or publish settings are null",this);
				return;
			}

			ProcessHistoryStorage(context.PublishOptions.TargetDatabase); //this updates the housekeeping fields

			Log.Debug("There are " + _cacheQueue.Count + " items in the cache queue");
            
			var mediaUrls = new List();
			
			foreach (var id in _cacheQueue)
			{
				if (context.PublishOptions.TargetDatabase.Items.GetItem(id) != null)
				{
					var item = context.PublishOptions.TargetDatabase.Items[id];
					if (item.Paths.IsMediaItem)
						mediaUrls.Add(akamaiPath);

				}

			}

			SomeCustomCDNProcessor.RefreshMediaUrls(mediaUrls);

			Log.Info("*** Processing cache clear for item: " + id, this);
		}

		private void ProcessHistoryStorage(Database database)
        {
            _cacheQueue.Clear();

            var utcNow = DateTime.UtcNow;

            // accessing the date of last operation
            var from = LastUpdateTime(database);

            //Log.Debug("Last Update Time: " + from);
            //Log.Debug("Database: " + database.Name);
            // get the history collection for the specified dates:
            var entrys = HistoryManager.GetHistory(database, from, utcNow);
            //Log.Debug("entry count: " +entrys.Count);
            if (entrys.Count > 0)
            {
                foreach (var entry in
                    entrys.Where(entry => !_cacheQueue.Contains(entry.ItemId) && entry.Category == HistoryCategory.Item))
                {
                    _cacheQueue.Add(entry.ItemId);
                    database.Properties[LastUpdate] = DateUtil.ToIsoDate(entry.Created, true);
                }
            }


            // writing back the date flag of our last operation
            database.Properties[LastUpdate] = DateUtil.ToIsoDate(utcNow, true);
        }

        protected DateTime LastUpdateTime(Database database)
        {
            var lastUpdate = database.Properties[LastUpdate];

            if (lastUpdate.Length > 0)
            {
                return DateUtil.ParseDateTime(lastUpdate, DateTime.MinValue);
            }

            return DateTime.MinValue;
        }

 }
}

The web.config changes are as follows:


<publish help="Processors should derive from Sitecore.Publishing.Pipelines.Publish.PublishProcessor">
        <processor type="Sitecore.Publishing.Pipelines.Publish.AddLanguagesToQueue, Sitecore.Kernel" />
        <processor type="Sitecore.Publishing.Pipelines.Publish.AddItemsToQueue, Sitecore.Kernel" />
        <processor type="Sitecore.Publishing.Pipelines.Publish.ProcessQueue, Sitecore.Kernel" />
        <processor type="Sitecore.Custom.CacheClearer,CustomCacheClearer" />
 </publish>

Share

Leave a Reply

Your email address will not be published. Required fields are marked *