Unity framework DependencyAttribute only works for public properties?

前端 未结 8 1231
小鲜肉
小鲜肉 2021-02-14 17:31

I was trying to clean up some accessability stuff in my code, and inadvertently broke Unity dependency injection. After a while I realized that I marked some public properties t

相关标签:
8条回答
  • 2021-02-14 17:53

    @rally25rs, although the post is more than two years old it's still ranked high (views/google etc.) so I thought I'd add my 2 cents.. I've had the same problem and eventually chose this solution: UnityContainer and internal constructor. This is meant as a comment but I can't post comments just yet.

    You've probably seen and know this already, still it might be of use for anyone else viewing: The InternalsVisibleTo() attribute should never have worked - that's because Unity isn't calling your classes directly. Instead, it's using reflection and inspecting the Type. Of course, the Type isn't changed as a result of the attribute being there. To "enjoy" the benefits of internals visible etc. on the receiving side, you have to explicitly call the internal c'tor (or property).

    0 讨论(0)
  • 2021-02-14 17:54

    The question itself seems to be a misunderstanding.

    Regarding the core statement:

    a bunch of public properties that you never want anyone to set or be able to set, other than Unity.

    You would want to set them in unit tests, or how else would you pass dependency mocks? Even if you don't have unit tests, it's a strange idea to have dependencies that nothing (except some magic of Unity) can set. Do you want your code to depend so much on support tool?

    Also, having public properties is not an issue at all, because your code MUST depend on interfaces, not on implementations (one of SOLID principles). If you don't follow this principle - there is no reason for you to use Unity. Surely you wouldn't declare dependencies in the interface, so the consuming class doesn't know about them.

    You've already been told that it's better to use constructor injection, but property injection also has its beauty. It allows adding new dependencies with less modifications (in particular, you can avoid changing existing unit tests at all, only adding new ones).

    0 讨论(0)
  • 2021-02-14 17:56

    Another solution is to use [InjectionMethod] on a method where you pass the dependency into the class.

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

    ...etc


    and calling it:

    container.BuildUp<MyClass>(instanceOfMyClass);
    

    which will call Init with the dependency from unity.

    didn´t quite solve the problem, I know...but

    :-) J

    0 讨论(0)
  • 2021-02-14 17:59

    Well after a lof of poking around in reflector, I figured this out. By default, the code that finds a constructor for constructor injection calls:

    ConstructorInfo[] constructors = typeToConstruct.GetConstructors()
    

    With no BindingFlags, that will only detect public constructors. With some trickery (as in copy/paste from reflector) you can make a UnityContainerExtension that does all the same stuff as the default implementation, but change the call to GetConstructors() to:

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

    Then add the extension into the unity container. The implemented extenstion is ~100 lines of code, so I didn't paste it here. If anyone wants it, let me know...

    New working test case. Note that all the Unity created classes are now internal:

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

    I'm sure I can make an extension to do the same to resolve non-public properties, but that code was a lot more complicated :)

    0 讨论(0)
  • 2021-02-14 17:59

    Based on Kent B's answer, I changed to use constructor injection, which does work for public classes. However the root issue still exists, where anything you ever want to assign or have assigned by Unity has to be public. This includes the classes themselves.

    New unit test:

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

    With the assembly attributes:

    [assembly: InternalsVisibleTo("Microsoft.Practices.Unity")]
    [assembly: InternalsVisibleTo("Microsoft.Practices.Unity.Configuration")]
    [assembly: InternalsVisibleTo("Microsoft.Practices.ObjectBuilder2")]
    

    Fails with the error:

    The type HasInternalDep does not have an accessible constructor.
    at Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, String name)
    

    So overall it seems that if you want to use Unity, you basically just have to blanket mark everything public. Really ugly for a utility/library .dll...

    0 讨论(0)
  • 2021-02-14 18:01

    If the property is get-only, it makes more sense to use contructor injection rather than property injection.

    If Unity did use reflection to set private or internal members, it would be subjected to code access security constraints. Specifically, it wouldn't work in a low-trust environment.

    0 讨论(0)
提交回复
热议问题