geek yapping

Twitter












Sexy Windsor Registrations

After working with FluentNHibernate and seeing examples of registries in StructureMap, I started craving the same thing for my registrations with Windsor. Our registrations often look like the following:
public static void Register(IWindsorContainer container)
{
	container.Register(Component.For<IFoo>().ImplementedBy<Foo>());
	container.AddComponent<IFoo, Foo>();
	...
}

There are a few things I don’t like about this approach:

  1. Passing a container around through static methods is a hack.
  2. Ceremony of “container.” calls clutter the registry and impede readability.
  3. Why do I need so much ceremony to get to Component.For? “container.Register(Component.For” is tedious!
Note: this code is available on github. The registries are tested with nunit, so you can drop in whatever version of windsor and verify it works.

IWindsorInstaller to the static rescue

A registry needs a uniform entry point. Enter IWindsorInstaller, a rather undocumented feature that deserves more attention.

	public interface IWindsorInstaller
	{
		void Install(IWindsorContainer container, IConfigurationStore store);
	}

	// Container entry point
	container.Install(IWindsorInstaller installer);

 

The container has an install method that takes an instance of IWindsorInstaller. See this post for more details about IWindsorInstaller.

Adapting to Component.For

Now to fix readability, what if we could:
	public class SampleRegistry : RegistryBase
	{
		public SampleRegistry()
		{
			For<IFoo>().ImplementedBy<Foo>().LifeStyle.Singleton();
			For<IFoo>().ImplementedBy<Foo>();
		}
	}
To pull this off, the RegistryBase keeps a collection of registrations and adapts to the Component.For entry point to capture the registration before returning it. These registrations are stored in a Steps collection, more on why this isn't called Registrations later.
	public class RegistryBase : IWindsorInstaller
	{
		...

		public ComponentRegistration<S> For<S>()
		{
			var registration = Component.For<S>();
			Steps.Add(registration);
			return registration;
		}

		public ComponentRegistration<S> For<S, F>()
		{
			var registration = Component.For<S, F>();
			Steps.Add(registration);
			return registration;
		}

		public ComponentRegistration<S> For<S, F1, F2>()
		{
			var registration = Component.For<S, F1, F2>();
			Steps.Add(registration);
			return registration;
		}

		public ComponentRegistration<S> For<S, F1, F2, F3>()
		{
			var registration = Component.For<S, F1, F2, F3>();
			Steps.Add(registration);
			return registration;
		}

		public ComponentRegistration<S> For<S, F1, F2, F3, F4>()
		{
			var registration = Component.For<S, F1, F2, F3, F4>();
			Steps.Add(registration);
			return registration;
		}

		public ComponentRegistration For(params Type[] types)
		{
			var registration = Component.For(types);
			Steps.Add(registration);
			return registration;
		}
		...
	}

Installation

RegistryBase implements the IWindsorInstaller.Install method to add registrations to the container.
	public virtual void Install(IWindsorContainer container, IConfigurationStore store)
	{
		Steps.ForEach(s => container.Register(s));
	}
The application bootstrapper adds registries. This example assumes all registries are loaded into the container, though they probably would never have dependencies (chicken/egg paradox). I just like to abuse my container :)
	private static void LoadRegistries(IWindsorContainer container)
	{
		var registries = container.ResolveAll<IWindsorInstaller>();
		registries.ForEach(r => container.Install(r));
	}

Other adaptations

The registry also adapts to a few other entry points and captures their registrations.
  1. container.AddComponent
  2. container.AddFacility
  3. AllTypes.FromAssemblyNamed
  4. AllTypes.FromAssembly
  5. AllTypes.FromAssemblyContaining

Adapting to the unknown

In the event there is an entry point missing from the registry, it has a Custom method that takes an Action. This allows for access to the container as usual. This is captured as a deferred action that won't be executed until the registry is installed in the container. Hence the name "Steps" for registrations and custom actions.

Show me the money

