Unity框架DependencyAttribute仅适用于公共属性?

时间:2009-01-23 16:36:13

标签: .net unity-container

我试图在我的代码中清理一些可访问性内容,并且无意中破坏了Unity依赖注入。过了一会儿,我意识到我标记了一些我不想在我的DLL之外暴露到内部的公共属性。然后我开始得到例外。

因此,似乎在Unity中使用[Dependency]属性仅适用于公共属性。我认为这是有道理的,因为内部和私人道具对于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 个答案:

答案 0 :(得分:6)

另一种解决方案是在将依赖项传递给类的方法中使用[InjectionMethod]。

public class MyClass {
private ILogger logger;

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

...等


并称之为:

container.BuildUp<MyClass>(instanceOfMyClass);

将使用unity的依赖关系调用Init。

没有完全解决问题,我知道......但是

:-) J

答案 1 :(得分:5)

如果属性是get-only,则使用contructor injection而不是属性注入更有意义。

如果Unity 使用反射设置私有或内部成员,则会受到代码访问安全性限制。具体而言,它不适用于低信任环境。

答案 2 :(得分:4)

问题本身似乎是一种误解。

关于核心声明:

  

一堆你永远不想让任何人设置或成为的公共属性   能够设置,而不是Unity。

您可能希望在单元测试中设置它们,或者如何通过依赖性模拟? 即使你没有单元测试,但依赖于没有任何东西(除了Unity的一些魔力)可以设置的依赖是一个奇怪的想法。您是否希望您的代码非常依赖支持工具?

此外,拥有公共属性根本不是问题,因为您的代码必须依赖于接口,而不是实现(SOLID原则之一)。如果您不遵循此原则 - 您没有理由使用Unity。当然你不会在接口中声明依赖,所以消费类不知道它们。

你已经被告知使用构造函数注入更好,但属性注入也有它的美感。它允许以较少的修改添加新的依赖项(特别是,您可以避免更改现有的单元测试,只添加新的单元测试)。

答案 3 :(得分:3)

Enterprise Library 5.0更新

由于可能会发生警告,因此升级到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);
        }
    }
}

答案 4 :(得分:2)

在反射器中捅了一下之后,我想出来了。 默认情况下,查找构造函数注入的构造函数的代码调用:

ConstructorInfo[] constructors = typeToConstruct.GetConstructors()

没有BindingFlags,只会检测公共构造函数。 通过一些技巧(如在反射器中复制/粘贴),您可以创建一个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
{
}

我确信我可以做一个扩展来解决非公共属性,但是这个代码要复杂得多:)

答案 5 :(得分:1)

@ rally25rs,虽然帖子已经超过两年了但它仍然排名很高(观看/谷歌等)所以我想我会加2美分..我遇到了同样的问题并最终选择了这个解决方案:UnityContainer and internal constructor。 这是一个评论,但我还不能发表评论。

你可能已经看过并且已经知道了这一点,但它仍然可以用于其他任何人查看:InternalsVisibleTo()属性应该永远不会有用 - 那是因为Unity没有直接调用你的类。相反,它使用反射并检查Type。当然,Type不会因属性存在而改变。要在接收方“享受”内部可见等的好处,您必须明确地调用内部c'tor(或财产)。

答案 6 :(得分:0)

根据Kent B的回答,我改为使用构造函数注入,它适用于公共类。但是,根本问题仍然存在,您想要分配或由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 ...

答案 7 :(得分:0)

这是我的内部构造函数Injector Extension类:

潜在的大问题:其中99%是来自.NET反射器的Unity代码的复制/粘贴,来自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());
        }
    }
}