Unity Framework DependencyAttribute работает только для открытых свойств? - PullRequest
9 голосов
/ 23 января 2009

Я пытался очистить некоторые элементы доступности в моем коде и случайно сломал внедрение зависимостей Unity. Через некоторое время я понял, что пометил некоторые общедоступные свойства, которые на самом деле я не хотел, чтобы их внутренние библиотеки были доступны для внутреннего использования. Затем я начал получать исключения.

Так что кажется, что использование атрибута [Dependency] в Unity работает только для открытых свойств. Я полагаю, что это имеет смысл, поскольку внутренний и закрытый реквизиты не будут видны сборке Unity, но чувствует себя очень грязно , чтобы иметь кучу открытых свойств, которые вы никогда не хотите, чтобы кто-то устанавливал или быть в состоянии установить, кроме Unity.

Есть ли способ позволить единице установить внутренние или частные свойства?

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

    [TestFixture]
public class UnityFixture
{
    [Test]
    public void UnityCanSetPublicDependency()
    {
        UnityContainer container = new UnityContainer();
        container.RegisterType<HasPublicDep, HasPublicDep>();
        container.RegisterType<TheDep, TheDep>();

        var i = container.Resolve<HasPublicDep>();
        Assert.IsNotNull(i);
        Assert.IsNotNull(i.dep);
    }

    [Test]
    public void UnityCanSetInternalDependency()
    {
        UnityContainer container = new UnityContainer();
        container.RegisterType<HasInternalDep, HasInternalDep>();
        container.RegisterType<TheDep, TheDep>();

        var i = container.Resolve<HasInternalDep>();
        Assert.IsNotNull(i);
        Assert.IsNotNull(i.dep);
    }

    [Test]
    public void UnityCanSetPrivateDependency()
    {
        UnityContainer container = new UnityContainer();
        container.RegisterType<HasPrivateDep, HasPrivateDep>();
        container.RegisterType<TheDep, TheDep>();

        var i = container.Resolve<HasPrivateDep>();
        Assert.IsNotNull(i);
        Assert.IsNotNull(i.depExposed);
    }
}

public class HasPublicDep
{
    [Dependency]
    public TheDep dep { get; set; }
}

public class HasInternalDep
{
    [Dependency]
    internal TheDep dep { get; set; }
}

public class HasPrivateDep
{
    [Dependency]
    private TheDep dep { get; set; }

    public TheDep depExposed
    {
        get { return this.dep; }
    }
}

public class TheDep
{
}

Обновлен:

Я заметил стек вызовов для установки свойства, передаваемого из:

UnityCanSetPublicDependency()
--> Microsoft.Practices.Unity.dll
--> Microsoft.Practices.ObjectBuilder2.dll
--> HasPublicDep.TheDep.set()

Итак, пытаясь хотя бы заставить работать внутреннюю версию, я добавил их в свойства моей сборки:

[assembly: InternalsVisibleTo("Microsoft.Practices.Unity")]
[assembly: InternalsVisibleTo("Microsoft.Practices.Unity.Configuration")]
[assembly: InternalsVisibleTo("Microsoft.Practices.ObjectBuilder2")]

Однако без изменений. Unity / ObjectBuilder по-прежнему не будет устанавливать внутреннее свойство

Ответы [ 8 ]

6 голосов
/ 17 февраля 2009

Другое решение заключается в использовании [InjectionMethod] для метода, в котором вы передаете зависимость в класс.

