0
votes

I just started using Castle Windsor (3.3.0) for the very first time and I got stuck on convention based registration.

I would like to register as much as possible by name convention (IDummyService -> DummyService):

var container = new WindsorContainer();
container.Register(Types.FromThisAssembly().Pick().WithServiceDefaultInterfaces());
container.Register(Component.For<IDummyService>().ImplementedBy<DummyService>().LifestyleSingleton()); // So long, I'm throwing here...

... but of course to be able to change some of just registered components little bit (changing life time management, constructor parameters etc.)

Because I want to keep registration as simple as it could be, I would like to avoid complex conditions. Only solution I found is simple - to move custom stuff above name convention:

var container = new WindsorContainer();
container.Register(Component.For<IDummyService>().ImplementedBy<DummyService>().LifestyleSingleton()); // Do custom stuff first...
container.Register(Types.FromThisAssembly().Pick().WithServiceDefaultInterfaces()); // And convention at the end...

My question is now, is this right way how to solve my registration? I can see Castle Windsor is quite mighty in this area and would rather solve my task properly, unfortunately don't see much real-world examples.

4

4 Answers

1
votes

One option would be to do the custom configuration by implementing the IContributeComponentModelConstruction interface - the ProcessModel method is called for each component:

public class ExtraConfiguration : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        if (model.Implementation == typeof(DummyService))
        {
            model.LifestyleType = LifestyleType.Singleton;
        }

        if ...
    }
}

You would then need to register this with the container prior to registering the other components:

container.Kernel.ComponentModelBuilder.AddContributor(new ExtraConfiguration());
1
votes

Your last registration line is an extremely broad one and whilst it will work in a simple application, in most real-world apps is probably too simplistic.

Typically an assembly will provide a one or more sets of services. Using the various selection methods (such as InNamespace, BasedOn, Where) you can register each set of services and configure their lifecycles, dependencies, naming, etc. I tend to create a separate method for each set of services. (e.g. RegisterDataAccessComponents()) Being very explicit about the sets of services being contributed by an assembly makes it much easier revisit the code later and figuring out what is being provided and track down the configuration that affect run-time behaviour. You are still registering by convention but you are doing it a little more explicitly.

To that end, I find that creating IWindsorInstaller implementations that take responsibility for registering and wiring the sets of services offered by an assembly also helps with separating the container initialization from other application initialization tasks.

1
votes

Castle Windsor is indeed very mighty and mature platform for dependency injection. It has lots of internal extensions that actually allows to do what you are aiming for. See here registering services assembly built in tool.

I have been using it for almost 5 years now and for me the following works the best way:

        // at the startup of the application
        _container = (new WindsorContainer()
            .AddHelperFacilities() // IWindsorContainer extension that comes from Framework.InversionOfControl 
            .AddWebApiAdapter() // IWindsorContainer extension that comes from Framework.InversionOfControl.WebApi 
            .InitializeDomainUsingConventions(  // IWindsorContainer extension that comes from Framework.InversionOfControl
                AppDomain.CurrentDomain, // domain for which container will be building registrations
                "ApplicationName.*", // regext to speed up registration process by processing only services from application namespace
                new WebApiControllersRegistrationConvention(), new DefaultRegistrationConvention())); // one or more conventions 
    // DefaultRegistrationConvention() comes from Framework.InversionOfControl
    // WebApiControllersRegistrationConvention() comes from Framework.InversionOfControl.WebApi . A separate assembly to be referenced to avoid extra dependancies on Asp.NET WebApi assemblies
            .Resolve<IApplication>.Start(); // resolves application specific entry point and launches the application

And then for Framework.InversionOfControl:

namespace Framework.InversionOfControl
{
    public static class WindowsContainerExtensions
    {
        public static IWindsorContainer InitializeDomainUsingConventions(
            this IWindsorContainer container, AppDomain appDomain, string commonNamespaceDenominatorMask, params IRegistrationConvention[] registrationConventions)
        {
            var assembliesToInitialize = new List<Assembly>();
            var runtimeAssemblies = new List<Assembly> { Assembly.GetCallingAssembly() };
            var processedAssemblies = new List<Assembly>();
            runtimeAssemblies.AddRange(appDomain.GetAssemblies());
            foreach (var assembly in runtimeAssemblies)
            {
                ProcessAssembly(assembly, assembliesToInitialize, processedAssemblies, commonNamespaceDenominatorMask, commonNamespaceDenominatorMask == null);
            }
            var allRuntimeTypes = new List<Type>();
            foreach (var assembly in assembliesToInitialize)
            {
                var assemblyTypes = assembly.GetTypes().ToList();
                var installerTypes = assemblyTypes.Where(t => !t.IsInterface && !t.IsAbstract && t.GetInterfaces().Contains(typeof(IWindsorInstaller))).ToArray();
                if (installerTypes.Any())
                {
                    foreach (var installer in installerTypes.Select(installerType => (IWindsorInstaller)Activator.CreateInstance(installerType)))
                    {
                        container.Install(installer);
                    }
                }
                else
                {
                    allRuntimeTypes.AddRange(assemblyTypes);
                }
            }
            foreach (var registrationConvention in registrationConventions)
            {
                registrationConvention.RegisterTypesUsingConvention(container, allRuntimeTypes);
            }
            return container;
        }

