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