ASP.NET Scheduled Tasks with Quartz.NET

Posted by ben 9. May 2010 03:18

There are many reasons why you may want to schedule tasks in your application, and there are a number of ways of doing it. One solution is to use a client application or windows service to execute the tasks. I’ve done this in a number of projects, writing a simple console application that is then executed using Windows Task Scheduler.

In the case of hosted ASP.NET applications, a client application is not always an option. Instead a common approach is to spin your tasks off on another thread in IIS. I’ve achieved this with a timer object in a number of web apps.

If you’ve been tracking my blog lately you will know I have spent a long time thinking about application extensibility. So whichever method I choose to use for task scheduling needs to be easy to extend and very flexible. A bit of googling revealed Quartz.NET, a port of the very popular Java job scheduling framework Quartz.

Implementing Quartz.NET was quite straightforward. The tutorial on the site is very good and in the download package there are a number of examples. I also found this presentation that covers the basics.

In Quartz.NET there are three main components, a scheduler, jobs and triggers. A scheduler is responsible for scheduling jobs and firing the triggers that execute them.

Although you can do all of the above in code, a key thing for me was that I should be able to configure jobs without having to recompile my code. Quartz.NET does actually ship with an ADO.NET Job Store (to store your scheduling data in a database) but to keep things simple I decided to manage my jobs in an xml file.

First thing you want to do is to reference the Quartz.NET and Common.Logging assemblies in your project. I’m not actually making use of the Common.Logging assembly but it needs to be there (I’m hoping I can just swap this out for Log4Net).

Next add the following to web.config:


<configSections>
  <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections><quartz>
  <add key="quartz.scheduler.instanceName" value="CommerceScheduler" />
  <!-- Configure Thread Pool -->
  <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
  <add key="quartz.threadPool.threadCount" value="10" />
  <add key="quartz.threadPool.threadPriority" value="Normal" />   <!-- Configure Job Store -->
  <add key="quartz.jobStore.misfireThreshold" value="60000" />
  <add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />   <add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.JobInitializationPlugin, Quartz" />
  <add key="quartz.plugin.xml.fileNames" value="~/quartzjobs.config" />
</quartz>

You should be able to add your quartz configuration to a separate file (quartz.config). Looking at the Quartz.NET source code, it should detect this automatically. However, in my tests, it was only loaded when I explicitly set the quartz.config parameter during the initialization of the ScheduleFactory.

Next we can add a simple job to our quartzjobs.config file:


<job>
  <job-detail>
    <name>MyJob</name>
    <group>MyJobs</group>
    <description>Logs a message to the application log</description>
    <job-type>PlanetCloud.Commerce.Extensions.MyJob, PlanetCloud.Commerce.Extensions</job-type>
    <volatile>false</volatile>
    <durable>true</durable>
    <recover>false</recover>
    <job-data-map>
      <entry>
        <key>MessageToLog</key>
        <value>Hello from MyJob</value>
      </entry>
    </job-data-map>
  </job-detail>
  <trigger>
    <cron>
      <name>MyJobTrigger</name>
      <group>MyJobs</group>
      <description>A description</description>
      <job-name>MyJob</job-name>
      <job-group>MyJobs</job-group>
      <cron-expression>0 0/1 * * * ?</cron-expression>
    </cron>
  </trigger>
</job>

The above code is fairly self describing. Inside our job element we configure the details of the job and create a cron trigger to execute it. Quartz.NET supports cron expressions which is a really powerful feature. The above cron expression creates a trigger that fires every 1 minute.

In the job-type element we specify the fully qualified class name and assembly of our job. This job simply writes a message to the application log:

public class MyJob : PluginBase, IJob
{
    #region IJob Members

    public void Execute(JobExecutionContext context)
    {
        JobDataMap data = context.MergedJobDataMap;

        string msg = data.GetString("MessageToLog") ?? string.Empty;

        _logger.Info(msg);
    }

    #endregion

    public override string Version
    {
        get { return "1.0"; }
    }
}

All jobs must implement IJob in order to be scheduled. In this example I am inheriting from my PluginBase abstract class since this gives me access to a logger.

The JobDataMap makes it easy to pass data to your jobs. If you look at the xml file you can see how to configure the job data map and then access the key value pairs from your code.

With all this done, we just need to start the scheduler. You can do this in your global.asax on application start:

IScheduler _scheduler = null;

// start up scheduler

// construct a factory
ISchedulerFactory factory = new StdSchedulerFactory();
// get a scheduler
_scheduler = factory.GetScheduler();
// start the scheduler
scheduler.Start();

After starting the web application and checking our log file after a few minutes we get the following:


2010-05-08 17:22:14,396 INFO - App has started
2010-05-08 17:23:00,034 INFO - Hello from MyJob
2010-05-08 17:24:02,091 INFO - Hello from MyJob
2010-05-08 17:24:55,267 INFO - App is shutting down

Quartz.NET is very powerful and can be used for far more advanced scheduling requirements, but hopefully this example will help you on your way.

Tags:
Categories Development | C# | ASP.NET

blog comments powered by Disqus