Creating Windows Services In .NET Core – Part 3 – The “.NET Core Worker” Way

This article is part of a series on creating Windows Services in .NET Core.

Part 1 – The “Microsoft” Way
Part 2 – The “Topshelf” Way
Part 3 – The “.NET Core Worker” Way

This article has been a long time coming I know. I first wrote about creating Windows Services in .NET Core all the way back in September (3 months ago). And on that post a helpful reader (Shout out to Saeid!) immediately commented that in .NET Core 3, there is a brand new way to create Windows Services! Doh! It reminds me of the time I did my 5 part series on Azure Webjobs in .NET Core, and right as I was completing the final article, a new version of the SDK got released with a tonne of breaking changes making me have to rewrite a bunch.

Thankfully, this isn’t necessarily a “breaking change” to how you create Windows Services, the previous articles on doing it the “Microsoft Way” and the “Topshelf Way” are still valid, but this is just another way to get the same result (Maybe with a little less cursing to the programming gods).

Looking to monitor your service?

Just a quick note from our sponsor Once your Windows Service is actually up and running, how are you intending to monitor it? provides an awesome feature called “Heartbeats” to monitor how your background tasks are getting on, and if they are running as they should be. comes with a 21 day, no credit card required trial so there’s no reason not to jump in and have a play.

The Setup

The first thing you need to know is that you need .NET Core 3.0 installed. At the time of writing, .NET Core 3.1 has just shipped and Visual Studio should be prompting you to update anyway. But if you are trying to do this in a .NET Core 2.X project, it’s not going to work.

If you like creating projects from the command line, you need to create a new project as the type “worker” :

dotnet new worker

If you are a Visual Studio person like me, then there is actually a template inside Visual Studio that does the exact same thing.

Doing this creates a project with essentially two files. You will have your program.cs which is basically the “bootstrapper” for your app. And then you have something called worker.cs which is where the logic for your service goes.

It should be fairly easy to spot, but to add extra background services to this program to run in parallel, you just need to create a new class that inherits from BackgroundService :

public class MyNewBackgroundWorker : BackgroundService
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
        //Do something. 

Then in our program.cs file, we just add the worker to our service collection :

