Sitecore publish at specific time

5/21/2018 sitecore-scheduling sitecore-publish sitecore-events sitecore

Publishing Sitecore items at a specific time increase performance of the Sitecore website, rather than going without of box Sitecore functionality of running an agent every x number of minutes to do publish.

Background:

Sitecore provides a functionality to auto-publish items based on the publishable from and publishable to dates set on the item.

More info on this can be found in this blog post. I would recommend reading that blog post before going forward.

However, there is a problem with this approach.

An agent which runs for every x number of minutes will clear the cache, this will be a huge impact on the website. So to solve this problem we need to run publish only when needed.

Solution:

To achieve this we are going to follow below steps:

  1. On item save, we are going to check if the item has publishing restrictions set.

  2. If the item has publishing restrictions. We create two scheduling tasks one for start date time and other for end date time.

  3. These scheduled tasks will be triggered every one minute.

  4. The scheduled tasks will trigger the publish only on specific time which was set in the publishing restrictions.

  5. Delete the scheduled task on a successful run.

Creating schedule task on item save:

For creating and implementing an item save the event, I would recommend reading this blog post

Create below methods on item event handlers:

 /// <summary>
        /// creates scheduled task item(which will publish this item) if this item is set to publish in future.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        protected void CreateScheduleTaskToPublish(object sender, EventArgs args)
        {
            var item = (Item)Event.ExtractParameter(args, 0);
            if (item != null && item.Paths.IsContentItem)
            {
                var itemName = item.ID.ToShortID();
                if (item.Publishing.ValidFrom != DateTime.MinValue && item.Publishing.ValidFrom > DateTime.UtcNow)
                {
                    CreateScheduleItem(item, string.Concat(itemName, "-", "start"));
                }
                if (item.Publishing.ValidTo != DateTime.MaxValue)
                {
                    CreateScheduleItem(item, string.Concat(itemName, "-", "end"));
                }
            }
        }

Above method creates scheduling tasks on item save event. U need to hook this method to item save event like below:

<events>
      <event name="item:saved">
        <handler type="sitecoreblog.tools4geeks.Data.Items.ItemEventHandler, sitecoreblog.tools4geeks" method="CreateScheduleTaskToPublish"/>
      </event>
</events>

Below is the method which creates the scheduled task:

        /// <summary>
        /// This creates a custom schedule task item.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="itemName"></param>
        private void CreateScheduleItem(Item item, string itemName)
        {
            using (new SecurityDisabler())
            {
                var masterDb = Sitecore.Configuration.Factory.GetDatabase("master");
                var parentItem = masterDb.GetItem(ItemIds.AutomaticPublishingFolder);

                var currentScheduleItem =
                    parentItem.Axes
                        .GetDescendants()
                        .FirstOrDefault(x => x.Name == itemName);

                if (currentScheduleItem == null)
                {
                    TemplateItem scheduleTemplate = masterDb.GetItem(Sitecore.TemplateIDs.Schedule);
                    currentScheduleItem = parentItem.Add(itemName, scheduleTemplate);
                }

                currentScheduleItem.Editing.BeginEdit();

                currentScheduleItem.Fields["Command"].Value = ItemIds.PublishWhenNeededCommand;

                if (itemName.Contains("start"))
                {
                    currentScheduleItem.Fields["Schedule"].Value = $"{item.Publishing.ValidFrom:yyyyMMddHH}|{item.Publishing.ValidFrom.AddHours(1):yyyyMMddhhmmss}|127|00:01:00";
                    currentScheduleItem.Fields["Last run"].Value = item.Publishing.ValidFrom.ToString("yyyyMMddTHHmmssZ");
                }
                else
                {
                    if (itemName.Contains("end"))
                    {
                        currentScheduleItem.Fields["Schedule"].Value = $"{item.Publishing.ValidTo:yyyyMMdd}|{item.Publishing.ValidTo.AddHours(1):yyyyMMddhhmmss}|127|00:01:00";
                        currentScheduleItem.Fields["Last run"].Value = item.Publishing.ValidTo.ToString("yyyyMMddTHHmmssZ");
                    }
                }
                currentScheduleItem.Fields["Items"].Value = item.ID.ToString();

                currentScheduleItem.Editing.AcceptChanges();
                currentScheduleItem.Editing.EndEdit();
            }
        }

 

The scheduled task I created above is normal scheduled task based on a template: /sitecore/templates/System/Tasks/Schedule.

The command i used in here is custom command i created based on template:  /sitecore/templates/System/Tasks/Command

Set the command type and method based on the class we going to create like below: Publish when needed.

In Items Field store the item that needs to be published.

Populate the schedule field based on Item Publishable from and To date.

 

Set these scheduled tasks to run every x number of minutes:

We need to run all scheduled tasks from root folder based on the set date and time. We can set this by using Sitecore scheduling agents.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <sitecore>
    <scheduling>
      <!-- An agent that processes scheduled tasks embedded as items in the master database. -->
      <agent type="Sitecore.Tasks.DatabaseAgent" method="Run" interval="00:01:00" name="Automatic_Publishing">
        <param desc="database">master</param>
        <param desc="schedule root">/sitecore/system/tasks/automatic publishing</param>
        <LogActivity>true</LogActivity>
      </agent>
    </scheduling>
  </sitecore>
</configuration>

 

So now on every 1-minute Sitecore checks if it needs to run any schedule tasks based on the time set for the tasks. when the task is ready to be run. its runs the task of running the task we need to publish the item which we stored the ID on that scheduled task. We can do this like below:

 public class PublishWhenNeeded
    {
        /// <summary>
        /// This method publish the item that are associated with schedule item.
        /// </summary>
        public void Execute(Item[] items, Sitecore.Tasks.CommandItem command, Sitecore.Tasks.ScheduleItem schedule)
        {
            Sitecore.Diagnostics.Log.Info("Started running custom schedule task", this);
            var itemsToPublish = schedule.Items;
            string currentSiteName = Context.Site.Name;
            Context.SetActiveSite("shell");
            using (new Sitecore.SecurityModel.SecurityDisabler())
            {
                if (itemsToPublish.Any())
                {
                    var publishingTargetDatabaseNames =
                        Sitecore.Publishing.PublishManager.GetPublishingTargets(
                            Sitecore.Configuration.Factory.GetDatabase("master")).Select(x => x["Target database"]).ToArray();

                    var publishingTargetLanguages = Sitecore.Configuration.Factory.GetDatabase("master").Languages;
                    Sitecore.Publishing.PublishManager.PublishItem(itemsToPublish.FirstOrDefault(),
                                                                    publishingTargetDatabaseNames.Select(Sitecore.Configuration.Factory.GetDatabase).ToArray(),
                                                                    publishingTargetLanguages,
                                                                    deep: true,
                                                                    compareRevisions: true, //Do smart publish for the item
                                                                    publishRelatedItems: true);
                    //Clear cache manually
                    Sitecore.Caching.CacheManager.ClearAllCaches();
                }
            }

            //Remove the schedule task once its ran.
            schedule.Remove();

            //switch back to current site
            Context.SetActiveSite(currentSiteName);
            Sitecore.Diagnostics.Log.Info("End of custom schedule task", this);
        }
    }

 

This will run the publish for that item and delete the scheduled task. This happens same for publishing To date scheduled task.

By this way, we can control when we can publish the item rather than trying to publish on every x number of minutes.

 

Hope this helps!

Comment below for any questions/suggestions and better ways and I will come back as soon as I can