You want to publish when?!

Publishing is a fundamental part of Sitecore and is the concept that I miss most when building websites on other platforms, but did you know that it can be set up to be automated? In the Sitecore configuration, you can find a Sitecore scheduled publishing agent, Sitecore.Tasks.PublishAgent, that is disabled by default (interval 00:00:00) and is a good starting point but lacks flexibility. My main objection is that it's all or nothing, there is no way to focus on a particular subsection of the content tree.
Having a rigorous publishing workflow in place is very important before enabling scheduled publishing as if something can go live, it will.
There are some options to help achieve the goal of flexible Schedule Publishing, most notably a Shared Source Module from Hedgehog, but in my case, I wanted to leverage as much of the default Sitecore functionality as possible. In a previous post of mine, I looked at the Scheduled task functionality as an example for using an IoC container adaptor over a Service Locator, and as this matched my search criteria, I set off to create a command that could be used to publish subsections of the content tree.
First off we have the command, there are two parts to this the template and the code. I first created a template inheriting from the existing command template and added fields that closely mimicked the Sitecore publishing dialog, and then a type that used those fields to perform a publish.
public class PublishCommand : ICommand
{
private readonly CommandConfigurationFactory<PublishConfiguration> _coinfigConfigurationFactory;
public PublishCommand(CommandConfigurationFactory<PublishConfiguration> coinfigConfigurationFactory)
{
_coinfigConfigurationFactory = coinfigConfigurationFactory;
}
public PublishCommand()
{
}
public void Execute(Item[] items, CommandItem commandItem, ScheduleItem schedule)
{
var config = _coinfigConfigurationFactory.DoGetCommandConfiguration(commandItem);
var dbSource = Sitecore.Configuration.Factory.GetDatabase(config.Source);
var dbTarget = Sitecore.Configuration.Factory.GetDatabase(config.Target);
var languages = config.LanguagesToPublish.Select(id => dbSource.GetItem(id)).ToArray();
foreach (var id in config.ItemsToPublish)
{
foreach (var language in languages)
{
var publishLanguage = dbSource.Languages.SingleOrDefault(l => l.Origin.ItemId == language.ID);
if(publishLanguage == null)
{
continue;
}
var item = dbSource.GetItem(id);
if (item == null)
{
continue;
}
var publishSubitems = config.PublishSubitems ?? false;
RunPublishJob(item, dbSource, dbTarget, config.PublishMode, publishLanguage, publishSubitems);
}
}
}
private void RunPublishJob(Item item, Database sourceDatabase, Database targetDatabase, PublishMode publishMode, Language language, bool deep)
{
var options = new PublishOptions(sourceDatabase, targetDatabase, publishMode, language, DateTime.Now)
{
Deep = deep,
PublishRelatedItems = false,
RootItem = item
};
try
{
var publisher = new Publisher(options);
publisher.Publish();
}
catch (Exception ex)
{
Sitecore.Diagnostics.Log.Error("Error publishing.", ex, this);
}
}
}
The PublishConfigurationFactory
maps the configured command item to a POCO object for use by the command type.
Next, I created a task that contains the schedule to define when to run the command. If you have the Powershell for Sitecore module installed you can use this to help define the schedule and you can even manually run it. If you don’t have Powershell for Sitecore installed go try it out.
One caveat to point out, if you are after a to the second resolution of rather than to the minute then this is not the approach for you, by default Sitecore checks to see if a schedule should run every 10 minutes. Changing the resolution of this polling is possible, but unless you have a dedicated publishing server it could introduce extra unnecessary load, and you should consider other and better options to achieve running a task to the second.
The first is exposing some endpoint and have windows scheduler make an HTTP request to it. There are a few things to consider with this approach:
How will you authenticate requests to the endpoint?
The first thing that comes to mind is some shared secret, this is quite sim
ple to implement, you store the secret on the hosting server somehow, and then the scheduled task supplies the secret along with the request. Another option you could look at is client certificate authentication, but this might be overkill.
Will this endpoint be available externally or only internally?
It’s probably not advisable to have this endpoint exposed to ‘The Wild’ as it increases the surface area available to malicious users to attack your site, and it is for this reason I don’t like this approach. If you do go down this path make sure you only deploy the endpoint to instances that are behind a firewall where you need a VPN to access, such as a Content Management environment.
Will your site be hosted in an IaaS or PaaS environment?
If your answer is IaaS, you have less to think about as the server that is hosting the website can also host the Schedule task. You have more things to juggle if PaaS was your answer, and you might need to investigate something like Azure Scheduler to achieve the same effect.
Akshay Sura has contributed a great module called SiteCron that uses Quartz.NET Scheduler under the hood, check it out.