.ConfigureServices((hostContext, services) =>

AddHostedService has actually been in the framework for quite some time as a “background service” type task runner that typically runs underneath your web application. We’ve actually done an article on hosted services in ASP.NET Core before, but in this case, the hosted service is basically the entire app rather than it being something that runs behind the scenes of your web app.

Running/Debugging Our Application

Out of the box, the worker template has a background service that just pumps out the datetime to the the console window. Let’s just press F5 on a brand new app and see what we get.

info: CoreWorkerService.Worker[0]
      Worker running at: 12/07/2019 08:20:30 +13:00
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.

We are up and running immediately! We can leave our console window open to debug the application, or close the window to exit. Compared to the hell we went through trying to debug our Windows Service when creating it the “Microsoft” way, this is like heaven.

Another thing to note is that really what we have infront of us is a platform for writing console applications. In the end we are only writing out the time to the console window, but we are also doing that via Dependency Injection creating a hosted worker. We can use this DI container to also inject in repositories, set environments, read configuration etc.

The one thing it’s not yet is a windows service…

Turning Our App Into A Windows Service

We need to add the following package to our app :

Install-Package Microsoft.Extensions.Hosting.WindowsServices

Next, head to our program.cs file and modify it by adding a call to “UseWindowsService()”.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    .ConfigureServices((hostContext, services) =>

And that’s it!

Running our application normally is just the same and everything functions as it was. The big difference is that we can now install everything as a service.

To do that, first we need to publish our application. In the project directory we run :

dotnet publish -r win-x64 -c Release

Note in my case, I’m publishing for Windows X64 which generally is going to be the case when deploying a Windows service.

Then all we need to do is run the standard Windows Service installer. This isn’t .NET Core specific but is instead part of Windows :

sc create TestService BinPath=C:\full\path\to\publish\dir\WindowsServiceExample.exe

As always, the other commands available to you (including starting your service) are :

sc start TestService
sc stop TestService
sc delete TestService

And checking our services panel :

It worked!

Installing On Linux

To be honest, I don’t have a hell of a lot of experience with Linux. But the general gist is…

Instead of installing Microsoft.Extensions.Hosting.WindowsServices , you need to install Microsoft.Extensions.Hosting.Systemd . And then instead of calling UseWindowsService()  you’ll instead call UseSystemd() .

Obviously your dotnet publish and installation commands will vary, but more or less you can create a “Windows Service” that will also run on Linux!

Microsoft vs Topshelf vs .NET Core Workers

So we’ve now gone over 3 different ways to create Windows Services. You’re probably sitting there going “Well… Which one should I chose?”. Immediately, let’s bin the first Microsoft old school way of going things. It’s hellacious to debug and really doesn’t have anything going for it.

That leaves us with Topshelf and .NET Core workers. In my opinion, I like .NET Core Workers for fitting effortlessly into the .NET Core ecosystem. If you’re already developing in ASP.NET Core, then everything just makes sense creating a worker. On top of that, when you create a BackgroundService, you can actually lift and shift that to run inside an ASP.NET Core website at any point which is super handy. The one downside is the installation. Having to use SC commands can be incredibly frustrating at times and Topshelf definitely has it beat there.

Topshelf in general is very user friendly and has the best installation process for Windows Services. But it’s also another library to add to your list and another “framework” to learn, which counts against it.

Topshelf or .NET Core Workers, take your pick really.

22 thoughts on “Creating Windows Services In .NET Core – Part 3 – The “.NET Core Worker” Way”

  1. I have tried topshelf and found it very easy to use. See
    I can let run my application as normal console application or I can choose to install/uninstall it with the same exe which is pretty handy and good to debug. Also the additional install/uninstall hooks which are simply delegates are plain easy and exactly the way I would have created such a tool.

  2. Both Microsoft and Topshelf have the Start and Stop events. I’m wondering how does it work with .NET Core Workers ? How can you detect that the service is stopping and perform a nice clean up before closing the app ?

  3. Great article! I would like to know if it’s possible to run Webjobs as windows service using this approach. I have a dotnet core console app Wich uses Webjobs with some service triggers(queue, blob, etc). But I cannot find how to ‘fit’ Backgrounder ice in this context.

    • Hmm, honestly I’ve never tried. I assume it’s because you want to run a Windows Service that triggers on a blob etc? I’m thinking that it probably wouldn’t work because it uses things like Storage to store where it’s up to, also things like how many Webjobs can run at one time etc.

      I guess you could try but it’s not hard to create a Console App to listen for a change in a message queue etc (Even via Polling).

  4. i used to prefer TopShelf (it runs on .NET Core) for ease of debugging by configuring it as a console app. However I must do some performance research on .NET core worker

  5. Hi,

    Thank you for this well detailed comparisson. I was wondering about the BeforeUninstall/AfterUninstall methods that Topshelf proposes, is there an equivalent for .Net Core (2.2 or 3.1) ?

  6. Thank you for the review of the three approaches. I’m using the “.net core worker” method. I’ve just encountered a minor issue with it worth noting when comparing options. I have a small number of hosted services batched together as a single windows service. I just found out the hard way if one of those hosted services crashes there is no attempt made to restart it and it doesn’t stop the entire windows service. So it crashes more-or-less silently. In this case it is my own fault for missing a try/catch but if you are expecting out-of-the-box support for restarts of crashed hosted services the .net core way does not support this directly.

    Topshelf seems to have an answer for that but I need my code as close to platform independent as possible (i.e. minimal changes necessary to convert) so I won’t be using it but I like that option a lot.

  7. Thank you as this is very useful, but I’m showing that UseWindowsService() is no longer available in
    Visual Studio Professional 2019
    Version 16.6.3

    • Disregard, I see what I missed. Will Microsoft.Extensions.Hosting.Systemd allow for both Windows or Linux? Trying to stay generic as possible, just in case I move to Linux.

      • @Gavin you’d need to have both installed at the same time in that case. Windows Services and Systemd are 2 different frameworks so it’s not really possible to have a single implementation that works with both without including both libraries.

        They should work fine together, however, since both detect whether they are being executed in their respective environments. When running locally, both will be ignored, when running as a Windows Service, the Systemd one will be ignored, and lastly when running in Linux, only the Systemd flow will be used.

  8. You can also add .UseSystemd to the Configure services so you can run this in linux using the systemd/systemctl daemon controllers.

  9. Great article. I am having trouble though when i install it as a windows service, my appsettings.json cannot be found. Did anybody else had the same issue?

  10. sc start myservice value1 value2 value3
    I found that when I started the service, I followed the parameters. These parameters could not be passed to the args of my service

  11. Hi! Great article!

    I don’t really understand why you said that debugging the MS way is hellacious. I read this statement a lot of times, but don’t understand why.

    I’ve created a lot of windows services using “the MS way” mixing with winforms (or console if you prefer that). Using my way:

    • when I debug the service using Visual Studio then I have a complete UI, not just log files, so I can even push buttons to interact with the background threads/services directly
    • when I run the application as a service it does not interact with desktop of course
    • the app can install and uninstall itself by passing command line arguments to it, so I don’t have to use sc commands, so I can use: myapp.exe /install

    The only thing was to pimp up the Main entry point to make the app able to run as a service or as a UI app or as a console.

    public partial class Server : ServiceBase
        internal void DbgOnStart()
        internal void DbgOnStop()
        protected override void OnStart(string[] args)
        protected override void OnStop()
    static class Program
        static void Main()
            if (Environment.UserInteractive)
                // Debug with UI
                var mainForm = new frmMain();   
                Thread t = new Thread(new ThreadStart(NonServiceProcess));
                t.IsBackground = true;
                // Real service
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[]
                    new Server()
        static Server service;
        internal static void NonServiceProcess()
            service = new Server();


    • I like it. I think the thing is, you’ve had to add wrappers around this etc (Which is fine), that make it slightly less hellacious. I think all you’re really doing is rebuilding what TopShelf does for you. Nothing wrong with that. But I felt that if you stuck with just the core Windows Services offering, then it is kinda “hellacious” without building out your own set of helpers to actually debug the thing. And why do that when Topshelf is there is my feeling.

      • Actually I use this code since 2005 or so… It has been an easy way for me to copy-paste for a new project…

  12. Hi, I found this series about creating Windows Services and I really like it. But I have trouble with the UseWindowsService() method. It seems to miss in the packages I install.
    I am using VS Code and I already tried to install different version of the package “Microsoft.Extensions.Hosting.WindowsServices”, but the method seems to be included nowhere.
    VS Code always shows me that “IHostBuilder” does not contain a definition for “UseWindowsService”
    Any idea what is wrong?


Leave a Comment