Have you ever wanted to run scheduled tasks within a running Windows Service? I do from time to time, and the discovery of this library made a life a lot easier. I’ve come across Quartz.NET, which gives you a rich job scheduling system.
Quartz.NET is a pure .NET library written in C# and is a port of popular framework Quartz.
Here you can find full list of features.
Our use case
Our customer wanted a solution where, they could run few sync jobs during the day from SQL database to other systems and services. They have on-prem infrastructure with Windows virtual machines. For that, we designed a Windows Service, which would implement a Quartz.NET jobs and run them at specific time. One thing to note, Quartz.NET supports cron job triggers, so you can easily write that instead of simple trigger every X seconds.
So the solution is pretty straight forward.
- Create Windows Service Project in Visual Studio.
- Install Quartz.NET nuget package.
- Setup Quartz.NET library within the service.
- Implement a IJob interface, which does the processing.
- Install the Windows Service on the server and run it
Below is a solution of the working Windows Service made out of 3 parts, app.config, UserSync.cs and UserSyncJob.cs. First let’s look at the configuration (app.config) which contains Active Directory login information (don’t store passwords there, this is for demo purposes only). The RunEveryXSeconds configuration runs a job every 300 seconds:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<applicationSettings> <SyncService.Properties.Settings> <setting name="ADDomain" serializeAs="String"> <value>domain.lan:389</value> </setting> <setting name="AdContainer" serializeAs="String"> <value>DC=domain,DC=lan</value> </setting> <setting name="AdUsername" serializeAs="String"> <value>admin</value> </setting> <setting name="AdPassword" serializeAs="String"> <value>P@ssword!!</value> </setting> <setting name="RunEveryXSeconds" serializeAs="String"> <value>300</value> </setting> </SyncService.Properties.Settings> </applicationSettings> |
Next let’s look at the Windows Service SyncService class. There is an important part JobBuilder.Create<UserSyncJob>() which ties UserSyncJob class to a job. Before that we set up the scheduler and after that the trigger and then run the scheduler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
namespace SyncService { public partial class Service : ServiceBase { public Service() { InitializeComponent(); } protected override async void OnStart(string[] args) { _logger.Info("Starting service..."); var props = new NameValueCollection { { "quartz.serializer.type", "binary" } }; var factory = new StdSchedulerFactory(props); // get a scheduler var sched = await factory.GetScheduler(); await sched.Start(); // define the job and tie it to our UserSyncJob class using configurations from app.config as a job data. var job = JobBuilder.Create<UserSyncJob>() .WithIdentity("UserSyncJob", "DefaultGroup") .UsingJobData("ADDomain", Properties.Settings.Default.ADDomain) .UsingJobData("ADContainer", Properties.Settings.Default.AdContainer) .UsingJobData("ADPassword", Properties.Settings.Default.AdPassword) .UsingJobData("ADUsername", Properties.Settings.Default.AdUsername) .Build(); // Trigger the job to run now, and then every X seconds, specified in the app.config and repeat forever var trigger = TriggerBuilder.Create() .WithIdentity("EveryXSecondsTrigger", "DefaultGroup") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(Properties.Settings.Default.RunEveryXSeconds) .RepeatForever()) .Build(); _logger.Info("Running job..."); await sched.ScheduleJob(job, trigger); } protected override void OnStop() { _logger.Info("Stopping service..."); } } } |
Now let’s look at the implementation of IJob, UserSyncJob class. Create a class UserSyncJob that implements IJob interface with method Execute(IJobExecutionContext context). A job below just writes the text “Job Run!” to a file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
namespace SyncService { public class UserSyncJob : IJob { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); public Task Execute(IJobExecutionContext context) { try { //Load Job configuration var key = context.JobDetail.Key; var dataMap = context.JobDetail.JobDataMap; var ADDomain = dataMap.GetString("ADDomain"); var ADContainer = dataMap.GetString("ADContainer"); var ADUsername = dataMap.GetString("ADUsername"); var ADPassword = dataMap.GetString("ADPassword"); _logger.Info($"UserSyncJob Run at {DateTime.Now}"); var stopwatch = Stopwatch.StartNew(); stopwatch.Start(); //********OUR LOGIC START _logger.Info("Job Run!"); //********OUR LOGIC END stopwatch.Stop(); _logger.Info($"UserSyncJob finished, time elapsed {stopwatch.Elapsed.Seconds}."); return Task.CompletedTask; } catch (Exception e) { _logger.Error(e.Message); return Task.CompletedTask; } } } } |
And that’s pretty much it. When you run the Windows service, the Job should be initialized and run for the first time and then every 300 seconds.
And that’s all for this post! I hope you find it useful :).
Leave a Reply
You must belogged in to post a comment.