Sunday 30 September 2012

Quick sample to schedule tasks using Quartz.Net in a Windows Service. (25 min)



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 { getset; }
            [XmlAttribute]
            public string Name { getset; }
            [XmlAttribute]
            public string Email { getset; }

            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 TaskTwoIJob
    {
        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 :)



7 comments:

  1. Thanks for posting - this helped me out quite a bit!

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Important: 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).

    I 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

    ReplyDelete
  5. Dear all,

    Please 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

    ReplyDelete
  6. Hi,
    I have done all steps but after instoletion it is not showing in services manager. Please help me

    ReplyDelete