Quick
sample to schedule tasks using Quartz.Net in a Windows Service. (25 min)
Tools
required: (minimum)
- Visual C# 2010 Express (minimum)
- Quartz.NET library (a port of open
source Java job scheduling framework, Quartz)
This post is
about implementing a job scheduling system using a Windows Service with Quartz.Net.
1)
Open Visual Studio
2) Create a New Project, Name it Scheduler
3) From Installed Templates
select Windows > Windows Service > Click on “Create directory for solution” and
click OK.
4)
Download Quartz.NET
library and unzip the project.
5)
Add Quartz assemblies to the
project. Right click on References > Add Reference > Click Browse button in
order to select Quartz.dll and *Common.logging.dll (Select both files as shown in
the 3th screenshot below and click open) when ready click add and Close buttons
subsequently.
*Quartz.dll comes with a wrapper (Common.logging) that can be
implemented with log4net, enterprise library logging block, NLog.
6)
In order to deploy the
solution, we’ll need to add a installer. Go back or reopen the design view of
your service (Service1.cs) Right click on the open area of the current file
open(Service1.cs) and select Add Installer.
After adding the installer
7)
Right click on
serviceProcessInstaller1, select properties, we will change the account type
under which the service will be running (change it to localsystem).
Implementing Quartz.
8)
Right click on the project >
Add > Class… We will start adding a interface to the project. Name it
ITaskScheduler.cs
9)
Remove the code for the current
class ITaskScheduler and add the following code that represents ITaskScheduler
interface:
public interface ITaskScheduler
{
string Name { get; }
void Run();
void Stop();
}
10)
Implementing ITaskScheduler,
Right click on the project > Add > Class… Name it TaskScheduler.cs
11)
Make TaskScheduler class public and add
the following code inside it.
private IScheduler _scheduler;
public string Name
{
get { return GetType().Name; }
}
public void Run()
{
NameValueCollection properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "MyTaskScheduler";
properties["quartz.plugin.xml.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz";
string path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
properties["quartz.plugin.xml.fileNames"] = path + @"\myTasks.xml";
ISchedulerFactory schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = schedulerFactory.GetScheduler();
System.Diagnostics.Debugger.Launch(); // attach debugger to the process
_scheduler.Start();
}
public void Stop()
{
_scheduler.Shutdown();
}
12)
Resolve errors, right click on
top of a highlighted error >Resolve > using…
13)
Now let’s wire up Quartz with
our Windows Service. Open Service1.cs (switch to code view) and replace the
content of Service1 partial class with the following code:
ITaskScheduler scheduler;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
scheduler = new TaskScheduler();
scheduler.Run();
}
protected override void OnStop()
{
if (scheduler != null)
{
scheduler.Stop();
}
}
14)
Now let’s create a couple of
Tasks. Right click on the project > Add > Class… Name it TaskOne.cs. This class will implement IJob Interface.
Remove the current empty class TaskOne and replace
it with the following:
public class TaskOne : IJob
{
public virtual void Execute(IJobExecutionContext context)
{
Person.SaveAllToXML();
Thread.Sleep(10000);
}
//
// in a real scenario dont leave this class here :)
//
[Serializable]
public class Person
{
[XmlAttribute]
public int PersonId { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Email { get; set; }
private static List<Person> GetAll()
{
var persons = new List<Person>();
persons.Add(new Person() { PersonId = 1, Name = "Peter Pan", Email = "Peter@pan.com" });
persons.Add(new Person() { PersonId = 1, Name = "Selena Testing", Email = "Selena@email.com" });
persons.Add(new Person() { PersonId = 1, Name = "Sharon Testing", Email = "Sharon@email.com" });
return persons;
}
public static void SaveAllToXML()
{
string outputFile = Path.Combine(@"C:\Temp\", "DummyXml_"+DateTime.Now.ToString("yyyMMddHHmmtt")+".txt");
using (var memoryStream = new StreamWriter(outputFile))
{
var xmlSerializer = new XmlSerializer(typeof(List<Person>));
xmlSerializer.Serialize(memoryStream, GetAll());
}
}
public static void SaveAllToCsv()
{
string outputFile = Path.Combine(@"C:\Temp\", "DummyCsv_"+DateTime.Now.ToString("yyyMMddHHmmtt")+".txt");
var persons = GetAll();
IEnumerable<string> rows = persons.Select(p => String.Format("{0},{1},{2}", p.PersonId, p.Name, p.Email));
File.WriteAllLines(outputFile, rows);
}
}
}
15)
Move the cursor to the first
bracket of the Execute method and press F9 to set a break point (And resolve
errors, Right click on the errors, select resolve and the correct assembly).
16)
One more task. Right click on
the project > Add > Class… Name it TaskTwo.cs. Remove the current code auto generated for class TaskTwo and replace
it with the following:
[DisallowConcurrentExecution]
public class TaskTwo: IJob
{
public virtual void Execute(IJobExecutionContext context)
{
Scheduler.TaskOne.Person.SaveAllToCsv();
Thread.Sleep(15000);
}
}
17)
Again, Move the cursor to the
first bracket of the Execute method and press F9 to set a break point.
18)
Add the file to schedule jobs.
Right click on the project > Add > New item … From Templates
select Data and them XML File and name the file MyTasks.xml
19)
Add the following XML to the
file you’ve just created.
<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>TaskOne</name>
<group>GroupOne</group>
<description>Task One</description>
<job-type>Scheduler.TaskOne, Scheduler</job-type>
<durable>true</durable>
<recover>false</recover>
</job>
<trigger>
<simple>
<name>TaskOne</name>
<group>GroupOne</group>
<job-name>TaskOne</job-name>
<job-group>GroupOne</job-group>
<start-time>2012-01-01T19:10:00+02:00</start-time>
<repeat-count>100</repeat-count>
<repeat-interval>6000</repeat-interval>
</simple>
</trigger>
<job>
<name>TaskTwo</name>
<group>GroupTwo</group>
<description>Task Two</description>
<job-type>Scheduler.TaskTwo, Scheduler</job-type>
<durable>true</durable>
<recover>false</recover>
</job>
<trigger>
<simple>
<name>TaskTwo</name>
<group>GroupTwo</group>
<job-name>TaskTwo</job-name>
<job-group>GroupTwo</job-group>
<start-time>2012-01-01T19:10:00+02:00</start-time>
<repeat-count>100</repeat-count>
<repeat-interval>10000</repeat-interval>
</simple>
</trigger>
</schedule>
</job-scheduling-data>
20)
Right click on the xml file you’ve
just added and select properties. Change Copy to Output Directory value from Do not copy to Copy always.
21)
Build solution (Make sure that
the configuration is set to debug). Press F6.
22)
To Install the Windows service,
Search for Visual Studio Command Prompt, right click on it and run it as
administrator.
23)
Once the command prompt window is
open, you can either a) use installutil.exe from the current location or b)
move to the output path where your build was generated. Either way the syntax
is similar: InstallUtil
[assemblyName.exe]
a)
C:\Windows\System32>Installutil
C:\(ProjectDirectory)\bin\debug\Scheduler.exe [enter]
b)
C:\ (ProjectDirectory)\bin\debug\
Installutil Scheduler.exe [enter]
Tip:
To move to the
output path where your build was generated (C:\ (ProjectDirectory)\bin\debug\) you can write from the current
directory (C:\Windows\System32>) the
following command:
CD\YourFolder\YourSubFolder\YourTestProject\bin\debug[enter] (make sure you type
the right location of your project)
CD\MyTrash\Post\Scheduler\Bin\debug [enter] (this is the location
I used for this post/recipe)
24)
(Conditional) if you get the
following message System.BadImageFormatException: Could
not load file or assembly … make sure that you are targeting the right
platform. Right Click on the project node, select the right target, rebuild the
project and go back to the previous step.
25)
If everything went fine you
will get the following message:
26)
Open Control Panel, select
System and Security > Administrative tools > Services.
27)
Once you have the local
services view open, look for Service1 and press right click on it to start it….
You will need to click the start button of the subsequent
windows too.
28)
After you press start, a series
of events will happen, the first one is a warning, select “Yes, Debug
Scheduler.exe”
29)
Next question is do you want to
allow the following program from an unknown publisher make changes to this
computer. Click Yes.
30)
Last question but not least…Double
click in your current instance: Scheduler – Microsoft Visual Studio
31)
Finally when the process hits
the line of code to launch the debugger (Debugger.Launch) just Press F5 to continue. The
breakpoints for TaskOne and TaskTwo will hit next. (Time to play and see how Quartz
library works) Note that TaskTwo will never execute more than once at a time
due to the attribute ([DisallowConcurrentExecution]).
32)
After
stoping the debug process, don’t forget to stop the service
To uninstall the service just
write installutil Scheduler.exe /u
Considerations
Another Quick
way to implement Quartz is as a Console Application. Easier to debug and
implement.
Logging
When you're
holding a hammer, everything looks like a nail. Make sure you find a good use
for the library :)
Thanks for posting - this helped me out quite a bit!
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteImportant: It took me ages to get this working as I'd named the class TestScheduler instead of Scheduler (and I didn't realise the class name was critical). Due to the logging framework, all the exceptions get hidden (even in the debugger).
ReplyDeleteI would strongly recommend you add an additional step: Create an App.config file file in the project root and add the following config information.
EDIT: I can't actually add the config file directly as this site doesn't let me, so it's here: http://pastebin.com/GR6K3T2a
Dear all,
ReplyDeletePlease let me know to how to running schedule every hour?
And I have a question, how to config MyTask.xml with Cron Expression?
I'm looking for your reply, Thanks
Hi,
ReplyDeleteI have done all steps but after instoletion it is not showing in services manager. Please help me
Scheduling in C# for .NET application
ReplyDelete