简体   繁体   English

温莎城堡,超越公约注册的组件

[英]Castle Windsor, overriding convention-registered components

I just started using Castle Windsor ( 3.3.0 ) for the very first time and I got stuck on convention based registration. 我刚开始第一次使用Castle Windsor( 3.3.0 ),但是我坚持使用基于约定的注册。

I would like to register as much as possible by name convention ( IDummyService -> DummyService ): 我想通过名称约定( 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. 我可以看到温莎城堡在这一领域非常强大,并且宁愿妥善解决我的任务,但不幸的是,看不到很多真实的例子。

One option would be to do the custom configuration by implementing the IContributeComponentModelConstruction interface - the ProcessModel method is called for each component: 一种选择是通过实现IContributeComponentModelConstruction接口来进行自定义配置-为每个组件调用ProcessModel方法:

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());

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. 使用各种选择方法(例如InNamespace,BasedOn,Where),您可以注册每组服务并配置它们的生命周期,依赖项,命名等。我倾向于为每组服务创建一个单独的方法。 (eg 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. (例如RegisterDataAccessComponents() )非常清楚地说明了程序集提供的服务集,这使得以后重新访问代码,弄清所提供的内容以及跟踪影响运行时行为的配置变得容易得多。 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. 为此,我发现创建负责注册和连接程序集提供的服务集的IWindsorInstaller实现也有助于将容器初始化与其他应用程序初始化任务分开。

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: 我已经使用了将近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

And then for Framework.InversionOfControl: 然后对于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 它只需几行代码即可按照约定注册整个应用程序,并允许添加特定的约定,例如:Mvc,WebApi,AutoMapper,Wcf,Quartz等

There are a few ConfigureX methods for that purpose, namely ConfigureIf or type based ConfigureFor<IDummyService> . 为此目的,有一些ConfigureX方法,即ConfigureIf或基于类型的ConfigureFor<IDummyService>

Here's the link to the relevant documentation. 这是相关文档的链接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM