DEV Community

Cover image for Easy Guide to Worker Services in .NET Core
Henrick Tissink
Henrick Tissink

Posted on

Easy Guide to Worker Services in .NET Core

Worker Service

complete

.NET finally feels like a complete framework - you can build RESTful APIs; Console Applications; Background Services; Web front-ends; and most importantly Windows Services - all without needing 3rd party software.

Worker Services are .NET's background service offering. It's the perfect way to create long running services, or services that need to fire at set intervals.

With the advent of .NET Core 3.0, Worker Services can now be created, and installed, as Windows Services without needing 3rd party tools like TopShelf.

Getting Started

The latest version of Visual Studio now comes with a Worker Service template - create a Worker Service Project from this template and call it SimpleFarm.

A farm is a good example project to illustrate the concept of a worker services, because for a farm you need to

  • do things at set intervals,
  • and in a set order.

farming

worker service

Setting up config

Firstly, we'll touch on the nifty way that .NET handles config. Create a Config folder in your project, and add the following class to it:

public class FarmConfig
{
   public int TillTimeInMinutes { get; set; }
   public int PlantTimeInMinutes { get; set; }
   public int HarvestTimeInMinutes { get; set; }
   public int FertileLandInMetersSquared { get; set; }
   public string Crop { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The appsettings.json file is where you will be keeping all your config settings - update this file with the following entry:

...
"Farm": {
  "TillTimeInMinutes": 3,
  "PlantTimeInMinutes": 3,
  "HarvestTimeInMinutes": 3,
  "FertileLandInMetersSquared": 100,
  "Crop": "Grain"
} 
...
Enter fullscreen mode Exit fullscreen mode

We're going to be planting grain, on a 100 square meter plot of land. We'll till the land for 3 minutes, plant the crop for 3 minutes, and harvest it in 3 minutes.

Setting up Processes

The farming logic won't be invoked directly in the Worker Service - rather, it will be moved to a set of classes I like to call processes. Create a Process folder in your project, and add the following interface and class to it:

public interface IFarmingProcess
{
   Task TillTheLand();
   Task PlantTheCrop();
   Task HarvestTheCrop();
}
Enter fullscreen mode Exit fullscreen mode

And

public class FarmingProcess : IFarmingProcess
{
   private readonly FarmConfig config;
   private readonly ILogger<FarmingProcess> logger;

   public FarmingProcess(IOptions<FarmConfig> config, ILogger<FarmingProcess> logger)
   {
      this.config = config.Value;
      this.logger = logger;
   }

   public async Task TillTheLand()
   {
      logger.LogInformation($"Tilling {config.FertileLandInMetersSquared} m^2 for {config.Crop} crop!!!");
      await Task.Delay(TimeSpan.FromMinutes(config.TillTimeInMinutes));
   }

   public async Task PlantTheCrop()
   {
      logger.LogInformation($"Planting {config.FertileLandInMetersSquared} m^2 for {config.Crop} crop!!!");
      await Task.Delay(TimeSpan.FromMinutes(config.PlantTimeInMinutes));
   }

   public async Task HarvestTheCrop()
   {
      logger.LogInformation($"Harvesting {config.FertileLandInMetersSquared} m^2 for {config.Crop} crop!!!");
      await Task.Delay(TimeSpan.FromMinutes(config.HarvestTimeInMinutes));
   }
}
Enter fullscreen mode Exit fullscreen mode

The IOptions<FarmConfig> config parameter in the constructor for FarmingProcess is how .NET Core injects config. This will all be wired up once we define what goes into the Dependency Injection container in the Startup.cs file.

Setting up the Worker

Now, we'll set up our Worker Service. Create a Worker folder and add the following class to it:

public class FarmWorker : BackgroundService
{
   private readonly IFarmingProcess farmingProcess;
   private readonly ILogger<FarmWorker> logger;

   public FarmWorker(IFarmingProcess farmingProcess, ILogger<FarmWorker> logger)
   {
      this.farmingProcess = farmingProcess;
      this.logger = logger;
   }

   protected override async Task ExecuteAsync(CancellationToken stoppingToken)
   {
      // This is how we keep the app running (in the background)
      while (!stoppingToken.IsCancellationRequested)
      {
         logger.LogInformation("FarmWorker running at: {time}", DateTimeOffset.Now);

     await farmingProcess.TillTheLand();
     await farmingProcess.PlantTheCrop();
     await farmingProcess.HarvestTheCrop();
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

This is the Worker Service - and it is created by inheriting the BackgroundService class, and overriding ExecuteAsync(...).

When the application runs, it will execute ExecuteAsync(...) in the background. We ensure that ExecuteAsync(...) keeps running/processing its task by wrapping it in a while (!stoppingToken.IsCancellationRequested) {...} loop, which only ends once the application terminates (and cancellation is requested).

Setting up the Application Host

Finally, we set up the host. Add the following Nuget package to your project, to allow it to be installed as a Windows Service:

Microsoft.Extensions.Hosting.WindowsServices

Now remove everything from the Program.cs file and replace with the following:

public class Program
{
   public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

   public static IHostBuilder CreateHostBuilder(string[] args) =>
      Host.CreateDefaultBuilder(args)
          .ConfigureLogging(logging =>
          {
             logging.ClearProviders();
             logging.AddConsole();
                         logging.AddEventLog();
          })
          // Essential to run this as a window service
          .UseWindowsService()
          .ConfigureServices(configureServices);

   private static void configureServices(HostBuilderContext context, IServiceCollection services)
   {
      services.Configure<FarmConfig>(context.Configuration.GetSection("Farm"));
      services.AddLogging();
      services.AddSingleton<IFarmingProcess, FarmingProcess>();
      services.AddHostedService<FarmWorker>();
   }
}
Enter fullscreen mode Exit fullscreen mode

lastly, add the following to the "Logging" property in your appsettings.json,

  "Logging": {
...
    "EventLog": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    }
  },
Enter fullscreen mode Exit fullscreen mode

There are 4 really important things happening here.

1.) .UseWindowsService() allows your worker service to be installed as a Windows Service.

2.) context.Configuration is how you access the configuration that you set within the appsettings.json file.

3.) services.AddHostedService<FarmWorker>() is how you hook your worker service into the application Host process.

4.) logging.AddEventLog() logs messages directly to the Windows Event Log, so we can see if your Worker Service is actually working.

