Thursday, 25 October 2012

Building Windows Services with TopShelf

TopShelf is a framework you can use to build windows services which can be installed easily. It is used by such famous products as NServiceBus. It allows you to create windows services that you don't have to install with "installutil.exe". The windows service you create is self installing.

When using TopShelf, you install your windows service by running the following command:

    Acme.TopShelfExample.Windows.Service.exe install

Which would result in the following console output:


    Configuration Result:
    [Success] Name ExampleWorker
    [Success] DisplayName Example Worker
    [Success] Description An Example Worker.
    [Success] ServiceName ExampleWorker
    Topshelf v3.0.105.0, .NET Framework v4.0.30319.269

    Running a transacted installation.

    Beginning the Install phase of the installation.
    Installing Example Worker service
    Installing service ExampleWorker...
    Service ExampleWorker has been successfully installed.
    Creating EventLog source ExampleWorker in log Application...

    The Install phase completed successfully, and the Commit phase is beginning.

    The Commit phase completed successfully.

    The transacted install has completed.


Which is pretty much the same as you get using "installutil.exe".

To uninstall, run the following:

    Acme.TopShelfExample.Windows.Service.exe uninstall

Which would result in the following console output:


    Configuration Result:
    [Success] Name ExampleWorker
    [Success] DisplayName Example Worker
    [Success] Description An Example Worker.
    [Success] ServiceName ExampleWorker
    Topshelf v3.0.105.0, .NET Framework v4.0.30319.269


    The uninstall is beginning.
    Uninstalling ExampleWorker service
    Removing EventLog source ExampleWorker.
    Service ExampleWorker is being removed from the system...
    Service ExampleWorker was successfully removed from the system.

    The uninstall has completed.


Again, the same as you'd get using "installutil.exe".

To use TopShelf for your windows service, create a console application and install TopShelf using NuGet:

    install-package TopShelf

The following code should get you started:


namespace Acme.TopShelfExample.Windows.Service
{
    using Topshelf;
 
    internal class Program
    {
        internal static void Main(string[] args)
        {
            HostFactory.Run(hostConfigurator =>
            {
                hostConfigurator.Service<ExampleWorker>(serviceConfigurator =>
                {
                    serviceConfigurator.ConstructUsing(name => new ExampleWorker());
                    serviceConfigurator.WhenStarted(ew => ew.Start());
                    serviceConfigurator.WhenStopped(ew =>
                        {
                            ew.Stop();
 
                            // And dispose or release any component containers (e.g. Castle) 
                            // or items resolved from the container.
                        });
                });
                hostConfigurator.RunAsLocalSystem();
                hostConfigurator.SetDescription("An Example Worker.");
                hostConfigurator.SetDisplayName("Example Worker");
                hostConfigurator.SetServiceName("ExampleWorker"); // No spaces allowed
                hostConfigurator.StartAutomatically();
            });
        }
    }
}



namespace Acme.TopShelfExample.Windows.Service
{
    using System;
    using System.Globalization;
    using System.Threading;
 
    public abstract class Worker
    {
        private Thread thread;
 
        private bool stop = true;
 
        protected Worker()
        {
            this.SleepPeriod = new TimeSpan(0, 0, 0, 10);
            this.Id = Guid.NewGuid();
        }
 
        public TimeSpan SleepPeriod { getset; }
 
        public bool IsStopped { getprivate set; }
 
        protected Guid Id { getprivate set; }
 
        public void Start()
        {
            string logMessage = string.Format(CultureInfo.CurrentCulture, "Starting worker of type '{0}'."this.GetType().FullName);
            System.Diagnostics.Debug.WriteLine(logMessage);
            this.stop = false;
 
            // Multiple thread instances cannot be created
            if (this.thread == null || this.thread.ThreadState == ThreadState.Stopped)
            {
                this.thread = new Thread(this.Run);
            }
 
            // Start thread if it's not running yet
            if (this.thread.ThreadState != ThreadState.Running)
            {
                this.thread.Start();
            }
        }
 
        public void Stop()
        {
            string logMessage = string.Format(CultureInfo.CurrentCulture, "Stopping worker of type '{0}'."this.GetType().FullName);
            System.Diagnostics.Debug.WriteLine(logMessage);
            this.stop = true;
        }
 
        protected abstract void DoWork();
 
        private void Run()
        {
            try
            {
                try
                {
                    while (!this.stop)
                    {
                        this.IsStopped = false;
                        this.DoWork();
                        Thread.Sleep(this.SleepPeriod);
                    }
                }
                catch (ThreadAbortException)
                {
                    Thread.ResetAbort();
                }
                finally
                {
                    this.thread = null;
                    this.IsStopped = true;
                    string logMessage = string.Format(CultureInfo.CurrentCulture, "Stopped worker of type '{0}'."this.GetType().FullName);
                    System.Diagnostics.Debug.WriteLine(logMessage);
                }
            }
            catch (Exception e)
            {
                string exceptionMessage = string.Format(CultureInfo.CurrentCulture, "Error running the '{0}' worker."this.GetType().FullName);
                System.Diagnostics.Debug.WriteLine(exceptionMessage, e);
                throw;
            }
        }
    }
}



namespace Acme.TopShelfExample.Windows.Service
{
    using System.Diagnostics;
 
    public class ExampleWorker : Worker
    {
        protected override void DoWork()
        {
            Debug.WriteLine("Example Worker is doing something.");
        }
    }
}


And that is it.

About Me