Monday, January 11, 2010

10 Advanced Windsor Tricks – 2. Auto Registration

Here’s part two of (at least) 10 Advanced Windsor Tricks.

Today’s trick isn’t really ‘Advanced’, but it’s very cool none the less. Windsor now has a very nice fluent registration API that’s far easier to use than the old XML configuration (but don’t dismiss the XML config, it’s very useful for standard application configuration tasks as we’ll see in a later trick). With fluent configuration you can set up conventions for your application so that as you create new components you aren’t required to register them individually.

For this example, let’s say we have an interface IThing:

namespace Mike.AdvancedWindsorTricks.Model
{
    public interface IThing
    {
        string SayHello(string name);
    }
}

And two classes that implement IThing:

namespace Mike.AdvancedWindsorTricks.Model
{
    public class ThingOne : IThing
    {
        public string SayHello(string name)
        {
            return string.Format("ThingOne says hello to {0}", name);
        }
    }
}

namespace Mike.AdvancedWindsorTricks.Model
{
    public class ThingTwo : IThing
    {
        public string SayHello(string name)
        {
            return string.Format("ThingTwo says hello to {0}", name);
        }
    }
}

Our solution structure looks something like this:

AdvancedWindsorTricksModelSolution

First we can set things (ha!) up so that any class that implements IThing gets registered:

var container = new WindsorContainer()
    .Register(
        AllTypes
            .Of<IThing>()
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Configure(component => component.LifeStyle.Transient)
    );

This will register both ThingOne and ThingTwo. Note that we can also configure our components as well. Here I’m making any implementation of IThing transient.

You can also ask Windsor to register all components in a particular namespace:

var container = new WindsorContainer()
    .Register(
        AllTypes
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Where(Component.IsInNamespace("Mike.AdvancedWindsorTricks.Model"))
    );

This will also register ThingOne and ThingTwo and any other classes that have the namespace ‘Mike.AdvancedWindsorTricks.Model’.

Note that for both these examples, if you ask the container for an IThing, you will get an error saying that no component has been registered for IThing. Each component’s service is its own type by default. If you want the service to be IThing you have to specify ‘WithService.FirstInterface’ like this:

.Register(
    AllTypes
        .Of<IThing>()
        .FromAssembly(Assembly.GetExecutingAssembly())
        .Configure(component => component.LifeStyle.Transient)
        .WithService.FirstInterface()
 
We can ask Windsor to register components based on any arbitrary condition with the Where clause:
 
var container = new WindsorContainer()
    .Register(
        AllTypes
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Where(t => t.GetInterfaces().Any())
    );

The Where clause takes a Predicate<Type>, so you can use any condition of the type to register the component. Here I’ve registered any types that have at least one interface.

Finally we can mix all the above in any combination we wish:

var container = new WindsorContainer()
    .Register(
        AllTypes
            .Of<IThing>()
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Where(Component.IsInNamespace("Mike.AdvancedWindsorTricks.Model"))
            .Where(t => t.GetInterfaces().Any())
            .Configure(component => component.LifeStyle.Transient)
            .ConfigureFor<ThingTwo>(component => component.Named("thing.the.second"))
            .WithService.FirstInterface()
    );

Notice that we can also specify configuration for particular components. Here I’m setting the component name for ThingTwo as ‘thing.the.second’.

Auto registration can make development really slick. I usually have a namespace something like ‘Company.Project.Services’ and setup auto registration so that any class that implements a single interface gets registered with the interface as its service. When I want to add some new functionality, I simply a create a new service and service interface under that namespace, then consume it anywhere I like with dependency injection. It’s smoother than a cat :)

2 comments:

Frank said...

That is cool. I was thinking about adding a [RegisterFor(IService)] attribute to my project to automatically register services. Now I know how I can implement it.

Aamir Sajjad said...

nice post