Easy Guide to Worker Services in .NET Core
Tags: dotnet
Published: 2020-11-17 07:18:49

# Worker Service

![complete](https://media.giphy.com/media/lS74OiH1JYYak/source.gif)

.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](https://media.giphy.com/media/RrU8f9lImvJja/source.gif)

![worker service](https://dev-to-uploads.s3.amazonaws.com/i/rlaczqzwgzlyxm93kcmt.PNG)

# Setting up config

Firstly, we'll touch on the nifty way that .NET handles [config](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1). Create a Config folder in your project, and add the following class to it:

```csharp
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; }
}
```
The `appsettings.json` file is where you will be keeping all your config settings - update this file with the following entry:

```json
...
"Farm": {
  "TillTimeInMinutes": 3,
  "PlantTimeInMinutes": 3,
  "HarvestTimeInMinutes": 3,
  "FertileLandInMetersSquared": 100,
  "Crop": "Grain"
} 
...
```

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:

```csharp
public interface IFarmingProcess
{
   Task TillTheLand();
   Task PlantTheCrop();
   Task HarvestTheCrop();
}
```

And

```csharp
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));
   }
}
```

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](https://docs.microsoft.com/en-us/dotnet/core/extensions/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:

```csharp
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();
      }
   }
}
```

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](https://www.nuget.org/) 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:

```csharp
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>();
   }
}
```

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

```json
  "Logging": {
...
    "EventLog": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    }
  },
```

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](https://media.giphy.com/media/VEzlrMWk3F7uuFuRSq/source.gif)

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

![worker console](https://dev-to-uploads.s3.amazonaws.com/i/2nxlrxh7w0qyuj6kum1f.PNG)

# 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 

```sh
dotnet build
```

Secondly, use the _Service Create_ (`sc`) tool on Windows to install your worker service as a _Windows Service_. I recommend using [PowerShell](https://docs.microsoft.com/en-us/powershell/) for this. Run your _command line shell_ as Administrator and execute the follow script/command:

```sh
sc.exe create FarmWorkerService binPath=C:\...\path to your solution\SimpleFarm\SimpleFarm\bin\Debug\netcoreapp3.1\SimpleFarm.exe
```

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](https://dev-to-uploads.s3.amazonaws.com/i/h029xx0aavx3jj5xbcjz.png)

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](https://media.giphy.com/media/BpDYodBlBXFIs/source.gif)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dg9v868hw6l1qh3pevh5.PNG)

See it happily plodding along!

[git hub link to the SimpleFarm project](https://github.com/henrick-tissink/SimpleFarm)