Azure Web Jobs: Custom Schedule for Azure Web Job Timer Triggers

CRON Expression is a nice way to define the schedule for the Timer Tigger in Azure Web jobs/Function apps. Though, sometimes it can be a little tricky.

Understanding and defining the CRON expression can be very tricky for some schedules and actual interpretation may not be the same what you had configured in the CRON expression. Though the positive side of defining the CRON expression for timer triggers is that, whenever a change is needed it’s quick and can be done via the KUDU site.

Azure Web Job SDK has the classes “DailySchedule” & “WeeklySchedule” (Nuget Microsoft.Azure.WebJobs.Extensions) which can be an alternative to the CRON expressions, which can make the schedule definition easy to read and define.

Using these classes, we can configure very tricky and complex schedules as below:

•    Every day but at different timings with no regular interval, let say every day 11:00 AM, 01:00 PM, 04:30 PM & 09:15 PM (Quite tricky)

•    On Tuesday 11:00 AM, on Thursday 12:00 PM and on Saturday 06:00 AM(Complex)

Frankly, I didn't even try creating CRON expression for above scenarios :), as being developer, it was quite easy for me to use these classes and configure above schedule in the configuration file.

Lets talk about the classes “DailySchedule” & “WeeklySchedule” now.

DailySchedule

This class is an inbuilt class for timers under the namespace “Microsoft.Azure.WebJobs.Extensions.Timers” and is inherited from “TimerSchedule” which is the base class for Timer Triggers.

Using the constructor of class “DailySchedule” we can define the daily schedule. The constructor takes in the collection of System.TimeSpan strings or collection of System.TimeSpan instances.

WeeklySchedule

Just like the “DailySchedule” class this class is also under the namespace “Microsoft.Azure.WebJobs.Extensions.Timers” and is inherited from the “TimerSchedule” class. Using the Add method defined in this class we can define the weekly schedule. Add method takes “DayOfWeek” and “TimeSpan” as the two arguments to define the schedule.

In order to make use of these classes we need to create our own classes, inherit these classes and use the methods to define the schedule. Once the custom class is defined we need to define the TimerTriggerAttribute as a typeof the custom class.

Lets take a look at the examples of Usage of these classes and make it more clear.

Example 1. DailySchedule.

 // This function will get triggered/executed on a custom schedule on daily basis.        
        public static void CustomTimerJobFunctionDaily([TimerTrigger(typeof(CustomScheduleDaily))] TimerInfo timerInfo)
        {
            Console.WriteLine("CustomTimerJobFunctionDaily ran at : " + DateTime.UtcNow);
        }
 
        //Class for custom schedule daily.
        public class CustomScheduleDaily : DailySchedule
        {
            public CustomScheduleDaily() : base("18:06:00", "18:07:00")// calling the base class constructor to supply the schedule for every day. This can be a colleaction of strings or timespan itself.
            {               
            }
        }

In the code above “CustomScheduleDaily” is a custom class inheriting predefined “DailySchedule” class. In the definition of constructor for “CustomScheduleDaily” class we are calling the base class constructor (: base("18:06:00", "18:07:00")) to define the schedule. In this example we have passed the collection of strings. We can also define TimeSpan variables and pass them to the base class constructor.

Once custom class “CustomScheduleDaily” is defined we can use the typeof(CustomScheduleDaily) and add it to the TimerTrigger of the function (CustomTimerJobFunctionDaily) which we want to run on a schedule in the web job.

In the current example the web job will run every day at "18:06:00" & "18:07:00".

Below is how it looks on the Azure Web Job Dashboard.

1

Example 2. WeeklySchedule

 //Class for custom schedule weekly
