Creating Schedule Driven Windows Service in .NET Core 3.0

The upcoming version of .NET Core, 3.0, will have native support for Windows and Linux Services.  On Windows the services will rely on standard Windows features.  On Linux it will rely on Systemd.  One of the most common use of windows services in to run background tasks based on a schedule.  One of the most popular frameworks for scheduling in Quartz.net.  In this quick post I will demonstrate now to achieve this task on Windows.  Let’s go step by step.

As step 1, you will need to download and install Visual Studio preview 16.3 that includes support for .NET Core 3.0 and C#8.  Of course, the Studio may be released by the time you are reading this, so you do not need to use preview in this case, use main download channel.

In step 2, run Visual Studio, select Create New Project, and pick template called Worker Service.  This will stub out the project for you with two files.  Program.cs is your entry point.  Worker.cs is the actual service worker.

In step 3, we need to enable Windows service infrastructure by calling UseWindowsServices extension method.

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });

To use Quartz we need to install the NuGet package called Quartz. 

In the next step we need to create a couple of Jobs.

    public class Job1 : IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            await File.AppendAllLinesAsync(@"c:\temp\job1.txt", new[] { DateTime.Now.ToLongTimeString() });
        }
    }
    public class Job2 : IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            await File.AppendAllLinesAsync(@"c:\temp\job2.txt", new[] { DateTime.Now.ToLongTimeString() });
        }
    }

The jobs just update text file, they are demo jobs after all, in c:\temp folder.  You need to create this folder if it does not exist.

In the next step we need to create schedule and schedule the jobs.  The final Worker class looks as follows.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Quartz;
using Quartz.Impl;

namespace WorkerService1
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private StdSchedulerFactory _schedulerFactory;
        private CancellationToken _stopppingToken;
        private IScheduler _scheduler;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await StartJobs();
            _stopppingToken = stoppingToken;
            while (!stoppingToken.IsCancellationRequested)
            {
                await Task.Delay(1000, stoppingToken);
            }
            await _scheduler.Shutdown();
        }

        protected async Task StartJobs()
        {
            _schedulerFactory = new StdSchedulerFactory();

            _scheduler = await _schedulerFactory.GetScheduler();
            await _scheduler.Start();

            IJobDetail job1 = JobBuilder.Create<Job1>()
                .WithIdentity("job1", "gtoup")
                .Build();

            ITrigger trigger1 = TriggerBuilder.Create()
                .WithIdentity("trigger_10_sec", "group")
                .StartNow()
                .WithSimpleSchedule(x => x
                    .WithIntervalInSeconds(10)
                    .RepeatForever())
            .Build();


            IJobDetail job2 = JobBuilder.Create<Job2>()
               .WithIdentity("job2", "group")
               .Build();

            ITrigger trigger2 = TriggerBuilder.Create()
                .WithIdentity("trigger_20_sec", "group")
                .StartNow()
                .WithSimpleSchedule(x => x
                    .WithIntervalInSeconds(20)
                    .RepeatForever())
            .Build();


            await _scheduler.ScheduleJob(job1, trigger1, _stopppingToken);
            await _scheduler.ScheduleJob(job2, trigger2, _stopppingToken);
        }
    }
}

Now we just run the project and observe everything working.  Now we need to confirm that we can install and run our code as Windows Service. 

In this final step, we need to install the service by typing the following on the command prompt to create the service:

sc create “service1″ binPath=”C:\Users\serge.000\Source\Repos\WorkerService1\WorkerService1\bin\Debug\netcoreapp3.0\WorkerService1.exe”

To start the service just type the following and hit Enter

sc start service1

We should now see our files being updated in c:\temp folder at different intervals, base on the schedules we setup for each job.  When your testing is done, type “sc stop service1” to stop the service.  Then type “sc delete service1” to remove the service from your machine. 

Enjoy.

Leave a Reply

Your email address will not be published. Required fields are marked *