scheduling made easy with Quartz

managing a scheduled job shouldn't be so hard. Quartz makes it easy in my example. maybe this will help you out when you build out your next scheduled job

February 20, 2013

I’ve created a new extremely flexible service using Quartz, through a configuration file that I wanted to share with you. In my example I’ve followed the standard Domain Driven Design principals and used the repository pattern. This service is a good tool to see log4net, Atlas, Quartz, Autofac, ClosedXML and Entity Framework using code first all in action. I will explain a few key areas of my service, but you will need to download the application to really get a feel for its full power and see it in action.

First I will point out the quartz configuration. I’ve wired up in my app.config to tell quartz to look at the quartzjobs.config file. In the quartz file I’ve setup a job to run every 30 seconds on the 30 second and 60 second tick. This differs from previous posts where I wired up the job and triggers inside of the code. Doing the quartz plumbing inside of a configuration file will allow me to change scheduling, without having to touch a line of code.

<?xml version="1.0" encoding="UTF-8"?>
<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        version="2.0" >
  <processing-directives>
    <overwrite-existing-data>true</overwrite-existing-data>
  </processing-directives>
  
  <schedule>
    <job>
      <name>CreateReportsJob</name>
      <group>MY_JOBS_GROUP</group>
      <description>creates the my service reports</description>
      <job-type>application.CreateReportsJob, application</job-type>
      <job-data-map>
        <entry>
          <key>MessageToLog</key>
          <value>Hello from MyJob</value>
        </entry>
      </job-data-map>
    </job>
    <trigger>
      <cron>
        <name>CreateReportsTrigger</name>
        <group>MY_TRIGGER_GROUP</group>
        <description>the trigger to create the my service reports</description>
        <job-name>CreateReportsJob</job-name>
        <job-group>MY_JOBS_GROUP</job-group>
        
        <!-- (1)Seconds  (2)Minutes  (3)Hours   (4)Day-of-Month   (5)Month   (6)Day-of-Week   (7)Year -->
        <!--<cron-expression>0 0 10 3 * ?</cron-expression>-->   <!-- Fires Every Month on the 3rd day at 10AM -->
        <cron-expression>0/30 * * * * ?</cron-expression>    <!-- Fires Every 30 seconds on the 30/60 second tick -->
      </cron>
    </trigger>
  </schedule>
</job-scheduling-data>

Within the app.config there is a Quartz section where I tell quartz to check the configration file every 10 seconds to see if there was an update to the quartz jobs and triggers. This will help to alleviate having to restart the service when you make a change to the quartz configuration.

<add key="quartz.plugin.xml.scanInterval" value="10" />

Now I will explain the job, CreateReportsJob.cs. This class implements the Quartz IJob which implements the execute method which does a couple things when it executes. First it will write a message out to the log telling you that its running, it pulls the message from the Quartz job-data-map in the configuration file. It will then write out to the log file and to the console all the brokers and clients. The clients differs in one key area from the broker, it has a foreign key relationship to the contact address table. Which I put in to display how a foreign key relationship can working using Entity Framework code first. Finally it uses ClosedXML to write out the brokers to a Excel file.

[DisallowConcurrentExecution]
public class CreateReportsJob : IJob
{
	public IBrokerRepository BrokerRepository { get; set; }
	public IClientRepository ClientRepository { get; set; }
	public IExcelRepository ExcelRepository { get; set; }
	public ICreateFileFactory CreateFileFactory { get; set; }
	public ILog Log { get; set; }
	
	public void Execute(IJobExecutionContext context)
	{
		var data = context.MergedJobDataMap;
		var msg = data.GetString("MessageToLog") ?? string.Empty;
		
		Console.WriteLine(DateTime.Now + " " + msg);
		Log.InfoFormat(DateTime.Now + " " + msg);
		
		WriteOutCollectionOfBrokers();
		WriteOutCollectionOfClients();
		
		CreateExcelOfBrokers();
	}
	
	private void CreateExcelOfBrokers()
	{
		var fileName = string.Format("Brokers {0} {1}.xlsx", DateTime.Now.ToShortDateString().Replace("/", ""), DateTime.Now.ToShortTimeString().Replace(":", ""));
		Log.Info("FileName:" + fileName);
		
		var brokers = BrokerRepository.Get();
		ExcelRepository.CreateBrokerExcelDocument(brokers, fileName);
	}
	
	private void WriteOutCollectionOfBrokers()
	{
		var brokers = BrokerRepository.Get();
		foreach (var broker in brokers)
		{
			Log.InfoFormat(broker.NameOf);
			Console.WriteLine(broker.NameOf);
		}
	}
	
	private void WriteOutCollectionOfClients()
	{
		var clients = ClientRepository.Get().Take(5000);
		
		foreach (var client in clients)
		{
			Log.InfoFormat("Client | Name: {0} Address 1: {1}", client.NameOf, client.ContactAddressAccount != null ? client.ContactAddressAccount.Address1 : string.Empty);
			Console.WriteLine("Client | Name: {0} Address 1: {1}", client.NameOf, client.ContactAddressAccount != null ? client.ContactAddressAccount.Address1 : string.Empty);
		}
	}
}

Those really are the key differences I wanted to point out in this new service that differ from my previous post a quick service that sends emails. I originally got the idea from a reader who was looking to have a more configurable option with quartz, rather than using application settings. I decided to explore a bit and pieced this solution together which I’m really happy with and wanted to share.

Sample Excel File Output

Sample Console Output

Notice that this console shows that it was triggered twice, once at 12:00 and once at 12:30.

Give me your thoughts and feel free to download the solution here.