public class CustomScheduleWeekly : WeeklySchedule
{
    public CustomScheduleWeekly()
    {
        TimeSpan ts = new TimeSpan();
        string[] values = null;
        //Iterating through complete appsettings section to get the schedule for all the days and then adding the schedule to the timer trigger for each day.
        foreach (String key in ConfigurationManager.AppSettings.Keys)
        {
            if (ConfigurationManager.AppSettings[key] != null)
            {
                string val = ConfigurationManager.AppSettings[key];
                values = val.Split('|');
            }
 
            switch (key)
            {
                //Calling the Add(day, time) method of WeeklySchedule class to add the weekday and time on that weekday to run the webjob.
                case "Mon":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Monday, ts);
                    }
                    break;
                case "Tue":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Tuesday, ts);
                    }
                    break;
                case "Wed":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Wednesday, ts);
                    }
                    break;
                case "Thu":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Thursday, ts);
                    }
                    break;
                case "Fri":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Friday, ts);
                    }
                    break;
                case "Sat":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Saturday, ts);
                    }
                    break;
                case "Sun":
                    foreach (string val in values)
                    {
                        ts = DateTime.Parse(val).TimeOfDay;
                        Add(DayOfWeek.Sunday, ts);
                    }
                    break;
            }
 
        }
 
    }
}
// This function will get triggered/executed on a custom schedule on weekly basis. 
public static void CustomTimerJobFunctionWeekly([TimerTrigger(typeof(CustomScheduleWeekly))] TimerInfo timerInfo)
{
    Console.WriteLine("CustomTimerJobFunctionWeekly ran at : " + DateTime.UtcNow);
}

CustomScheduleWeekly has the implementation similar to CustomScheduleDaily.

CustomScheduleWeekly  Inherits WeeklySchedule class. So in the constructor we can add the day of week and time on that particular day when we want to run the webjob. This way we can define different timings on different days easily.

Once the schedule is defined we need to assign the typeof(CustomScheduleWeekly) to the TimerTrigger attribute of the function we want to run in the web job.

In this example the weekly schedule is from Monday to Saturday but every day the timing is different. We can add multiple TimeSpan’s for the same DayOfWeek as well so that on that particular day the job can run multiple times as per the time defined.

One limitation with this approach is we are defining the schedule in the code which means if we change the schedule we need to recompile the Azure Web Jobs every time and redeploy it to Azure.

Below is how it looks on the Azure Web Job Dashboard.

2

One way to improve this solution is to read the day and time from the app.config file in the web job code. Complete Sample has been uploaded on the GITHUB repo(TimerTriggerWebJobCustomSchedule) https://github.com/amitxagarwal/Azure-Webjobs-Samples . An example of Customer weekly class getting the schedule from app.config file is shown below.

 public CustomScheduleWeekly()
{
    TimeSpan ts = new TimeSpan();
    string[] values = null;
    //Iterating through complete appsettings section to get the schedule for all the days and then adding the schedule to the timer trigger for each day.
    foreach (String key in ConfigurationManager.AppSettings.Keys)
    {
        if (ConfigurationManager.AppSettings[key] != null)
        {
            string val = ConfigurationManager.AppSettings[key];
            values = val.Split('|');
        }
 
        switch (key)
        {
            //Calling the Add(day, time) method of WeeklySchedule class to add the weekday and time on that weekday to run the webjob.
            case "Mon":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Monday, ts);
                }
                break;
            case "Tue":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Tuesday, ts);
                }
                break;
            case "Wed":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Wednesday, ts);
                }
                break;
            case "Thu":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Thursday, ts);
                }
                break;
            case "Fri":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Friday, ts);
                }
                break;
            case "Sat":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Saturday, ts);
                }
                break;
            case "Sun":
                foreach (string val in values)
                {
                    ts = DateTime.Parse(val).TimeOfDay;
                    Add(DayOfWeek.Sunday, ts);
                }
                break;
        }
 
    }
 
}

Here is the appsettings section from app.config.

   <appSettings>
    <add key="Mon" value="08:11:20|09:24:20|09:28:20"/>    
    <add key="Tue" value="09:19:40"/>
    <add key="Wed" value="09:15:40"/>
    <add key="Thu" value="09:15:40"/>
    <add key="Fri" value="09:15:40"/>
    <add key="Sat" value="09:15:40"/>
    <add key="Sun" value="09:15:40"/>
  </appSettings>

This is just another “easy ”way of defining the schedule programmatically. However the CRON Expressions are still the CRISP way of defining the schedule, may be tricky though.