Creating Windows Services with Topshelf

Topshelf provides an alternative approach to developing Windows services. This article demonstrates how to build a Windows service using Topshelf through a step-by-step process. Topshelf is an open-source, cross-platform hosting framework that supports both Windows and Mono environments. It enables developers to quickly create robust service hosts with minimal code.

Installation

To get started:

  1. Visit the official website: http://topshelf-project.com/ for comprehensive documentation and downloads.
  2. The source code is hosted on GitHub at http://github.com/topshelf/Topshelf/downloads.
  3. Create a new project and reference the Topshelf.dll assembly. For enhanced logging capabilities, consider also referencing Topshelf.Log4Net.

Usage

The official documentation provides a straightforward example that can run immediately after implementation. Refer to the quickstart guide at: http://docs.topshelf-project.com/en/latest/configuration/quickstart.html

public class MessagePublisher
{
    readonly Timer _timer;
    
    public MessagePublisher()
    {
        _timer = new Timer(1000) { AutoReset = true };
        _timer.Elapsed += (sender, eventArgs) => Console.WriteLine("Time is {0} and everything is fine", DateTime.Now);
    }
    
    public void Start() { _timer.Start(); }
    public void Stop() { _timer.Stop(); }
}

public class Program
{
    public static void Main()
    {
        HostFactory.Run(x => 
        {
            x.Service<MessagePublisher>(s => 
            {
                s.ConstructUsing(name => new MessagePublisher());
                s.WhenStarted(tc => tc.Start());
                s.WhenStopped(tc => tc.Stop());
            });
            
            x.RunAsLocalSystem();
            x.SetDescription("Sample Topshelf Host");
            x.SetDisplayName("Messages");
            x.SetServiceName("MessagesService");
        });
    }
}

After running the application, output will appear every second as shown below:

Deployment Configuration

Once development is complete, deploying the application as a service requires simple configuration:

  • Install: TopshelfDemo.exe install
  • Start: TopshelfDemo.exe start
  • Uninstall: TopshelfDemo.exe uninstall

Upon successful installation, the newly created service will apppear in the Windows Services list.

Extended Configuration

Basic Host Configuraton

The official documentation details the parameters of HostFactory. Below are explanations of commonly used methods:

HostFactory.Run(x => 
{
    x.Service<MessagePublisher>(s => 
    {
        s.ConstructUsing(name => new MessagePublisher());
        s.WhenStarted(tc => tc.Start());
        s.WhenStopped(tc => tc.Stop());
    });
    
    x.RunAsLocalSystem(); // Run under NETWORK_SERVICE account
    
    x.SetDescription("Sample Topshelf Host description");
    x.SetDisplayName("Messages Display Name");
    x.SetServiceName("MessagesService");
});

Service Implementation Patterns

Pattern 1: Implementing ServiceControl Interface

namespace TopshelfDemo
{
    public class MessagePublisher : ServiceControl
    {
        private Timer _timer = null;
        readonly ILog _log = LogManager.GetLogger(typeof(MessagePublisher));
        
        public MessagePublisher()
        {
            _timer = new Timer(1000) { AutoReset = true };
            _timer.Elapsed += (sender, eventArgs) => _log.Info(DateTime.Now);
        }
        
        public bool Start(HostControl hostControl)
        {
            _log.Info("TopshelfDemo has started");
            _timer.Start();
            return true;
        }

        public bool Stop(HostControl hostControl)
        {
            throw new NotImplementedException();
        }
    }
    
    class Program
    {
        public static void Main(string[] args)
        {
            var logCfg = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config");
            XmlConfigurator.ConfigureAndWatch(logCfg);

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

                x.SetDescription("Sample Topshelf Host description");
                x.SetDisplayName("Messages Display Name");
                x.SetServiceName("MessagesService");
            });
        }
    }
}

Pattern 2: Standard Approach

namespace TopshelfDemo
{
    public class MessagePublisher
    {
        private Timer _timer = null;
        readonly ILog _log = LogManager.GetLogger(typeof(MessagePublisher));
        
        public MessagePublisher()
        {
            _timer = new Timer(1000) { AutoReset = true };
            _timer.Elapsed += (sender, eventArgs) => _log.Info(DateTime.Now);
        }
        
        public void Start(){ _timer.Start();}
        public void Stop() { _timer.Stop(); }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            var logCfg = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config");
            XmlConfigurator.ConfigureAndWatch(logCfg);

            HostFactory.Run(x =>
            {
                x.Service<MessagePublisher>(s =>
                {
                    s.ConstructUsing(name => new MessagePublisher());
                    s.WhenStarted(tc => tc.Start());              
                    s.WhenStopped(tc => tc.Stop());             
                });
                
                x.RunAsLocalSystem();
                x.SetDescription("Sample Topshelf Host description");
                x.SetDisplayName("Messages Display Name");
                x.SetServiceName("MessagesService");
            });
        }
    }
}

Log4Net Configuration

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>

  <log4net>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <param name="File" value="D:\App_Data\servicelog\"/>
      <param name="AppendToFile" value="true"/>
      <param name="MaxSizeRollBackups" value="10"/>
      <param name="StaticLogFileName" value="false"/>
      <param name="DatePattern" value="yyyy-MM-dd".log""/>
      <param name="RollingStyle" value="Date"/>
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n %loggername" />
      </layout>
    </appender>

    <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
      <mapping>
        <level value="ERROR" />
        <foreColor value="Red, HighIntensity" />
      </mapping>
      <mapping>
        <level value="Info" />
        <foreColor value="Green" />
      </mapping>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%n%date{HH:mm:ss,fff} [%-5level] %m" />
      </layout>

      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="Info" />
        <param name="LevelMax" value="Fatal" />
      </filter>
    </appender>

    <root>
      <level value="all" />
      <appender-ref ref="ColoredConsoleAppender"/>
      <appender-ref ref="RollingLogFileAppender"/>
    </root>
  </log4net>
</configuration>

The second pattern is recommended for most scenarios.

Tags: Topshelf windows-service csharp logging hosting-framework

Posted on Fri, 12 Jun 2026 17:40:31 +0000 by genesysmedia