        private static void ProcessAssembly(Assembly assembly, List<Assembly> assemblies, List<Assembly> processedAssemblies, string commonNamespaceDenominatorMask, bool fullScan)
        {
            if (processedAssemblies.Any(x => x.FullName == assembly.FullName)) return;
            if (assembly == typeof(WindowsContainerExtensions).Assembly) return;
            processedAssemblies.Add(assembly);
            var initialize = (new Regex(commonNamespaceDenominatorMask, RegexOptions.IgnoreCase)).Match(assembly.FullName).Success;
            if (initialize && assemblies.Any(x => x.FullName == assembly.FullName))
            {
                initialize = false;
            }
            if (initialize)
            {
                assemblies.Add(assembly);
            }

            foreach (var referencedAssembliyNames in assembly.GetReferencedAssemblies())
            {
                var referencedAssembliyNames1 = referencedAssembliyNames;
                if (assemblies.Any(x => x.FullName == referencedAssembliyNames1.FullName)) continue;
                if (fullScan == false && (new Regex(commonNamespaceDenominatorMask, RegexOptions.IgnoreCase)).Match(assembly.FullName).Success == false) continue;
                Assembly referencedAssembliy;
                try
                {
                    referencedAssembliy = Assembly.Load(referencedAssembliyNames);
                }
                catch 
                {
                    continue;
                }
                ProcessAssembly(referencedAssembliy, assemblies, processedAssemblies, commonNamespaceDenominatorMask, fullScan);
            }
        }

        public static IWindsorContainer AddHelperFacilities(this IWindsorContainer container)
        {
            container.AddFacility<TypedFactoryFacility>();

            container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));

            container.Register(Component.For<IWindsorContainer>().ImplementedBy<WindsorContainer>());
            container.Register(Component.For<IContainerAccessor>().ImplementedBy<ContainerAccessor>());
            container.Resolve<IContainerAccessor>().Container = container;

            return container;
        }
    }

    public interface IRegistrationConvention
    {
        IWindsorContainer RegisterTypesUsingConvention(IWindsorContainer container, List<Type> assemblyTypes);
    }

    public class DefaultRegistrationConvention : IRegistrationConvention
    {
        /// <summary>
        /// Register every service possible from the calling assembly with default singleton lifestyle
        /// with the exception of ISomething Factory where the the ISomething GetSomething() where
        /// Something that implements ISomething is registered with transient lifestyle
        /// </summary>
        public IWindsorContainer RegisterTypesUsingConvention(IWindsorContainer container, List<Type> assemblyTypes)
        {
            // Step 1: Factories installation.
            // We register interfaces ending 'Factory' keyword like proxy (implementionless) factories.
            var factoryServices = new List<Type>();
            var factorySelector = new FullNameFactorySelector();
            foreach (var factoryType in assemblyTypes.Where(t => t.Name.EndsWith("Factory") && t.IsInterface))
            {
                foreach (var method in factoryType.GetMethods())
                {
                    if (method.Name.StartsWith("Get") == false) continue;
                    if (method.ReturnType.IsInterface == false) continue;
                    factoryServices.Add(method.ReturnType);
                }

                container.Register(Component.For(factoryType).AsFactory(factorySelector));
            }

            // Step 2: Rest of the services registrations
            // transientServices list is populated with services that needs to has transient lifespan
            // everything else needs to go as preconfigured lifestyle - lifeStyleType
            var components = assemblyTypes.Where(t => !t.IsInterface && !t.IsAbstract);
            foreach (var component in components)
            {
                // for every interface and implementation do registration
                foreach (var service in component.GetInterfaces())
                {
                    IRegistration registration;
                    Type service1 = service;
                    if (factoryServices.Any(x => x.FullName == service1.FullName))
                    {
                        if (component.IsGenericType)
                        {
                            // GetInterfaces() and GetMethod().ReturnType both returns Type.FullName = null
                            // Castle.Windsor fails to Resolve later generic types if registered type is with FullName = null,
                            // Workaround is to find the type with correct FullName from the 'assemblyTypes'
                            var serviceWithFullName = assemblyTypes.FirstOrDefault(x => x.Name == service1.Name);
                            if (serviceWithFullName == null) continue; // if null then the mapping is not supported by this convention
                            registration = Component.For(serviceWithFullName)
                                .ImplementedBy(component)
                                .LifestyleTransient()
                                .Named(serviceWithFullName.FullName + " / " + component.FullName);
                        }
                        else
                        {
                            registration = Component.For(service)
                                .ImplementedBy(component)
                                .LifestyleTransient()
                                .Named(service.FullName + " / " + component.FullName);
                        }
                    }
                    else
                    {
                        registration = Component.For(service)
                            .ImplementedBy(component)
                            .Named(service.FullName + " / " + component.FullName)
                            .LifeStyle.Is(LifestyleType.Singleton);

                    }
                    container.Register(registration);
                }
            }

            return container;
        }
    }
}

All of the above does what Castle Windsor is doing it already by in modular and extensible way without limiting out the Castle Windsor capabilities. With few lines it register the entire application following conventions and allows adding of specific conventions like for: Mvc, WebApi, AutoMapper, Wcf, Quartz and others

0
votes

There are a few ConfigureX methods for that purpose, namely ConfigureIf or type based ConfigureFor<IDummyService>.

Here's the link to the relevant documentation.