9

我试图在我的代码中清理一些可访问性的东西,并且无意中破坏了 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 仍然不会设置内部属性

4

8 回答 8

6

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

public class MyClass {
private ILogger logger;

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

...ETC


并称它为:

container.BuildUp<MyClass>(instanceOfMyClass);

它将使用来自统一的依赖关系调用 Init。

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

:-) Ĵ

于 2009-02-17T11:16:30.627 回答
5

如果属性是 get-only,则使用构造函数注入而不是属性注入更有意义。

如果 Unity确实使用反射来设置私有或内部成员,它将受到代码访问安全约束。具体来说,它不会在低信任环境中工作。

于 2009-01-23T16:50:49.077 回答
5

这个问题本身似乎是一个误解。

关于核心声明:

除了 Unity 之外,您永远不希望任何人设置或能够设置的一堆公共属性。

您想在单元测试中设置它们,或者您将如何传递依赖模拟?即使您没有单元测试,拥有任何东西(除了 Unity 的一些魔法)都无法设置的依赖项也是一个奇怪的想法。您是否希望您的代码如此依赖支持工具?

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

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

于 2012-10-16T13:03:58.287 回答
3

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);
        }
    }
}
于 2010-06-09T19:19:13.093 回答
2

好吧,在反射器中四处闲逛之后,我想通了。默认情况下,为构造函数注入调用找到构造函数的代码:

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
{
}

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

于 2009-01-23T18:51:11.457 回答
1

@rally25rs,虽然该帖子已有两年多的历史,但它仍然排名很高(视图/谷歌等)所以我想我会加我的 2 美分。我遇到了同样的问题并最终选择了这个解决方案:UnityContainer 和内部构造函数。这意味着评论,但我还不能发表评论。

您可能已经看到并知道这一点,但它可能对其他任何查看的人有用:该InternalsVisibleTo()属性永远不会起作用 - 那是因为 Unity 没有直接调用您的类。相反,它使用反射并检查Type. 当然,Type不会因为属性的存在而改变。要在接收端“享受”内部可见等的好处,您必须显式调用内部 c'tor(或属性)。

于 2011-09-30T02:12:21.663 回答
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 来说真的很难看...

于 2009-01-23T17:15:09.963 回答
0

这是我的内部构造函数注入器扩展类:

潜在的大问题:其中 99% 是来自统一版本 4.1.0.0 的 .NET 反射器的 Unity 代码的复制/粘贴。较新版本的 Unity 可能会更改实现并破坏此扩展,或导致 flakey 错误。你被警告了!

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());
        }
    }
}
于 2009-06-26T17:43:27.530 回答