Castle Windsor Configuration by Convention Proposal

Since adding convention over configuration features is the second most popular suggestion (after actually shipping) on the Castle User Voice site, I figured I’d chip in my own thoughts.  Impressive as Krzysztof’s approach is, I’d rather that we didn’t modify the runtime behaviour of Castle in order to get these kind of features.  Instead, I’d rather make the registration API more powerful.  The AllTypes code shows us a way forward on this, and the fluent NHibernate code has a good model for convention over configuration.

So, here’s a syntax that I would like to see just for auto-registration.  You’ll note it doesn’t even address the question of sub-dependency resolution, which was Krzysztof’s original focus, but I’m trying to start small here.  🙂

using System.Linq;
using System.Reflection;
using Castle.Windsor;
using NUnit.Framework;

namespace ColourCoding.ConventionConfigurationProposal {
    [TestFixture]
    public class ExampleUsage
    {
        [Test]
        public void RegisteringImplementations()
        {
            var container = new WindsorContainer();
            // Observations: we probably need some new syntax like AllTypes.  
// Sadly, we can't use AllTypes // since it's already a convention-based configuration mechanism.
var allInterfaces = Assembly.GetExecutingAssembly().GetTypes().Where(
t => t.IsInterface && !container.Kernel.HasComponent(t)); var allClasses = Assembly.GetExecutingAssembly().GetTypes(); container.Register(UseConvention .For(allInterfaces) .ImplementedBy(allClasses) ); var result = container.Resolve<ILogger>(); Assert.IsInstanceOfType(typeof(StandardLogger), result); // Convention by name Assert.IsFalse(container.Kernel.HasComponent(typeof(IService))); container.Register(UseConvention .For(allInterfaces) .ImplementedBy(allClasses) .UsingSelectionRule((i, s) => s.Name == "Default" + i.Name.Substring(1)) ); var service = container.Resolve<IService>(); Assert.IsInstanceOfType(typeof(DefaultService), service); // Multiple Registration Assert.IsFalse(container.Kernel.HasComponent(typeof(IMultipleService))); container.Register(UseConvention .For(allInterfaces) .ImplementedBy(allClasses) .AllowMultiple() ); var services = container.ResolveAll<IMultipleService>(); Assert.AreEqual(2, services.Count()); } public interface ILogger { } public class StandardLogger : ILogger { } public interface IService { } public class DefaultService : IService { } public class NonDefaultService : IService { } public interface IMultipleService { } public class Service1 : IMultipleService { } public class Service2 : IMultipleService { } } }

 

Now, there’s a couple of problems here:

  • As I’ve already said, we haven’t dealt with resolution of sub-dependencies.
  • We probably need a syntax for the allInterfaces and allClasses variables.  There’s no way you want that syntax repeated across the world.
  • We might want to create a syntax for common naming patterns as well.

You’ll note that I’ve tried to make the conventions based stuff look similar to ComponentRegistration directly.  There’s also a general hook, like in NHibernate, for intercepting the registration.  Here’s the implementation.  All of the code compiles against Castle Windsor 2.0 RTM.

using System;
using System.Collections.Generic;
using System.Linq;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;

namespace ColourCoding.ConventionConfigurationProposal
{
    public static class UseConvention {
        public static ConventionRegistration For(IEnumerable<Type> serviceTypes) {
            return new ConventionRegistration(serviceTypes);
        }
    }

    public delegate string NameRule(Type serviceType, Type implementationType);
    public delegate IEnumerable<Type> SelectionRule(Type serviceType, 
IEnumerable<Type> implementationTypeCandidates); public delegate bool SingleSelectionRule(Type serviceType, Type implementationType); public class ConventionRegistration : IRegistration { private IEnumerable<Type> serviceTypes; private IEnumerable<Type> implementationTypes; private Func<Type, Type> findImplementationRule; private NameRule nameRule; private Action<ComponentRegistration<object>> creationRules; private SelectionRule selectionRule; private SingleSelectionRule singleSelectionRule; public ConventionRegistration(IEnumerable<Type> serviceTypes) { this.serviceTypes = serviceTypes; } public IEnumerable<Type> ImplementationTypes { get { return implementationTypes; } } public IEnumerable<Type> ServiceTypes { get { return serviceTypes; } } public void Register(IKernel kernel) { var initialComponents = InitialComponents(); if (nameRule != null) { Apply(initialComponents, c => c.Named(nameRule(c.ServiceType, c.Implementation))); } if (creationRules != null) { Apply(initialComponents, creationRules); } foreach (var component in initialComponents) { kernel.Register(component); } } void Apply(IEnumerable<ComponentRegistration<object>> components,
Action<ComponentRegistration<object>> action) { foreach (var component in components) { action(component); } } IEnumerable<ComponentRegistration<object>> InitialComponents() { if (findImplementationRule == null) { return ComponentsByMatch(); } return ComponentsByFindRule(); } IEnumerable<Type> SingleOnly(Type serviceType,
IEnumerable<Type> implementationTypeCandidates) { if (implementationTypeCandidates.Count() == 1) { return implementationTypeCandidates; } return new Type[0]; } private IEnumerable<ComponentRegistration<object>> ComponentsByMatch() { var actualSelectionRule = selectionRule ?? SingleOnly; var filter = singleSelectionRule ?? new SingleSelectionRule((s, i) => true); // Yes, this code could be faster... return from serviceType in serviceTypes from implementationType in ImplementationTypes where serviceType.IsAssignableFrom(implementationType) && filter(serviceType, implementationType) group implementationType by serviceType into candidates from selectedImplementationType in actualSelectionRule(candidates.Key, candidates) select Component.For(candidates.Key)
.ImplementedBy(selectedImplementationType); } IEnumerable<ComponentRegistration<object>> ComponentsByFindRule() { foreach (var serviceType in ServiceTypes) { var implementation = findImplementationRule(serviceType); if (implementation != null) { yield return Component.For(serviceType)
.ImplementedBy(findImplementationRule(serviceType)); } } } public ConventionRegistration ImplementedBy(Func<Type, Type> findImplementation) { this.findImplementationRule = findImplementation; return this; } public ConventionRegistration ImplementedBy(IEnumerable<Type> implementationTypes) { this.implementationTypes = implementationTypes.Where(t => !t.IsInterface); return this; } public ConventionRegistration With(Action<ComponentRegistration<object>> creationRule) { this.creationRules += creationRule; return this; } public ConventionRegistration Named(NameRule nameRule) { this.nameRule = nameRule; return this; } public ConventionRegistration UsingSelectionRule(SelectionRule selection) { this.selectionRule = selection; return this; } public ConventionRegistration AllowMultiple() { if (selectionRule == null) { selectionRule = (serviceType, candidates) => candidates; } return this; } public ConventionRegistration UsingSelectionRule(SingleSelectionRule selection) { this.singleSelectionRule = selection; return this; } } }

 

Anyway, I hope this is a useful contribution to the discussion. 

Technorati Tags:

Published by

Julian Birch

Full time dad, does a bit of coding on the side.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s