Creating a TopShelf application

What is Topshelf?

Topshelf is basically a framework for creating a Windows service (it also supports Mono on Linux – although I’ve not tested this yet).

You might still ask “why do we need this, writing a Windows service isn’t difficult?”

Well a few years back I wrote a similar library, my reasoning was that I wanted to write an application which could be run as both a console application and/or as a Windows service – well Topshelf gives you this capability and more. Ofcourse, it’s also far simpler to debug a console application than a Window’s service.

Getting started

We can write our service in such a way as to have a dependency upon Topshelf or no dependency in which case we use Topshelf methods to redirect Topshelf methods to our code – let’s start by looking at this route (i.e. a non-dependency version of the service).

Topshelf has a perfectly good sample for getting started on their site’s Show me the code page.

I’m going to pretty much recreate it here, but do check out their page.

  • Create a console application (mine’s called MyTickerService)
  • Add a reference to Topshelf via Nuget
  • Create a new class called TickerService

TickerService looks like this

public class TickerService
{
   private readonly Timer timer;

   public TickerService()
   {
      timer = new Timer(5000) {AutoReset = true};
      timer.Elapsed += (sender, e) =>
      {
         Console.WriteLine("Tick:" + DateTime.Now.ToLongTimeString());
      };
   }
 
   public void Start()
   {
      Console.WriteLine("Service started");
      timer.Start();
   }

   public void Stop()
   {
      Console.WriteLine("Service stopped");
      timer.Stop();
   }
}

This is simple enough, we’re just going to keep writing out to the Console on each elaspe of the timer. The Start and Stop methods are there to just allow us to intercept the service start and then the top to allow us to set-up and clean-up anything on our service. The names can be anything we like at this point, as these will ultimately be called from the Topshelf framework methods when we write that code, which we shall do now…

We now need to edit the Program.cs file Main method. Here’s the code

static void Main(string[] args)
{
   HostFactory.Run(x =>
   {
      x.Service<TickerService>(s =>
      {
         s.ConstructUsing(name => new TickerService());
         s.WhenStarted(svc => svc.Start());
         s.WhenStopped(svc => svc.Stop());
      });

      x.SetServiceName("SampleService");
      x.SetDisplayName("Sample");
      x.SetDescription("Sample");
      x.RunAsLocalSystem();
   });
}

Now the Topshelf fluent interface allows us to run the service and as can be seen, when the service starts or stops we simply call any appropriate methods on the TickerService. We then go on to set the service name and the service’s display name and a description before the call to RunAsLocalSystem. We can also run as a specific user/password using RunAs, require a prompt for the same using RunAsPrompt and more (see Topshelf Configuration for more information).

If you now run this application via Visual Studio or from the command prompt, you’ll see the application output text on each elapsed tick. So we can now use our “service” as a standalone console application and debug it easily like this. Then if we want to use the application in a Windows service scenario we can simply run a command prompt as administrator and then use any/all of the following to install the service (once installed it will be visible in services.msc), start the service manually, stop it and uninstall it.

MyTickerService.exe install
MyTickerService.exe start
MyTickerService.exe stop
MyTickerService.exe uninstall

Alternate implementation

The sample code previously showed a way to take a standard C# class and using the Topshelf methods, turn it into a simple service. The class itself had no dependency on Topshelf, but we could have gone a different route and implemented the ServiceControl interface from Topshelf, giving us a TickerService which looks like this

public class TickerService : ServiceControl
{
   private readonly Timer timer;

   public TickerService()
   {
      timer = new Timer(5000) {AutoReset = true};
      timer.Elapsed += (sender, e) =>
      {
         Console.WriteLine("Tick:" + DateTime.Now.ToLongTimeString());
      };
   }

   public bool Start(HostControl hostControl)
   {
      Console.WriteLine("Service started");

      timer.Start();
      return true;
   }

   public bool Stop(HostControl hostControl)
   {
      Console.WriteLine("Service stopped");

      timer.Stop();
      return true;
   }
}

So the only changes here are that we implement the ServiceControl interface and with that implement the Start and Stop methods, meaning we no longer need to tell Topshelf which methods to run when the applications starts and stops, so our Program.cs Main method can now be reduced to

HostFactory.Run(x =>
{
   x.Service<TickerService>();

   x.SetServiceName("SampleService");
   x.SetDisplayName("Sample");
   x.SetDescription("Sample");
   x.RunAsLocalSystem();
});

As our TickerService constructor takes no arguments we can simply use the x.Service() call to create it.

Logging

By default Topshelf will log using TraceSource, but if you want to use one of the alternate, more advanced logging libraries such as NLog or log4net etc. Then thankfully Topshelf allows us to easily integrate these.

I’m going to use NLog. So from Nuget find a version of Topshelf.NLog comaptible with the version of Topshelf you’re using and add this package to your project.

Let’s add a fairly generic NLog configuration to the App.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
  </configSections>
  <nlog>
    <targets>
      <target name="console" type="Console" layout="${message}" />
      <target name="debugger" type="Debugger" layout="${message}"/>
    </targets>
    <rules>
      <logger name="*" minlevel="Debug" writeTo="console,debugger" />
    </rules>
  </nlog>
</configuration>

Now we’ll declare the following variable in the TickerService class

private readonly LogWriter logWriter;

within the TickerService constructor, assign the log writer thus

logWriter = HostLogger.Get<TickerService>();

and finally, in place of the Console.WriteLine in the TickerService we use logWriter.Debug for example, here’s the Elasped event

logWriter.Debug("Tick:" + DateTime.Now.ToLongTimeString());