Scheduling Background Tasks in ASP.NET using Quartz.NET

3 minute read

 

Ever wondered how we can have critical tasks (not just sending out newsletters, tweeting, etc.) scheduled as background tasks our an ASP.NET website? This post outlines a quick way to use an open-source tooling to schedule background tasks in not more than 3 steps.

For scheduling background tasks, I recommend using one of the two open-source libraries – Quartz or Hangfire.

If we don't need any monitoring, are a fan of zero/minimal configuration, don't have any database or cache, etc. we can choose Quartz otherwise we can use Hangfire. In this post, we will use Quartz as a scheduling framework. To start with, we need to add reference of the NuGet package 'Quartz' to our Web Application by typing following command in Package Manager Console (or we can also use the UI in Visual Studio)

Install-Package Quartz

Now, there are 3 steps to adding a background tasks

 

Defining a Job

The beauty about Quartz is that it allows us to define the job in a separate class which ensures right abstraction and encapsulation. So let's define a MaintenanceJob that inherits the interface IJob which has one member Execute. The method defines the action to be performed when the scheduled job is triggered

using Quartz;
namespace Tasks
{
    public class MaintenanceJob : IJob
    {
       public void Execute(IJobExecutionContext context)
       {
            var ftpLocation = context.JobDetail.JobDataMap.Get("ftp.location");
            // zip old log files
            // delete the files once zip is completed
            // sftp the zip to another location
        }
    }
}

Here, the IJobExecutionContext provides us the context of the job like – JobRunTime, PreviousFireTimeUtc, NextFireTimeUtc, Trigger, JobDetail, etc. JobDetail can be set from Trigger or Scheduler as additional data passed to the scheduled jobs.

Defining a Job Trigger

 

Trigger defines when a Job should be executed. There are different types of triggers – Time based, CRON, Calendar, Schedule, etc.

We want to run this MaintenanceJob once daily (at interval of 24 hours) at midnight. So our trigger definition will look like,

        public static ITrigger TimeTrigger()
        {
            return TriggerBuilder.Create()
               .WithDailyTimeIntervalSchedule
                 (s =>
                    s.WithIntervalInHours(24)
                   .OnEveryDay()
                   .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
                 )
               .Build();
        }

 

When we use Time Interval Schedule, we have many options to schedule this job on weekdays OnMondayThroughFriday or on weekends OnSaturdayAndSunday, every minute/second, etc. If we were to define the trigger using CRON expressions, our trigger will look like,

        public static ITrigger CRONTrigger()
        {
            return TriggerBuilder.Create()
               .WithCronSchedule("0 57 23 ? * *", s=> s.WithMisfireHandlingInstructionDoNothing())
               .Build();
        }

When we use CRON based trigger, we can use CRON expressions to define interval and can also actions to handle failures, etc.  The above expression will run the job every day at 23:57 (server time)

 

Defining a Job Schedule

A scheduler acts as an entry point and definition for all Quartz jobs. So we need to derive a correlation between the job we created and the trigger we defined. This scheduler should be called in the Application Start of our ASP.NET website (Webforms or MVC). Now we can either write the below code directly in Application_Start method in Global.asax or we can structure it in a new class called JobScheduler

    public class JobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();

            IJobDetail job = JobBuilder.Create<MaintenanceJob>().Build();
            job.JobDataMap.Add("ftp.location", "ftp://SomeFileLocation");
            scheduler.ScheduleJob(job, Triggers.TimeTrigger());
        }
    }

In the above code, we are using a scheduler, defining (not creating an object) MaintenanceJob using JobBuilder and associating MaintenanceJob with the TimeTrigger trigger. Next, we need to have this JobScheduler.Start method called when the application starts (i.e. at every IIS reset)

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            JobScheduler.Start();
        }

 

If we need to run tasks on a schedule as part of our ASP.NET application with reliable accuracy and without multiple Batch Scripts or PowerShell scripts running as Scheduled Tasks, choose Quartz. The code is also available as Gist on GitHub

Updated: