Creating a Windows Service with .NET Core 3.0

In a previous version of a Professional C# book I’ve written a complete chapter on how to create Windows Services with the .NET Framework. Using .NET Core 3.0, it’s a lot easier to create Windows Services: just a single line of code is needed to convert a background worker to a Windows Service.

Windows Services

Create a Worker

With .NET Core 3.0, a background worker can be created using Visual Studio or the dotnet CLI command dotnet new worker.

With this template, a Program class is created that uses the Host class. The method CreateDefaultBuilder is used to setup the dependency injection container, configuration, and logging. The dependency injection container managed by the Host class is configured by invoking the method ConfigureServices. In the generated code, the extension method AddHostedService is used to register a background class that implements the interface IHostedService. This interface is indirectly implemented by the Worker class by deriving from the base class BackgroundService. The interface IHostedService defines the methods StartAsync and StopAsync. Adding a hosted service, invoking the Run method of the host starts the host and in turn invokes the startup of the IHostedService.

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

  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
      .ConfigureServices((hostContext, services) =>
      {
        services.AddHostedService<Worker>()
          .Configure<EventLogSettings>(config =>
          {
            config.LogName = "Sample Service";
            config.SourceName = "Sample Service Source";
          });
        });
    }

The Host class is also used by ASP.NET Core 3.0 Web projects. The WebHost class from .NET Core 2.0 is replaced by the more generic Host class.

The Worker class derives from the class BackgroundService. BackgroundService implements the interface IHostedService and defines the abstract method ExecuteAsync. This abstract method is called by the StartAsync method in the BackgroundService. StartAsync is defined by the IHostedService interface. With the implementation of the Worker class, ExecuteAsync uses an endless loop (until cancellation is requested) and writes a log message once a second.

public class Worker : BackgroundService
{
  private readonly ILogger<Worker> _logger;

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

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    while (!stoppingToken.IsCancellationRequested)
    {
      _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
      await Task.Delay(1000, stoppingToken);
    }
  }
}

The main functionality for the Host class is creating the dependency injection container, configuration, and logging. Using CreateDefaultBuilder, configuration is read from the configuration files appsettings.json, appsettings.{env.EnvironmentName}.json, environmental variables, and the command line.

Source Code Host class

Logging configuration is read from the section Logging within the configuration settings. Using the worker template, the configuration file appsettings.json defines logging based on the log level – with Microsoft sources to turn on logging for the Warning level, with the exception of Microsoft.Hosting.Lifetime: here logging is turned on for the Information level. The default configuration is also for the Information level:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

If the application runs on a Windows system, the method CreateDefaultBuilder also adds logging to the Windows event log and sets a filter provider to only log warnings and more critical issues to this provider.

Running the application, log information is written to the console. The worker writes a message every second.

info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:14 +02:00
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\github\MoreSamples\DotnetCore\WindowsServiceSample\WindowsServiceSample
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:15 +02:00
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:16 +02:00
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:17 +02:00
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:18 +02:00
info: WindowsServiceSample.Worker[0]

Convert to a Windows Service

To make a Windows Service of this, you just need to add the NuGet package Microsoft.Extensions.Hosting.WindowsServices, and add the method invocation UseWindowsService to the IHostBuilder fluent API:

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureLogging(
      options => options.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Information))
    .ConfigureServices((hostContext, services) =>
    {
      services.AddHostedService<Worker>()
        .Configure<EventLogSettings>(config =>
      {
        config.LogName = "Sample Service";
        config.SourceName = "Sample Service Source";
      });
    }).UseWindowsService();

To see information level logging in the Windows event log, the filter is explicitly applied with the ConfigureLogging method used with the host builder.

Installing and Managing the Windows Service

After building the application, the new Windows Service can be published using dotnet publish (or using Visual Studio):

dotnet publish -c Release -o c:\sampleservice

To control Windows Services, the sc command can be used. Creating a new Windows Service is done using sc create passing the name of the service and the binPath parameter referencing the executable:

sc create “Sample Service” binPath=c:\sampleservice\WindowsServiceSample.exe

The status of the service can be queried using the Services MMC, or with the command line sc query:

sc query “Sample Service”

After the service is created, it is stopped and need to be started:

sc start “Sample Service”

To stop and delete the service, the sc stop and sc delete commands can be used.

After starting the service, log information can be seen with the Windows Event Viewer:

Windows Event Viewer Log

Web Application as Windows Service

What about hosting Kestrel as a Windows Service? There’s not a lot difference – the package Microsoft.Extensions.Hosting.WindowsServices needs to be added, and the API UseWindowsService invoked.

A Web API project can be created using dotnet new api. This template creates an API returning random weather information.

What’s changed to run the API as a Windows Service is a call to UseWindowsService, and a configuration to what port number the server should listen to. Port 80 is not used to not get in conflict with a local IIS configuration.

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

  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
      .ConfigureServices(services =>
      {
        services.Configure<EventLogSettings>(config =>
        {
          config.LogName = "Sample API Service";
          config.SourceName = "Sample API Service Source";
        });
      })
      .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup<Startup>();
      })
      .ConfigureWebHost(config => 
      {
        config.UseUrls("http://*:5050"); 
      }).UseWindowsService();
}

Now the service can be build, published, and configured as a Windows Service. Opening a browser to reference the configured port with the controller route WeatherForecast returns JSON information from the API service:

http://localhost:5050/WeatherForecast

Weather Forecast

Accessing the Windows Service from a different system, the Firewall needs to be configured to allow accessing this port from the outside.

Take away

The Host class which allows configuration, logging, and dependency injection services configuration in one place, also offers an easy way to create Windows Services. Adding the NuGet package Microsoft.Extensions.Hosting.WindowsServices along with the extension method UseWindowsService practically is all what’s needed.

This way, background functionality based on the worker template, but also hosting a Kestrel server for offering ASP.NET Core Web applications is an easy task.

While Windows Services are only offered on Windows Systems, similar functionality can be offered on Linux systems. On Linux, the NuGet package Microsoft.Extensions.Hosting.SystemD along with the extension method UseSystemD can be used. Using the UseWindowsService API call on a Linux system doesn’t break the server throwing an exception, but instead it just does nothing on a Linux system. Similarly invoking UseSystemD on Windows, nothing is done. This way it’s easy to create both a Windows Service and a SystemD daemon.

If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

Buy Me A Coffee

You can get the complete sample code.

Enjoy learning and programming!

Christian

Links

Hosting DI Container with .NET Core 3.0

Source Code Host class

More information on C# and programming .NET Core applications is in my book Professional C# 7 and .NET Core 2.0, and in my workshops.

Windows Cleaners Image ID 24554590 © Andrey Koturanov | Dreamstime.com

Advertisements

9 thoughts on “Creating a Windows Service with .NET Core 3.0

  1. What will be the same application behavior in Linux?
    In .net core 2.1 i have used PeterKottas.DotNetCore.WindowsService via which i have same application running as windows service in Windows Platform and have setup same application as Linux Service via systemd/systemctl funtionality

    Like

    1. Hi Adam,

      if you need some functionality started when the operating system starts you can create a Windows Service, without the need for a user to login to the system. An example is background functionality that can e.g. check for files in a specific folder and act on the files received. Internet Information Server itself starts up as a Windows Service. You can directly host the Kestrel server to start on your Windows system without the need for IIS. You can also create a listener using GRPC with .NET Core 3. If you start “Services” on your system (or use the command line “sc query” you’ll find many services running, e.g. Windows Time to update the time, Windows Push Notifications to receive notifications, AdobeUpdateService to check if updates are available for the Adobe tools, and many more.

      Christian

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.