Замок Виндзор, переопределяющий зарегистрированные компоненты

Я только начал использовать замок Виндзор (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...

Мой вопрос сейчас, это правильный путь, как решить мою регистрацию? Я вижу, что Виндзорский замок достаточно силен в этой области и скорее решит мою задачу должным образом, к сожалению, я не вижу много реальных примеров.

4 ответа

Замок Виндзор действительно очень могучая и зрелая платформа для внедрения зависимостей. Он имеет множество внутренних расширений, которые фактически позволяют делать то, к чему вы стремитесь. Смотрите здесь регистрацию сервисов сборки встроенного инструмента.

Я использую его уже почти 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

А затем для 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;
        }
    }
}

Все вышеперечисленное делает то, что Castle Windsor делает уже модульным и расширяемым способом, не ограничивая возможности Castle Windsor. С помощью нескольких строк он регистрирует все приложение в соответствии с соглашениями и позволяет добавлять определенные соглашения, такие как: Mvc, WebApi, AutoMapper, Wcf, Quartz и другие.

Ваша последняя строка регистрации чрезвычайно широка, и хотя она будет работать в простом приложении, в большинстве реальных приложений она, вероятно, слишком упрощена.

Обычно сборка будет предоставлять один или несколько наборов сервисов. Используя различные методы выбора (например, InNamespace, BasedOn, Where), вы можете зарегистрировать каждый набор служб и настроить их жизненные циклы, зависимости, наименования и т. Д. Я склонен создавать отдельный метод для каждого набора служб. (например RegisterDataAccessComponents()Очень ясное представление о наборах сервисов, предоставляемых сборкой, значительно упрощает повторное рассмотрение кода позже и выяснение того, что предоставляется, и отслеживание конфигурации, влияющей на поведение во время выполнения. Вы все еще регистрируетесь по соглашению, но делаете это немного более явно.

С этой целью я считаю, что создание реализаций IWindsorInstaller, которые берут на себя ответственность за регистрацию и подключение наборов сервисов, предлагаемых сборкой, также помогает отделить инициализацию контейнера от других задач инициализации приложения.

Одним из вариантов будет сделать пользовательскую конфигурацию путем реализации 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());

Есть несколько ConfigureX методы для этой цели, а именно ConfigureIf или тип на основе ConfigureFor<IDummyService>,

Вот ссылка на соответствующую документацию.

Другие вопросы по тегам