Creating windows sercvice in dotnet core
TopShelf
Setup
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;
}
}
</pree>
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 :
<pre>
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.