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.
See Creating a Windows Service with .NET 6 for a new version!
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. TheWebHost
class from .NET Core 2.0 is replaced by the more genericHost
class.The
Worker
class derives from the classBackgroundService
.BackgroundService
implements the interfaceIHostedService
and defines the abstract methodExecuteAsync
. This abstract method is called by theStartAsync
method in theBackgroundService
.StartAsync
is defined by theIHostedService
interface. With the implementation of theWorker
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. UsingCreateDefaultBuilder
, configuration is read from the configuration files appsettings.json, appsettings.{env.EnvironmentName}.json, environmental variables, and the command line.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:
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
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.
You can get the complete sample code.
Enjoy learning and programming!
Christian
Links
Hosting DI Container with .NET Core 3.0
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
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
LikeLike
Just use the package Microsoft.Extensions.Hosting.SystemD and the API UseSystemD
LikeLike
Hey Chris, great article.
But now I just wonder what is the usage of windows services?
Do you have any examples?
LikeLiked by 1 person
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
LikeLike
Hi Christian,
I added use the package Microsoft.Extensions.Hosting.WindowsService from nugget package manager as suggested but when I go to add .usewindowsservice() in CreateHostBuilder I can’t add it. It doesn’t appear in the intelligence.
Please guide..
Thanks
LikeLike
UseWindowsService is an extension method for IHostBuilder defined in the namespace Microsoft.Extensions.Hosting. Did you import this namespace?
You can also check the source code for the sample: https://github.com/ProfessionalCSharp/MoreSamples/tree/master/DotnetCore/WindowsServiceSample
LikeLike
I ran into this too. What I did to resolve it was run:
Uninstall-Package Microsoft.Extensions.Hosting
Install-Package Microsoft.Extensions.HOsting.WindowsServices
LikeLike
NOTE: When using Powershell to install a Windows Service, you must use `sc.exe create …` instead of just `sc create …`. I had to go and find this answer. `sc` alone is for the traditional command line.
LikeLiked by 1 person
Thanks for the information. I just tried it out with Powershell of the new Windows Terminal, but here `sc` is working as well.
The problem indeed is with other PowerShell environments. Using the older “Windows PowerShell” environment, `sc` is a cmdlet of PowerShell for Set-Content. That’s why `sc.exe` is needed instead.
`sc` invokes \System32\sc.exe if not overwritten.
LikeLike
Interesting. I thought I had the later version installed. I’ll have to look at that. Thanks!
LikeLike
Thanks for this
LikeLike
Great article, but, at first, I could not get the ConfigureWebHost to appear in intelligence. Turned out that my project was a console type. Changed it to “” It worked.
Add in case someone else had this issue.
LikeLike
Hi thanks for the good write-up.
Source Code for Host class however not found; can you link up?
Thanks, Habib
LikeLike
https://github.com/ProfessionalCSharp/MoreSamples/tree/main/DotnetCore/WindowsServiceSample
LikeLike
Hi, in all my windows service i have something like a OnStart and OnStop method. Are they gone, or how can get them in a .net core application ?
LikeLike
IHostedService defines the methods StartAsync and StopAsync. BackgroundService just implements these methods and defines the ExecuteAsync method, so you only need to override this method. You can also implement the interface on your own, or override the virtual methods from BackgroundService. https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs
LikeLike
Hi Christian,
Thanks for your article.
Its works like a charm.
Especially the web application as windows service part was very useful for me.
LikeLiked by 1 person
Thanks for the great article! I spent 3 days trying to get a .NET5 Windows Service going with a self-hosted SignalR streaming hub before ending up here and realising it was all about the Program and Startup classes. So, thanks!
LikeLike
Ciao Christian
I read your fantastic article, simple and very clear: I would like to ask you is it possible to have a simple starting example for the new .Net 6 in particular for a WebApplication as a windows service?
Grazie!
LikeLike
Hi,
thanks for asking! I’ll do it, but with my current workload expect a few weeks before this article is released.
All the best,
Christian
LikeLike
Chris can you please do a tutorial for https based .net core api as a windows service.. i am stuck with this issue from past 2 days. The http works just fine but https wont load. I already have the certificates installed.
Your help will be highly appreciated.
LikeLike
Hi, did you configure the service user to have access to the certificates? Do you get errors in the event log?
LikeLike
Hi, I use .Net Core Web Api with Net Core 5, and i was write Program.cs like code bellow
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
}).ConfigureWebHost(config =>
{
config.UseUrls(“http://*:5050”);
}).UseWindowsService();
}
but, when start my windows service API not working but when run manually helperService.exe it is work perfect. Do you know what the problem may be?
LikeLike
Can I please ask about controlling the services that you create with .net core.
I followed your example above but found it could not accept command line arguments from the SC command.
Also when I tried to start the service using services.msc it ignored anything entered into the Start Parameters box.
I wanted my service to work with the windows scheduler and have it start with different values depending on when it was started. But I have so far been unable to have the service take the arguments from the commandline in .bat files.
If you could help I would appreciate it.
LikeLike
To clarify, its when calling SC Start that I can’t get the service to see the arguments…
The docs for SC say I should be able to do:
sc [] start []
but how do I get at the ServiceArguments.
LikeLike
try using Environment.GetCommandLineArgs()
LikeLike
Thanks, but I have tried that and it didn’t work when doing SC Start or when starting from Services.msc …it does not pick up the command line args or the Start parameters.
[I should say I am running on windows server 2016 and have tried this with .net core 3.1 and 5.0.]
So, for example, this does not work:
SC START MyService Argvalue1 Argvalue2
All I get is arg[0] which is the path of the executeable.
The only time it picks up other args is on SC Create..eg:
sc create “MyService” binpath=”C:\MyService\MyService.exe Arg1 Arg2″
I was hoping I could SC Stop and then SC Start the service with different Arg values..
I’m starting to think I would need to revert to using a.net framework instead of .net core
LikeLike
Defining the command line arguments for the application to start can be defined with the service (sc create). You can check the service configured with Windows – several services use the same binary but with different arguments. With my new blog article, I also mentioned this – but you’ve already succeeded with that, accessing the command line arguments from the service configuration.
You can communicate with a running service to do some actions by creating a pipe, and sending custom commands to the pipe – or send HTTP requests.
LikeLike
See my new blog article on creating Windows Services with .NET 6: https://csharp.christiannagel.com/2022/03/22/windowsservice-2/
LikeLike