public class MyClass {
private ILogger logger;

[InjectionMethod]
public void Init([Dependency] ILogger logger)
{
    this.logger = logger;

... и т.д.


и вызывая его:

container.BuildUp<MyClass>(instanceOfMyClass);

, который будет вызывать Init с зависимостью от единицы.

не совсем решил проблему, я знаю ... но

:-) J

5 голосов
/ 23 января 2009

Если свойство доступно только для получения, имеет смысл использовать внедрение в конструктор , а не внедрение свойства.

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

4 голосов
/ 16 октября 2012

Сам вопрос кажется недоразумением.

Относительно основного утверждения:

набор открытых свойств, которые вы никогда не хотите, чтобы кто-либо устанавливал или возможность установки, кроме Unity.

Вы хотели бы установить их в модульных тестах, или как иначе вы бы проходили макеты зависимостей? Даже если у вас нет модульных тестов, странно иметь зависимости, которые ничто (кроме некоторой магии Unity) не может установить. Хотите, чтобы ваш код сильно зависел от инструмента поддержки?

Кроме того, наличие открытых свойств вовсе не является проблемой, поскольку ваш код ДОЛЖЕН зависеть от интерфейсов, а не от реализаций (один из принципов SOLID). Если вы не следуете этому принципу - у вас нет причин использовать Unity. Конечно, вы не будете объявлять зависимости в интерфейсе, поэтому класс потребления не знает о них.

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

3 голосов
/ 09 июня 2010

ОБНОВЛЕНИЕ для Enterprise Library 5.0

Как может предупредить rally52rs, обновление до EntLib5.0 нарушает его реализацию. Используя тот же подход, что и Rally, я размышлял над новой базой кода и работал над следующей 5.0-совместимой версией InternalConstructorSelectorPolicy.

Обратите внимание, что моя версия специально ограничивается внутренними конструкторами в методе FindLongestConstructor. На данный момент мой код функционально отличается от кода Rally .

.
public class InternalConstructorSelectorPolicy : IConstructorSelectorPolicy, IBuilderPolicy 
{
    private IDependencyResolverPolicy CreateResolver(ParameterInfo parameter)
    {
        List<DependencyResolutionAttribute> attrs = parameter.GetCustomAttributes(false).OfType<DependencyResolutionAttribute>().ToList<DependencyResolutionAttribute>();
        if (attrs.Count > 0)
        {
            return attrs[0].CreateResolver(parameter.ParameterType);
        }
        return new NamedTypeDependencyResolverPolicy(parameter.ParameterType, null);
    }

    private SelectedConstructor CreateSelectedConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination, ConstructorInfo ctor)
    {
        SelectedConstructor result = new SelectedConstructor(ctor);
        foreach (ParameterInfo param in ctor.GetParameters())
        {
            string key = Guid.NewGuid().ToString();
            IDependencyResolverPolicy policy = this.CreateResolver(param);
            resolverPolicyDestination.Set<IDependencyResolverPolicy>(policy, key);
            DependencyResolverTrackerPolicy.TrackKey(resolverPolicyDestination, context.BuildKey, key);
            result.AddParameterKey(key);
        }
        return result;
    }

    private static ConstructorInfo FindInjectionConstructor(Type typeToConstruct)
    {
        ConstructorInfo[] injectionConstructors = typeToConstruct
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .Where<ConstructorInfo>(delegate(ConstructorInfo ctor)
        {
            return ctor.IsDefined(typeof(InjectionConstructorAttribute), true);
        }).ToArray<ConstructorInfo>();
        switch (injectionConstructors.Length)
        {
            case 0:
                return null;

            case 1:
                return injectionConstructors[0];
        }
        throw new InvalidOperationException(string.Format("Multiple constructors found for {0}" , typeToConstruct.Name ));
    }

    private static ConstructorInfo FindLongestConstructor(Type typeToConstruct)
    {
        var constructors =
            Array.FindAll(
                typeToConstruct.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic),
                ctor => !ctor.IsFamily && !ctor.IsPrivate);  //Filter out protected and private constructors

        Array.Sort<ConstructorInfo>(constructors, new ConstructorLengthComparer());
        switch (constructors.Length)
        {
            case 0:
                return null;

            case 1:
                return constructors[0];
        }
        int paramLength = constructors[0].GetParameters().Length;
        if (constructors[1].GetParameters().Length == paramLength)
        {
            throw new InvalidOperationException(string.Format("Ambiguous constructor found for {0}", typeToConstruct.Name));
        }
        return constructors[0];
    }

    public SelectedConstructor SelectConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination)
    {
        Type typeToConstruct = context.BuildKey.Type;
        ConstructorInfo ctor = FindInjectionConstructor(typeToConstruct) ?? FindLongestConstructor(typeToConstruct);
        if (ctor != null)
        {
            return this.CreateSelectedConstructor(context, resolverPolicyDestination, ctor);
        }
        return null;
    }

    // Nested Types
    private class ConstructorLengthComparer : IComparer<ConstructorInfo>
    {
        // Methods
        public int Compare(ConstructorInfo x, ConstructorInfo y)
        {
            return (y.GetParameters().Length - x.GetParameters().Length);
        }
    }
}
2 голосов
/ 23 января 2009

