I had been given a business requirement to provide a self service Website for job scheduling. Having worked with Quartz.Net, it seemed the perfect backend engine to schedule and process jobs. I decided to expose the functionality of the Quartz.Net via a few web methods using Web services to allow for it to be consumed in Excel, SharePoint, a Web or a desktop app. In this blog post, I’ll show you how to expose Quartz.Net via a Web Service.

01 – Architecture
- The Scheduler Web Service (Scheduler.asmx) is hosted on a Web Server.
- I currently have 3 App Servers which are running Quartz.Net as a Windows service. The names of these app servers are ‘Alpha’, ‘Beta’ and ‘Gamma’. I have added a firewall exception for port 555 for Quartz.Net to listen on port 555.
- Tags: Interesting! Now you would ask, what are tags? Tags are basically my way of identifying which type of scheduled jobs is my quartz app server capable of running. For instance, Alpha is only equipped to schedule PowerShell specific jobs, while Beta can schedule VBA jobs and Gamma can schedule any job that requires the cmd process. You can scale this out to include .NET, Python or just different versions of a process.
- XML: The XML config you see flowing from the user to the web server is the job description, i.e., the request for scheduling Job X while specifying the schedule, priority, etc.

02 – Business Logic
Please see the business logic below for the methods you have seen in the web service signature above.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using Quartz;
using Quartz.Impl;
using Quartz.Impl.Matchers;
using Rte.Model.Entities;
namespace Scheduler.Business
{
public class Scheduler
{
public readonly IScheduler Instance;
public string Address { get; private set; }
public string JobName { get; set; }
public string JobGroup { get; set; }
public int Priority { get; set; }
public string CronExpression { get; set; }
private readonly ISchedulerFactory _schedulerFactory;
public Scheduler(string server, int port, string scheduler)
{
Address = string.Format("tcp://{0}:{1}/{2}", server, port, scheduler);
_schedulerFactory = new StdSchedulerFactory(GetProperties(Address));
try
{
Instance = _schedulerFactory.GetScheduler();
if (!Instance.IsStarted)
Instance.Start();
}
catch (SchedulerException ex)
{
throw new Exception(string.Format("Failed: {0}", ex.Message));
}
}
private static NameValueCollection GetProperties(string address)
{
var properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "ServerScheduler";
properties["quartz.scheduler.proxy"] = "true";
properties["quartz.threadPool.threadCount"] = "0";
properties["quartz.scheduler.proxy.address"] = address;
return properties;
}
public IScheduler GetScheduler()
{
return Instance;
}
public List<GroupStatus> GetGroups()
{
var results = new List<GroupStatus>();
foreach (var gp in Instance.GetJobGroupNames())
{
results.Add(new GroupStatus()
{
Group = gp,
IsJobGroupPaused = Instance.IsJobGroupPaused(gp),
IsTriggerGroupPaused = Instance.IsTriggerGroupPaused(gp)
});
}
return results;
}
public JobSchedule GetSchedule()
{
var jobKey = new JobKey(JobName, JobGroup);
var trigger = Instance.GetTriggersOfJob(jobKey).FirstOrDefault();
var js = new JobSchedule();
if (trigger != null)
{
js.Name = trigger.Key.Name;
js.Group = trigger.Key.Group;
js.Description = trigger.Description;
js.Priority = trigger.Priority;
js.TriggerType = trigger.GetType().Name;
js.TriggerState = Instance.GetTriggerState(trigger.Key).ToString();
DateTimeOffset? startTime = trigger.StartTimeUtc;
js.StartTime = TimeZone.CurrentTimeZone.ToLocalTime(startTime.Value.DateTime);
var nextFireTime = trigger.GetNextFireTimeUtc();
if (nextFireTime.HasValue)
{
js.NextFire = TimeZone.CurrentTimeZone.ToLocalTime(nextFireTime.Value.DateTime);
}
var previousFireTime = trigger.GetPreviousFireTimeUtc();
if (previousFireTime.HasValue)
{
js.LastFire = TimeZone.CurrentTimeZone.ToLocalTime(previousFireTime.Value.DateTime);
}
}
return js;
}
public List<JobSchedule> GetSchedules()
{
var jcs = new List<JobSchedule>();
foreach (var group in Instance.GetJobGroupNames())
{
var groupMatcher = GroupMatcher<JobKey>.GroupContains(group);
var jobKeys = Instance.GetJobKeys(groupMatcher);
foreach (var jobKey in jobKeys)
{
var triggers = Instance.GetTriggersOfJob(jobKey);
foreach (var trigger in triggers)
{
var js = new JobSchedule();
js.Name = jobKey.Name;
js.Group = jobKey.Group;
js.TriggerType = trigger.GetType().Name;
js.TriggerState = Instance.GetTriggerState(trigger.Key).ToString();
js.Priority = trigger.Priority;
DateTimeOffset? startTime = trigger.StartTimeUtc;
js.StartTime = TimeZone.CurrentTimeZone.ToLocalTime(startTime.Value.DateTime);
DateTimeOffset? nextFireTime = trigger.GetNextFireTimeUtc();
if (nextFireTime.HasValue)
{
js.NextFire = TimeZone.CurrentTimeZone.ToLocalTime
(nextFireTime.Value.DateTime);
}
DateTimeOffset? previousFireTime = trigger.GetPreviousFireTimeUtc();
if (previousFireTime.HasValue)
{
js.LastFire = TimeZone.CurrentTimeZone.ToLocalTime
(previousFireTime.Value.DateTime);
}
jcs.Add(js);
}
}
}
return jcs;
}
public List<JobSchedule> GetSchedules(string groupName)
{
var jcs = new List<JobSchedule>();
var groupMatcher = GroupMatcher<JobKey>.GroupContains(groupName);
var jobKeys = Instance.GetJobKeys(groupMatcher);
foreach (var jobKey in jobKeys)
{
var triggers = Instance.GetTriggersOfJob(jobKey);
foreach (var trigger in triggers)
{
var js = new JobSchedule();
js.Name = jobKey.Name;
js.Description = trigger.Description;
js.Group = jobKey.Group;
js.TriggerType = trigger.GetType().Name;
js.TriggerState = Instance.GetTriggerState(trigger.Key).ToString();
js.Priority = trigger.Priority;
DateTimeOffset? startTime = trigger.StartTimeUtc;
js.StartTime = TimeZone.CurrentTimeZone.ToLocalTime(startTime.Value.DateTime);
DateTimeOffset? nextFireTime = trigger.GetNextFireTimeUtc();
if (nextFireTime.HasValue)
{
js.NextFire = TimeZone.CurrentTimeZone.ToLocalTime(nextFireTime.Value.DateTime);
}
DateTimeOffset? previousFireTime = trigger.GetPreviousFireTimeUtc();
if (previousFireTime.HasValue)
{
js.LastFire = TimeZone.CurrentTimeZone.ToLocalTime
(previousFireTime.Value.DateTime);
}
jcs.Add(js);
}
}
return jcs;
}
public string GetMetaData()
{
var metaData = Instance.GetMetaData();
return string.Format(
"{0}Name: '{1}'{0}Version:
'{2}'{0}ThreadPoolSize: '{3}'{0}IsRemote: '{4}'{0}JobStoreName: '{5}'
{0}SupportsPersistance: '{6}'{0}IsClustered: '{7}'",
Environment.NewLine, metaData.SchedulerName, metaData.Version, metaData.ThreadPoolSize,
metaData.SchedulerRemote, metaData.JobStoreType.Name,
metaData.JobStoreSupportsPersistence,
metaData.JobStoreClustered);
}
public bool UnscheduleJob()
{
var jobKey = new JobKey(JobName, JobGroup);
if (Instance.CheckExists(jobKey))
{
return Instance.UnscheduleJob(new TriggerKey(JobName, JobGroup));
}
return false;
}
public bool UnscheduleAll()
{
foreach (var group in Instance.GetTriggerGroupNames())
{
var groupMatcher = GroupMatcher<JobKey>.GroupContains(group);
var jobKeys = Instance.GetJobKeys(groupMatcher);
foreach (var triggers in jobKeys.Select(jobKey => Instance.GetTriggersOfJob(jobKey)))
{
return Instance.UnscheduleJobs(triggers.Select(t => t.Key).ToList());
}
}
return false;
}
public void DeleteAll()
{
Instance.Clear();
}
public void RescheduleJob()
{
var trigger = (ICronTrigger)TriggerBuilder.Create()
.WithIdentity(JobName, JobGroup)
.WithCronSchedule(CronExpression)
.WithPriority(Priority)
.Build();
Instance.RescheduleJob(new TriggerKey(JobName, JobGroup), trigger);
}
}
}
I hope this is useful for you…
This was sixth in the series of posts on enterprise scheduling using Quartz.net. In the next post, I’ll be covering how to build a sample MVC website to look at the jobs scheduled via Quartz.Net scheduling Windows service. Thank you for taking the time out and reading this blog post. If you enjoyed the post, remember to subscribe to http://feeds.feedburner.com/TarunArora. Stay tuned!