Here’s part five of (at least) 10 Advanced Windsor Tricks.
Sometimes you may want a service instance to last the lifetime of your application. You might, for example, need to have a file watcher or maybe a service publishing events. We’ll see a good example of the later when we look at registering events on components. You could of course simply resolve a singleton instance when your application starts and call any start code on it, but there is a more elegant way of achieving the same goal: the Startable Facility.
The startable facility simply hooks into the ComponentModelCreated and ComponentRegistered events on the Kernel, looks for any component marked as startable, and then creates an instance and calls a start method as soon as all its dependencies can be resolved. It’s in the Castle.MicroKernel assembly, so you don’t have to add any new references to your project to use it.
You have two options to mark a component as startable, either have your component implement IStartable, or use configuration. The interface option doesn’t require any further configuration, but the second option allows you to start up components from 3rd parties and also allows you to keeps your classes free of Windsor dependencies.
Let’s look at an interface based startable component first. IStartable looks like this:
public interface IStartable { void Start(); void Stop(); }
Here’s a startable component called StartableThing:
public class StartableThing : IThing, IStartable { public void Start() { Console.WriteLine("Starting"); } public void Stop() { Console.WriteLine("Stopping"); } public string SayHello(string name) { return string.Format("Hello, {0}, from StartableThing", name); } }
To use StartableThing, simply register it like any other component. Note that we’ve also added the StartableFacility:
var container = new WindsorContainer() .AddFacility<StartableFacility>() .Register( Component.For<IThing>().ImplementedBy<StartableThing>() ); Console.WriteLine("After container registration, before resolving IThing"); var thing = container.Resolve<IThing>(); Console.WriteLine(thing.SayHello("Jack")); container.Dispose();
This code will print out:
Starting After container registration, before resolving IThing Hello, Jack, from StartableThing Stopping
As you can see, the component starts up at registration. It’s also worth noting that the ‘Start’ method is called in-process with the container registration, so you need to be careful not to block for long periods. Typically you would configure a startable component as singleton which means that at any point you can resolve it and examine or alter its state. Its ‘Stop’ method is called when the container is disposed.
Let’s have a look at a non-interface startable now. I’ve unimaginatively called it ‘NonInterfaceStartableThing’:
public class NonInterfaceStartableThing : IThing { public void SomeStartMethod() { Console.WriteLine("Starting"); } public void SomeStopMethod() { Console.WriteLine("Stopping"); } public string SayHello(string name) { return string.Format("Hello, {0}, from NonInterfaceStartableThing", name); } }
Note that this class does not implement IStartable, instead we have to explicitly tell the startable facility what the start and stop methods are. The registration looks like this:
var container = new WindsorContainer() .AddFacility<StartableFacility>() .Register( Component.For<IThing>().ImplementedBy<NonInterfaceStartableThing>() .StartUsingMethod("SomeStartMethod") .StopUsingMethod("SomeStopMethod") ); Console.WriteLine("After container registration, before resolving IThing"); var thing = container.Resolve<IThing>(); Console.WriteLine(thing.SayHello("Jack")); container.Dispose();
As you can see the Startable Facility is a very useful addition to your IoC toolbox.
2 comments:
I use a similar feature in a homebrew Ioc contrainer I use for Lua programming. One thing though, what about when you run your unit tests? At least for my project, the IoC registrations still happen, but I suppress the startup event as it would cause trouble. Does Windsor let you suppress the startup event, or do you just not register your services when unit testing?
Hi Frank,
I don't usually use a container in my unit tests, since they're unit tests, not integration tests. I usually have some tests around registration, but you can factor out adding facilities so that you can test registration without having to start anything.
Post a Comment