Configuring Topshelf Using A StructureMap Container
At Dovetail we use StructureMap a lot. Lately we’ve also been using Topshelf a lot too. This post takes a look at how you can marry these two technologies together. We will be using the StrucutreMap container to configure which services get started by Topshelf at runtime.
Simple Services
Let’s start simple. We need a way to identify which configured types want to get configured to be spun up by Topshelf. To accomplish this I created an interface called ISimpleService which includes only the Start and Stop methods that Topshelf uses to manage the services lifetime.
public interface ISimpleService { void Start(); void Stop(); }
Next up we’ll update and expand the example from my previous post Controlling Application Lifetime In Topshelf to have it implement the ISimpleService. We’ll also add another service in charge of spamming the log file.
public class LogSpamService : ISimpleService { private readonly ILogger _logger; private Timer _timer; public LogSpamService(ILogger logger) { _logger = logger; } public void Start() { _timer = new Timer(SpamTheLog, this, TimeSpan.Zero, TimeSpan.FromSeconds(1)); } private void SpamTheLog(object state) { _logger.LogInfo("spam spam spam"); } public void Stop() { _timer.Dispose(); } }
Configuring Simple Services
Here comes the magic. I created a handy Topshelf configuration extension method for registering all the types in the container registered implementing the ISimpleService interface.
public static class TopShelfRunnerConfiguratorExtensions { public static void ConfigureSimpleServices(this IRunnerConfigurator configurator,
IContainer container, ILogger logger) { var simpleServiceInstances = container.Model.InstancesOf(); foreach (var simpleServiceInstance in simpleServiceInstances) { var requiredServiceType = simpleServiceInstance.ConcreteType; logger.LogDebug("Configuring runtime to manage the
lifecycle of the service {0}.", requiredServiceType); configurator.ConfigureService(service => { service.HowToBuildService(name =>
container.GetInstance(requiredServiceType)); service.WhenStarted(s => s.Start()); service.WhenStopped(s => s.Stop()); }); } } }
This extension method will be used to configure Topshelf to start up all of the ISimpleService types present. Next all that is left to do is update the configuration code to use our new extension method.
Starting Up The Application
internal class ProgramExample { private static void Main(string[] args) { Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory; var container = new Container(cfg => { cfg.AddRegistry(); cfg.Scan(scan=> { scan.TheCallingAssembly(); scan.AddAllTypesOf (); }); }); var logger = container.GetInstance (); var runConfiguration = RunnerConfigurator.New(config => { config.ConfigureSimpleServices(container, logger); }); container.Inject(runConfiguration.Coordinator); Runner.Host(runConfiguration, args); } }
First I tell StructureMap to scan the assembly and register all the ISimpleServices it finds. Then we use the Topshelf extension method to configure all the simple services. In this case we have two services present LogSpamService and the HealthMonitoringService.
Let It Rip
The application output shows the two services being started. A lot of spam being logged and eventually the health monitor shuts the application down.
Conclusion
The real power behind using an IoC container to drive the configuration of the Topshelf application host is that we can now add a service to be started at runtime by simply implementing a single interface.
A more advanced scenario in use at Dovetail is to only start up the services which are necessary. We have an application with a plug-in architecture where there may be services present in the application that are not necessary because the plug-ins that consume the output of the service are not in use.