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