【问题标题】:Unity framework DependencyAttribute only works for public properties?Unity 框架 DependencyAttribute 仅适用于公共属性?
【发布时间】:2009-01-23 16:36:12
【问题描述】:

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

所以在 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 仍然不会设置内部属性

【问题讨论】:

  • 通过创建自定义扩展找到了解决方案。请看下面我的回答...

标签: .net unity-container


【解决方案1】:

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

public class MyClass {
private ILogger logger;

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

...等


并调用它:

container.BuildUp<MyClass>(instanceOfMyClass);

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

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

:-) J

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 是的,这可能是一个更清洁的解决方案。仍然对 unity / ob 不能使用内部结构感到恼火。哦,好吧...谢谢!
    【解决方案3】:

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

    关于核心声明:

    一堆你永远不希望任何人设置或成为的公共属性 可以设置,除了 Unity。

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

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

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

    【讨论】:

      【解决方案4】:

      企业库 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);
              }
          }
      }
      

      【讨论】:

      • 我还没有迁移到 EntLib5,所以每当我迁移到 EntLib5 时,我都会感到头疼……感谢代码!
      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        @rally25rs,虽然该帖子已有两年多的历史,但它的排名仍然很高(浏览量/谷歌等),所以我想我会加 2 美分。我遇到了同样的问题,最终选择了这个解决方案:UnityContainer and internal constructor。 这是一个评论,但我还不能发布 cmets。

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

        【讨论】:

          【解决方案7】:

          根据 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 来说真的很难看...

          【讨论】:

            【解决方案8】:

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

            潜在的大问题:其中 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());
                    }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-11-23
              • 2012-02-16
              • 1970-01-01
              • 2010-10-22
              • 2019-11-15
              • 2013-11-28
              • 1970-01-01
              • 2021-06-26
              相关资源
              最近更新 更多