It's crucial that we update the appsettings.json to configure logging to the Windows Event Log.

so close

Running the application, the console should look similar to the following, after all three farming processes have been completed:

worker console

Installing as a Windows Service

Great! We now have a Worker Service that can process data at set intervals, execute some SQL cleanup commands, or even do some farming!

This worker service can be attached to any running .NET Core host - an ASP.NET Core API might be a good place to do this, or it can be conveniently installed as a Windows Service and run all on it's own.

To install this as a Windows Service, run a command line shell from the root of your solution and execute

dotnet build
Enter fullscreen mode Exit fullscreen mode

Secondly, use the Service Create (sc) tool on Windows to install your worker service as a Windows Service. I recommend using PowerShell for this. Run your command line shell as Administrator and execute the follow script/command:

sc.exe create FarmWorkerService binPath=C:\...\path to your solution\SimpleFarm\SimpleFarm\bin\Debug\netcoreapp3.1\SimpleFarm.exe
Enter fullscreen mode Exit fullscreen mode

Your service has been created! but it's not running just yet.

Open the Services app on Windows, and look for your service by name - FarmWorkerService. You should see something similar to this.

Alt Text

You can manage the FarmWorkerService through the Services UI or you can manage it through the console with the following 3 commands

  • sc.exe start DemoWorkerService start the service
  • sc.exe stop DemoWorkerService stops the service
  • sc.exe delete DemoWorkerService deletes the service

Either start your service using the Service Create command line tool, or start it using the Services UI. Your FarmWorkerService should now be running!

running

To confirm that your service is actually running, you can open the Event Viewer app on windows and...

Alt Text

See it happily plodding along!

git hub link to the SimpleFarm project

Oldest comments (0)