Unity framework DependencyAttribute работает только для общедоступных свойств?

StackOverflow https://stackoverflow.com/questions/473575

  •  19-08-2019
  •  | 
  •  

Вопрос

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

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

Есть ли способ разрешить unity также устанавливать внутренние или частные свойства?

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

    [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 по-прежнему не будет устанавливать внутреннее свойство

Это было полезно?

Решение 5

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

ConstructorInfo[] constructors = typeToConstruct.GetConstructors()

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

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

Затем добавьте расширение в контейнер unity.Реализованное расширение составляет ~ 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
{
}

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

Другие советы

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

public class MyClass {
private ILogger logger;

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

... и т.д


и называя это:

container.BuildUp<MyClass>(instanceOfMyClass);

который вызовет Init с зависимостью от unity.

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

:-) J

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

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

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

Что касается основного заявления:

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

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

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

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

ОБНОВЛЕНИЕ ДЛЯ Корпоративной библиотеки 5.0

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

Обратите внимание, что моя версия специально ограничивает себя внутренними конструкторами в методе 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);
        }
    }
}

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

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

Основываясь на ответе Кента Би, я перешел на использование внедрения конструктора, которое действительно работает для общедоступных классов.Однако коренная проблема все еще существует, когда все, что вы когда-либо хотели назначить или уже назначили 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...

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

Большая потенциальная проблема: 99% этого является копией / вставкой кода Unity из .NET reflector, из 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());
        }
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top