Here is a sample of different useages of the registry, of course the entire fluent registration API is at your finger tips.
	public class SampleRegistry : RegistryBase
	{
		public SampleRegistry()
		{
			// Register a singleton
			For<IFoo>().ImplementedBy<Foo>().LifeStyle.Singleton(); // Extension methods to call property.

			// Register a single item
			For<IFoo>().ImplementedBy<Foo>();
			For(typeof (IFoo)).ImplementedBy<Foo>();
			AddComponent<IFoo, Foo>();

			// Custom actions if you want to access the original container API, with deferred installation via lambda expressions
			Custom(c => c.AddComponent<IFoo, Foo>());
			Custom(c => c.Register(Component.For<IFoo>().ImplementedBy<Foo>()));

			// Scan for types
			FromAssemblyContaining<SampleRegistry>().BasedOn<IFoo>();
			FromAssemblyContaining(typeof (SampleRegistry)).BasedOn<IFoo>();
			FromAssemblyNamed("GotFour.Windsor.Tests").BasedOn<IFoo>();
			FromAssembly(typeof (SampleRegistry).Assembly).BasedOn<IFoo>();

			// Forwarding types
			For<IFoo, Foo>().ImplementedBy<Foo>();
			For<IFoo, Foo, FooBar>().ImplementedBy<FooBar>();
			For<IFoo, Foo, FooBar, FooBar2>().ImplementedBy<FooBar2>();
			For<IFoo, Foo, FooBar, FooBar2, FooBar3>().ImplementedBy<FooBar3>();

			// Adding facilities
			AddFacility<StartableFacility>();
		}
	}

Notes: I have tested capturing registrations for all of the above scenarios but I suppose there might be some deep dark portion of the registration API that might not work. This would happen if something creates a brand new registration, independent of the original captured one. I have yet to run into this, the design of the api is pretty rock solid as a builder that collects state. I left out AllTypes.Of.From since Of doesn't return a registration, it is simply a call to AllTypes.FromAssemblyXyz().BasedOn() reversed and really isn't very helpful.

ExtendedRegistryBase : RegistryBase

I added an extended set of registration points with ExtendedRegistryBase. This adds another layer of new fluent registrations for common scenarios, often involving convention based registration :) If you have additions, please add them in the comments and I will get them added.
	public class SampleExtendedRegistry : ExtendedRegistryBase
	{
		public SampleExtendedRegistry()
		{
			// Same as scanning above in SampleRegistry but much cleaner!
			ScanMyAssemblyFor<IFoo>();

			// Scan for all services of the pattern Service : IService
			ScanMyAssembly(Conventions.FirstInterfaceIsIName);

			// Scan for all services of the pattern Whatever : IService (register with first interface)
			ScanMyAssembly(Conventions.FirstInterface);

			// Next we could use some attributes to discover services, to register imports / exports :)
		}
	}
This registry class helps avoid the static calls to registries from my applications. Now I can scan for registries of a known type and install them into the container. The registries are much more readable with the ceremony gone. I know there was talk of adding something like this to the next version of Windsor/MicroKernel. I hope this is the direction that effort is headed towards. In the mean time enjoy this as a fix to the cravings for a cleaner registry. I typically add one of these per project and let it control registration within that layer.
Shout it kick it on DotNetKicks.com

-Wes



Feedback

# re: Sexy Windsor Registrations

Cool stuff, was going to write something of this kind, you've saved me a day or two :)

One note :
LoadRegistries method does not look to be a good idea to me. All extensions e.g. facilities should be registered before ordinary components. Using this approach beaks this constraint.





3/18/2010 6:31 AM | Konstantin

# re: Sexy Windsor Registrations

Yeah, you might want to extend my registry and add some sort of priority to the order of installing them. To be honest, I'm not sure I like the registry adding facilities. In our apps we wrap our container with a class that adds facilities/subresolvers etc when it is instantiated and registries are only for components. 3/18/2010 1:34 PM | Wes

# re: Sexy Windsor Registrations

Exactly facilities should not be rigistered in such way.

I think it makes sense to replace IWindsorContainer inheritance with some custom interface (single method GetRegistrations) and wrap it with implementation of IWindserInstaller at run time.

Something like



private static void LoadRegistries(IWindsorContainer container)
{
var registries = container.ResolveAll<IWindsorInstaller>();
registries.ForEach(r => container.Install(new RegistryInstaller(r)));
}
3/23/2010 6:40 AM | Konstantin