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
In our previous piece on creating Windows Services in .NET Core, we talked about how to do things the “Microsoft” way. What we found was that while it was simple to get up and running, it was much harder to debug our service. Infact I would say borderline impossible.
And that’s where Topshelf comes in. Topshelf is a .NET Standard library that takes a tonne of the hassle out of creating Windows Services in both .NET Framework and .NET Core. But rather than recite the sales pitch to you, let’s jump right in!
Looking to monitor your service?
Just a quick note from our sponsor elmah.io. Once your Windows Service is actually up and running, how are you intending to monitor it? elmah.io provides an awesome feature called “Heartbeats” to monitor how your background tasks are getting on, and if they are running as they should be. elmah.io comes with a 21 day, no credit card required trial so there’s no reason not to jump in and have a play.
Setup
Similar to our “Microsoft” method, there is no Windows Service or Topshelf “Visual Studio Template”. We instead just create a regular old .NET Core console application.
Then from our Package Manager Console, we run the following to install the Topshelf libraries.
Install-Package Topshelf
The Code
We essentially want to recreate our previous code sample to work with Topshelf. Here’s how we do that :
public class LoggingService : ServiceControl { private const string _logFileLocation = @"C:\temp\servicelog.txt"; private void Log(string logMessage) { Directory.CreateDirectory(Path.GetDirectoryName(_logFileLocation)); File.AppendAllText(_logFileLocation, DateTime.UtcNow.ToString() + " : " + logMessage + Environment.NewLine); } public bool Start(HostControl hostControl) { Log("Starting"); return true; } public bool Stop(HostControl hostControl) { Log("Stopping"); return true; } }
All rather simple.
We inherit from the “ServiceControl” class (Which isn’t actually needed but it just provides a good base for us to work off). We have to implement the two methods to start and stop, and we just log those methods as we did before.
In our Main method of program.cs, it’s actually really easy. We can just use the HostFactory.Run method to kick off our service with minimal effort :
static void Main(string[] args) { HostFactory.Run(x => x.Service<LoggingService>()); }
Crazy simple. But that’s not the only thing HostFactory can do, for example I may want to say that when my service crashes, I want to restart the service after 10 seconds, give it a nicer name, and set it to start automatically.
static void Main(string[] args) { HostFactory.Run(x => { x.Service<LoggingService>(); x.EnableServiceRecovery(r => r.RestartService(TimeSpan.FromSeconds(10))); x.SetServiceName("TestService"); x.StartAutomatically(); } ); }
I could go on and on but just take a look through the Topshelf documentation for some configuration options. Essentially anything you would normally have to painfully do through the windows command line, you can set in code : https://topshelf.readthedocs.io/en/latest/configuration/config_api.html
Deploying Our Service
Same as before, we need to publish our app specifically for a Windows environment. From a command prompt, in your project directory, run the following :
dotnet publish -r win-x64 -c Release
Now we can check out the output directory at bin\Release\netcoreappX.X\win-x64\publish and we should find that we have a nice little exe waiting for us to be installed.
Previously we would use typical SC windows commands to install our service, but Topshelf utilizes it’s own command line parameters for installing as a service. Almost all configuration that you can do in code you can also do from the command line (Like setting recovery options, service name/description etc). You can check out the full documentation here :
For us, we are just going to do a bog standard simple install. So in our output directory, I’m going to run the following from the command line : http://docs.topshelf-project.com/en/latest/overview/commandline.html
WindowsServiceExample.exe install
Where WindowsServiceExample.exe is my project output. All going well my service should be installed! I often find that even when setting the service to startup automatically, it doesn’t always happen. We can actually start the service from the command line after installation by running :
WindowsServiceExample.exe start
In deployment scenarios I often find I have to install the service, wait 10 seconds, then attempt to start it using the above and we are away laughing.
Debugging Our Service
So when doing things the “Microsoft” way, we ran into issues around debugging. Mostly that we had to either use command line flags, #IF DEBUG directives, or config values to first work out if we are even running inside a service or not. And then find hacky ways to try and emulate the service from a console app.
Well, that’s what Topshelf is for!
If we have our code open in Visual Studio and we just start debugging (e.g. Press F5), it actually emulates starting the service in a console window. We should be met with a message saying :
The TestService service is now running, press Control+C to exit.
This does exactly what it says on the tin. It’s started our “service” and runs it in the background as if it was running as a Windows Service. We can set breakpoints as per normal and it will basically follow the same flow as if it was installed normally.
We can hit Ctrl+C and it will close our application, but not before running our “Stop” method of our service, allowing us to also debug our shutdown procedure if required. Compared to debug directives and config flags, this is a hell of a lot easier!
There is just one single gotcha to look out for. if you get a message similar to the following :
The TestService service is running and must be stopped before running via the console
This means that the service you are trying to debug in Visual Studio is actually installed and running as a Windows Service on the same PC. If you stop the Windows Service from running (You don’t have to uninstall, just stop it), then you can debug as normal.
What’s Next
A helpful reader pointed out in the previous article that .NET Core actually has a completely different way of running Windows Services. It essentially utilizes the “Hosted Services” model that has been introduced into ASP.NET Core and allows them to run as Windows Services which is pretty nifty!
The title “Creating Windows Services In .NET Core ‘Topshelf Way’ is fake!
When runinng, the service shows the version 4.xxx from .net framework…
topshelf is a cool tool but depends on old framework…
Hi Fred,
Do you have a gist/repo so I can check out your issue? Topshelf is .NET Standard so itself shouldn’t run under any framework.
Hi Fred,
Topshef supports .Net Standard 2.0 and is good with .net core. I created a Windows Service on .Net Core 3.1 and referred to TopShelf v4.2.1.215
When debugging the service it incorrectly says .Net Framework but is showing the v3.1.0 which is .net core 3.1 for my project.
service showing: ‘Topshelf v4.2.1.215, .NET Framework v3.1.0’
Thanks,
Hi Wade. Thanks for great article. I have a functioning console app that processes http requests using NancyFX. If I don’t start the console app, but make the HTTP request, it says “This site can’t be reached. localhost refused to connect”. This is expected of course. Using this article and Topshelf documentation, I’m trying to run it as a simulated Windows service. I made all the changes, moved most logic from Main into the Service class Start method, spin up a thread and go into a “DoWork” method which just loops waiting for the message. Unfortunately I’m getting the above error message, even though the “service” is running. Tried running Visual Studio as administrator. Tried running the compiled EXE as administrator. Still doesn’t work, and probably not a permissions issue. Can you think of any obvious thing I’m missing? Tried Topshelf group forum and Stack Overflow, but no answers forthcoming yet. Thanks…
Never mind, figured it out.
Thanks so much for this post. Had been breaking my head over the local debugging issue that I had for my windows service.
I am writing a service for Windows for the first time.You’ve helped me a lot. Excellent series of articles. Thank you very much.