As a new Umbraco user, one of the first things I needed to set up on my new site was a scheduled job. According to the documentation you do so by specifying a url to the task in umbracoSettings.config. But there is an alternative if you dont like to trigger them by url’s.

The reason I started looking for alternatives, is because my site uses Windows autentication. It seems the builtin scheduler for timed publishes fails in this environment due to how the authentication headers are passed to the api’s. But that is another matter.

I started to look at the Umbraco code for how the scheduled publishing is implemented, and found the Umbraco.Web.Scheduling namespace. You can browse the source here.

It seems the ScheduledPublishing class is derived from a base class called RecurringTaskBase. RecurringTaskBase has the following description:

“Provides a base class for recurring background tasks.”

Now that sounds promising, so let us try to build a new scheduled task mimicking the behaviour of ScheduledPublishing.

The task

For this example, we will be creating a simple task, writing to the log every minute to tell the world it is alive. We will call it the PingTask. To execute the task at the specified intervals, we need a BackgroundTaskRunner, so we will create this as well.

Step one

Im assuming you have started a brand new asp.net project and installed Umbraco using nuget.

Lets start by creating the class we need to perform the job itself, writing a message to the log. We first create a new class called PingTask, deriving from RecurringTaskBase. Since we do not need any initialization for ourselves, we leave the constructor blank and pass the variables to the base.

public class PingTask : RecurringTaskBase
{
    public PingTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds)
        : base(runner, delayMilliseconds, periodMilliseconds)
    {
    }
}

Looking at the definition of ScheduledPublishing, we can also see they are overriding a couple of methods. Specifically IsAsync and RunsOnShutdown. Running async sounds good for a background task, and not running when an application is shutting down seems reasonable, so lets implement them as well.

public override bool IsAsync
{
    get { return true; }
}

public override bool RunsOnShutdown
{
    get { return false; }
}

With those out of the way, only one more method is needed, the one to actually perform the task we set out to do. RecurringTaskBase have two methods for “running”. PerformRun() and PerformRunAsync(). Since we have told the the taskbase we want to run asynchronously, we will be overriding PerformRunAsync().

public override async Task<bool> PerformRunAsync(CancellationToken token)
{
    using (ApplicationContext.Current.ProfilingLogger.DebugDuration<PingTask>("PingTask is starting its job", "PingTask has finished"))
    {
        try
        {
            LogHelper.Info<PingTask>("*******   PING   *******");
        }
        catch (Exception e)
        {
            LogHelper.Error<PingTask>("PingTask failed", e);
        }
    }

    return true; // repeat
}

And thats all the plumbing done. We now have a task who knows how to write a ping to the log. Fantastic!

Step two – the BackgroundTaskRunner

Now we need to tell Umbraco how often we want our task to be run. The Umbraco core has a class called scheduler. This class creates 4 background task runners. keepAliveRunner, publishRunner, taskRunner and scrubberRunner. I’m not sure what the keepAliveRunner keeps alive, but the publishRunner is responsible for scheduled publishing of your content. The taskRunner executes the scheduled tasks url’s you have setup in umbraco.config and the scrubberRunner cleans the logs.

At first I was looking for a way to add my new PingTask to one of the existing runners. However, since the scheduler class is sealed and the runners private, I found no way of doing this. Instead I ended up creating my own background task runner and add my task to it.

Since we need to wire up the runner each time Umbraco starts, we create a new class implementing the Umbraco.Core.IApplicationEventHandler interface. This gives us an entrypoint to attach code when Umbraco has started, same as the built in scheduler does.

public class StartupHandler : IApplicationEventHandler
{
    private BackgroundTaskRunner<IBackgroundTask> _tasksRunner;

    public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
        _tasksRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPings", applicationContext.ProfilingLogger.Logger);
        LogHelper.Info<PingTask>("ApplicationStarting (PingTask): Setting schedule ");
        var pingSchedule = new PingTask(_tasksRunner, 60000, 60000);
        _tasksRunner.Add(pingSchedule);
    }
}

That is all the wiring we need to do. In short we create a background task runner, create an instance of our PingTask and add it to the runner. We tell the task to run every minute (60000ms intervals).

note: IApplicationHandler need two more methods implemented, OnApplicationInitialized and OnApplicationStarting. I left them out for brevity.

Time to ping

Time to hit F5 and see the results. When your site has started, open your file explorer and go to the App_Data/logs folder of your project. This is where the log files are stored by default. Inspecting the file, we want to look for two things.

The first thing we should be looking for is the log message from our startup handler, to verify it is running and the background logger is registered. The log entry will look like this (your namespace will vary)

2017-06-26 13:32:54,172 [P21452/D2/T1] INFO ScheduleSample.Core.PingTask - ApplicationStarting (PingTask): Setting schedule

 

You can see from the logs in my example, that it started at 13:32. Since we told the task to run at one minute intervals, we should find the text “PING” written to the log every minute afterwards if our scheduler runs as it should. This is the second thing we should look for.

Here are the entries from my log.

2017-06-26 13:33:54,175 [P21452/D2/T9 ] INFO ScheduleSample.Core.PingTask - ******* PING *******
2017-06-26 13:34:54,175 [P21452/D2/T10] INFO ScheduleSample.Core.PingTask - ******* PING *******
2017-06-26 13:35:54,174 [P21452/D2/T10] INFO ScheduleSample.Core.PingTask - ******* PING *******
2017-06-26 13:36:54,188 [P21452/D2/T31] INFO ScheduleSample.Core.PingTask - ******* PING *******
2017-06-26 13:37:54,201 [P21452/D2/T29] INFO ScheduleSample.Core.PingTask - ******* PING *******

The end

I found no information in the documentation on using the scheduling namespace, so I’m not sure if they don’t want us to use it or just havent gotten around to document it yet. So use at your own risk 🙂 I tried to google alternatives to scheduling before I started digging around, and all blog posts or other references I could find, pointed to the scheduled tasks or external schedulers like HangFire.

Since I wanted to try and minimize 3. party modules and run the task without hitting a url, this was a good fit. At the time of writing, it has been running smoothly for two weeks.

If you know ways this could be done better, or if I’ve done something “not so smart” (yes it does happen), please leave a comment below 🙂

Zipfile containing both classes can be downloaded here if you want them.