Ну, после того, как я долго ковырялся в отражателе, я понял это. По умолчанию код, который находит конструктор для вызовов внедрения конструктора:

ConstructorInfo[] constructors = typeToConstruct.GetConstructors()

Без BindingFlags, который будет обнаруживать только публичные конструкторы. С помощью некоторых хитростей (как в копировании / вставке из отражателя) вы можете создать UnityContainerExtension, который выполняет те же функции, что и реализация по умолчанию, но изменить вызов GetConstructors () на:

ConstructorInfo[] constructors = typeToConstruct..GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)

Затем добавьте расширение в контейнер единицы. Реализованное расширение составляет ~ 100 строк кода, поэтому я не вставил его сюда. Если кто-то хочет, дайте мне знать ...

Новый рабочий тестовый набор. Обратите внимание, что все классы, созданные Unity, теперь являются внутренними:

[TestFixture]
public class UnityFixture
{
    [Test]
    public void UnityCanSetInternalDependency()
    {
        UnityContainer container = new UnityContainer();
        container.AddNewExtension<InternalConstructorInjectionExtension>();
        container.RegisterType<HasInternalDep, HasInternalDep>();
        container.RegisterType<TheDep, TheDep>();

        var i = container.Resolve<HasInternalDep>();
        Assert.IsNotNull(i);
        Assert.IsNotNull(i.dep);
    }
}


internal class HasInternalDep
{
    internal HasInternalDep(TheDep dep)
    {
        this.dep = dep;
    }

    internal TheDep dep { get; set; }
}

internal class TheDep
{
}

Я уверен, что могу сделать расширение для того же, чтобы разрешить непубличные свойства, но этот код был намного сложнее:)

1 голос
/ 30 сентября 2011

@ rally25rs, хотя посту более двух лет, он по-прежнему занимает высокое место (views / google и т. Д.), Поэтому я решил добавить свои 2 цента. У меня возникла та же проблема, и в итоге я выбрал это решение : UnityContainer и внутренний конструктор . Это подразумевается как комментарий, но я пока не могу оставлять комментарии.

Вы, наверное, уже видели и знаете это, но, тем не менее, это может пригодиться любому, кто просматривает: атрибут InternalsVisibleTo() никогда не должен был работать - это потому, что Unity не вызывает ваши классы напрямую. Вместо этого он использует отражение и осматривает Type. Конечно, Type не изменяется в результате присутствия атрибута. Чтобы «пользоваться» преимуществами внутренних компонентов, видимых и т. Д. На принимающей стороне, вы должны явно вызвать внутренний c'or (или свойство).

0 голосов
/ 26 июня 2009

Это мой класс расширения внутреннего конструктора инжектора:

Большая потенциальная проблема: 99% из этого - копирование / вставка кода Unity из отражателя .NET, из версии Unity 4.1.0.0. Более новые версии Unity могут изменить реализацию и сломать это расширение или вызвать ошибочные ошибки. Вы предупреждены!

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.ObjectBuilder;
using Microsoft.Practices.Unity.Utility;

namespace MyApp.Unity.Configuration
{
    /// <summary>
    /// This extension changes the behavior of Unity constructor injection to allow the use of non-public constructors.
    /// By default, Unity/ObjectBuilder would call Type.GetConstructors() to get the constructors. With the default binding
    /// flags, this only returns public constructors.
    /// The code here is 99% copy/paste from Reflector's dissassembly of the default Unity/OB implementation.
    /// My only change was to add binding flags to get all constructors, not just public ones.
    /// For more info, see: Microsoft.Practices.Unity.ObjectBuilder.DefaultUnityConstructorSelectorPolicy
    /// </summary>
    public class InternalConstructorSelectorPolicy : IConstructorSelectorPolicy
    {
        protected IDependencyResolverPolicy CreateResolver(ParameterInfo param)
        {
            List<DependencyResolutionAttribute> list = new List<DependencyResolutionAttribute>(Sequence.OfType<DependencyResolutionAttribute>(param.GetCustomAttributes(false)));
            if (list.Count > 0)
            {
                return list[0].CreateResolver(param.ParameterType);
            }
            return new NamedTypeDependencyResolverPolicy(param.ParameterType, null);
        }

