温莎城堡,重写约定注册的组件



我刚开始使用温莎城堡(3.3.0),我被困在基于惯例的注册。

我想注册尽可能多的名称约定(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...

…当然,也可以稍微改变一些已注册的组件(改变生命周期管理,构造函数参数等)

因为我想保持注册尽可能简单,所以我想避免复杂的条件。我找到的唯一解决方案很简单-将自定义内容移动到名称约定之上:

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...

我现在的问题是,这是正确的方式如何解决我的注册?我可以看到温莎城堡在这方面是相当强大的,我宁愿正确地解决我的任务,不幸的是没有看到太多现实世界的例子。

一种选择是通过实现IContributeComponentModelConstruction接口来进行自定义配置—为每个组件调用ProcessModel方法:

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

在注册其他组件之前,您需要先将其注册到容器中:

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

你的最后一个注册行是一个非常广泛的,虽然它将工作在一个简单的应用程序,在大多数现实世界的应用程序可能过于简单。

通常,程序集将提供一组或多组服务。使用各种选择方法(如InNamespace、BasedOn、Where),您可以注册每组服务并配置它们的生命周期、依赖关系、命名等。我倾向于为每组服务创建一个单独的方法。(例如RegisterDataAccessComponents())非常明确地说明程序集所提供的服务集,可以使以后更容易重新访问代码,弄清楚所提供的是什么,并跟踪影响运行时行为的配置。你仍然按惯例注册,但你做得更明确一些。

为此,我发现创建负责注册和连接程序集提供的服务集的IWindsorInstaller实现也有助于将容器初始化与其他应用程序初始化任务分开。

Castle Windsor确实是一个非常强大和成熟的依赖注入平台。它有很多内部扩展,实际上允许做你的目标。请参阅此处注册工具内建的服务程序集。

我已经使用它将近5年了,对我来说下面的方法是最好的:

        // 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

然后是框架。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;
        }
    }
}

以上所有做城堡温莎已经通过模块化和可扩展的方式,而不限制城堡温莎的能力。只用几行,它就注册了整个应用程序的以下约定,并允许添加特定的约定,如:Mvc, WebApi, AutoMapper, Wcf, Quartz和其他

有几个ConfigureX方法用于此目的,即ConfigureIf或基于类型的ConfigureFor<IDummyService>

这是相关文档的链接

最新更新