Last year I wrote a post on Testing Windows Services. I pointed out that recompiling and installing your Windows Service following each change is a really inefficient way to develop and test a service. Instead, I suggest that you can run and debug your service in Visual Studio simply by wrapping the service up in a test console application.
I said that I would revisit the post for services using dependency injection, so here goes. Note that the code in this article follows on directly from the code in the previous article, so you’ll need to read the previous article in order for this to make sense!
First, a quick discussion as to how you could add dependency injection to a Windows Service:
I believe the easiest way to use dependency injection in a .NET Windows Service application is to use the dependency injection engine to inject required services into the ServiceBase.Run() method in the Program class for the service:
using System.ServiceProcess;
namespace MyService
{
public static class Program
{
public static void Main()
{
var servicesToRun = new[] { (ServiceBase)Injector.Instance.Resolve() };
ServiceBase.Run(servicesToRun);
}
}
}
In order to do this, you simply need to create an empty interface for your service, which the service implements:
namespace MyService
{
public interface IService
{
// No implementation
}
}
using System.IO;
using System.ServiceProcess;
namespace MyService
{
public partial class Service : ServiceBase, IService
{
public Service()
{
InitializeComponent();
}
// Service implementation...
}
}
My original article gave an example of a service which creates and deletes a file when it starts and stops. To complete the example, let’s extract the file interaction code out as a dependency. Here’s the interface:
namespace MyService
{
public interface IFileCreator
{
void Create();
void Delete();
}
}
…and here’s the implementation:
using System.IO;
namespace MyService
{
public class FileCreator : IFileCreator
{
public void Create()
{
using (var stream = new FileStream("Running", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
using (var writer = new StreamWriter(stream))
{
writer.WriteLine("Running");
writer.Flush();
}
}
}
public void Delete()
{
var fileInfo = new FileInfo("Running");
if (fileInfo.Exists)
{
fileInfo.Delete();
}
}
}
}
The injector class for the service would then look something like this:
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Installer;
namespace MyService
{
public static class Injector
{
private static readonly object InstanceLock = new object();
private static IWindsorContainer instance;
public static IWindsorContainer Instance
{
get
{
lock (InstanceLock)
{
return instance ?? (instance = GetInjector());
}
}
}
private static IWindsorContainer GetInjector()
{
var container = new WindsorContainer();
container.Install(FromAssembly.This());
RegisterInjector(container);
RegisterService(container);
RegisterFileCreator(container);
return container;
}
private static void RegisterInjector(WindsorContainer container)
{
container.Register(
Component.For()
.Instance(container));
}
private static void RegisterService(WindsorContainer container)
{
container.Register(
Component.For()
.ImplementedBy(typeof(Service)));
}
private static void RegisterFileCreator(WindsorContainer container)
{
container.Register(
Component.For()
.ImplementedBy(typeof(FileCreator)));
}
}
}
Note that I’ve wired up both the service (using IService) and my new FileCreator class.
The ServiceWrapper class would be exactly as in the previous example. The only change to the wrapper console application would be to change the Program, as follows:
using System;
using MyService;
namespace MyServiceWrapper
{
public class Program
{
public static void Main(string[] args)
{
var serviceWrapper = new ServiceWrapper();
// Wire up service dependencies here:
serviceWrapper.FileCreator = Injector.Instance.Resolve();
try
{
serviceWrapper.TestStart();
Console.ReadLine();
serviceWrapper.TestStop();
}
catch (Exception exception)
{
Console.WriteLine("Error: " + exception.Message);
Console.ReadLine();
}
}
}
}
Unfortunately, in this class I’ve had to wire up all the dependencies in the ServiceWrapper manually as I haven’t found a way to do this automatically. However, this one small pain point is a significant improvement compared to compiling and installing your service each time you make a change!