        private SelectedConstructor CreateSelectedConstructor(IBuilderContext context, ConstructorInfo ctor)
        {
            SelectedConstructor constructor = new SelectedConstructor(ctor);
            foreach (ParameterInfo info in ctor.GetParameters())
            {
                string buildKey = Guid.NewGuid().ToString();
                IDependencyResolverPolicy policy = this.CreateResolver(info);
                context.PersistentPolicies.Set<IDependencyResolverPolicy>(policy, buildKey);
                DependencyResolverTrackerPolicy.TrackKey(context.PersistentPolicies, context.BuildKey, buildKey);
                constructor.AddParameterKey(buildKey);
            }
            return constructor;
        }

        private ConstructorInfo FindInjectionConstructor(Type typeToConstruct)
        {
            ConstructorInfo[] infoArray = Array.FindAll<ConstructorInfo>(typeToConstruct.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), delegate(ConstructorInfo ctor)
            {
                return ctor.IsDefined(typeof(InjectionConstructorAttribute), true);
            });
            switch (infoArray.Length)
            {
                case 0:
                    return null;

                case 1:
                    return infoArray[0];
            }
            throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Resources.MultipleInjectionConstructors", new object[] { typeToConstruct.Name }));
        }

        private ConstructorInfo FindLongestConstructor(Type typeToConstruct)
        {
            ConstructorInfo[] constructors = typeToConstruct.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            Array.Sort<ConstructorInfo>(constructors, new ConstructorLengthComparer());
            switch (constructors.Length)
            {
                case 0:
                    return null;

                case 1:
                    return constructors[0];
            }
            int length = constructors[0].GetParameters().Length;
            if (constructors[1].GetParameters().Length == length)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Resources.AmbiguousInjectionConstructor", new object[] { typeToConstruct.Name, length }));
            }
            return constructors[0];
        }

        public virtual SelectedConstructor SelectConstructor(IBuilderContext context)
        {
            Type typeToConstruct = BuildKey.GetType(context.BuildKey);
            ConstructorInfo ctor = this.FindInjectionConstructor(typeToConstruct) ?? this.FindLongestConstructor(typeToConstruct);
            if (ctor != null)
            {
                return this.CreateSelectedConstructor(context, ctor);
            }
            return null;
        }

        // Nested Types
        private class ConstructorLengthComparer : IComparer<ConstructorInfo>
        {
            // Methods
            public int Compare(ConstructorInfo x, ConstructorInfo y)
            {
                return (y.GetParameters().Length - x.GetParameters().Length);
            }
        }
    }

    /// <summary>
    /// Registeres the InternalConstructorSelectorPolicy with the Unity container.
    /// </summary>
    public class InternalConstructorInjectionExtension : UnityContainerExtension
    {
        protected override void Initialize()
        {
            this.Context.Policies.SetDefault(typeof(IConstructorSelectorPolicy), new InternalConstructorSelectorPolicy());
        }
    }
}
0 голосов
/ 23 января 2009

Основываясь на ответе Кента Б., я переключился на использование инжектора конструктора, который работает для открытых классов. Однако основная проблема все еще существует, когда все, что вы когда-либо хотите назначить или назначить в Unity, должно быть общедоступным. Это включает в себя сами классы.

Новый юнит-тест:

    [TestFixture]
public class UnityFixture
{
    [Test]
    public void UnityCanSetInternalDependency()
    {
        UnityContainer container = new UnityContainer();
        container.RegisterType<HasInternalDep, HasInternalDep>();
        container.RegisterType<TheDep, TheDep>();

        var i = container.Resolve<HasInternalDep>();
        Assert.IsNotNull(i);
        Assert.IsNotNull(i.dep);
    }
    }

internal class HasInternalDep
{
    internal HasInternalDep(TheDep dep)
    {
        this._Dep = dep;
    }

    private TheDep _Dep;
        internal TheDep dep
        {
            get { return _Dep; }
        }
}

internal class TheDep
{
}
}

С атрибутами сборки:

[assembly: InternalsVisibleTo("Microsoft.Practices.Unity")]
[assembly: InternalsVisibleTo("Microsoft.Practices.Unity.Configuration")]
[assembly: InternalsVisibleTo("Microsoft.Practices.ObjectBuilder2")]

Сбой с ошибкой:

The type HasInternalDep does not have an accessible constructor.
at Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, String name)

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

...