Thursday, March 7, 2013

Building a Service App: Adding Windsor and TopShelf

Preface

In the last post we started with a basic exception handling clause in our console application. This time, we'll be dropping in TopShelf, Windsor, and some Logging bits. The example code is available on GitHub.

The Service Class

The service class will be very simple to start. All it will do is write a couple messages to our logger:

    public interface IExampleService
    {
        void Start();
        void Stop();
    }

    public class ExampleService : IExampleService
    {
        private readonly ILogger logger;

        public ExampleService(ILogger logger)
        {
            this.logger = logger;
        }

        public void Start()
        {
            logger.Debug("The service was started.");
        }

        public void Stop()
        {
            logger.Debug("The service was stopped.");
        }
    }

To make this class available elsewhere, we'll need to register it with our IoC container, Windsor. There are a lot of different ways to register components. This time, we'll use an installer. Installers are a convenient way to segregate the registration of your application's components.

    public class MyInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component.For<IExampleService>().ImplementedBy<ExampleService>()
                );
        }
    }


We'll need to add other components to this as things go along. But for now, it's a good start.

Updating Program.Main()

Now that we have a service class and an installer we can modify the Program.Main() method.

        static void Main()
        {
            try
            {
                var container = ContainerFactory();

                RunTheHostFactory(container);
            }
            catch (Exception exception)
            {
                var assemblyName = typeof(Program).AssemblyQualifiedName;

                if (!EventLog.SourceExists(assemblyName))
                    EventLog.CreateEventSource(assemblyName, "Application");

                var log = new EventLog { Source = assemblyName };
                log.WriteEntry(string.Format("{0}", exception), EventLogEntryType.Error);
            }
        }

The ContainerFactory() method creates a new instance of the container. It then configures the container with the appropriate services.

        private static IWindsorContainer ContainerFactory()
        {
            var container = new WindsorContainer()
                .Install(Configuration.FromAppConfig())
                .Install(FromAssembly.This());
            return container;
        }

The next method, RunTheHostFactory(), covers the bulk of the TopShelf implementation. It uses the TopShelf HostFactory static class to perform the actual work.


        private static void RunTheHostFactory(IWindsorContainer container)
        {
            HostFactory.Run(config =>
                {
                    config.Service<IExampleService>(settings =>
                        {
                            // use this to instantiate the service
                            settings.ConstructUsing(hostSettings => container.Resolve<IExampleService>());
                            settings.WhenStarted(service => service.Start());
                            settings.WhenStopped(service =>
                                {
                                    // stop and release the service, then dispose the container.
                                    service.Stop();
                                    container.Release(service);
                                    container.Dispose();
                                });
                        });

                    config.RunAsLocalSystem();

                    config.SetDescription("This is an example service.");
                    config.SetDisplayName("My Example Service");
                    config.SetServiceName("MyExampleService");
                });
        }

TopShelf's HostFactory use our container to instantiate our service. It also cleans up when the service is stopped. This is the basic TopShelf implementation. Calls to .WhenPaused(), and .WhenContinued() can be added to the HostFactory to handle when the service is paused and resumed in the service control panel.

First Run

At this point it's possible to build and install our service. A handy thing about TopShelf is that it greatly simplifies using generic services. Running our service from the Visual Studio debugger shows us we have some basic functionality (red tic marks indicate the logger entries made by the service class):

The console app being run in the debugger.

There are some other basic things we can do with a TopShelf-based application. We can run the executable as any other console app can be run:

TopShelf app being run as a console app.

Installing the service is pretty done by adding a parameter, install, to the command:

Output of the WcfExample.exe install command.

Once a service is installed, it can be started and stopped:

Example of the WcfExample.exe start command.

Example of the WcfExample.exe stop command.

Finally, the service may be uninstalled:

Example of the WcfExample.exe uninstall command.

Conclusion

We covered how to get started with TopShelf. We also looked at setting up an IoC to work with a TopShelf-based application. Next we'll look a little more at logging, and how we can use a component to configure our service.