z4kn4fein / stashbox

A lightweight, fast, and portable dependency injection framework for .NET-based solutions.
https://z4kn4fein.github.io/stashbox
MIT License
141 stars 10 forks source link

Expected override behaviour not working with scopes in 3.1 prerelease 536 #80

Closed Tobriand closed 3 years ago

Tobriand commented 4 years ago

Hi,

When I use the preview version of stashbox (appreciate this may be on your radar and just not final therefore), I'm getting some very strange behaviour when working with scopes:

[TestMethod]
        public void RegisteredInstancesCanBeOverridenViaAFactory()
        {
            // Expectation is that
            // - A registration can be set up that will provide a default value
            // - Scoped instances will override the registration when they have been added.

            //var sbc = CreateContainer(c => c.WithScopedLifetime());
            var sbc = new StashboxContainer(c => c.WithUnknownTypeResolution());
            var toInclude = Args<IFoo>.Get(20);

            // Try using a factory (original scenario)
            //  - ScanScopesFor is an extension method that relies on GetScopedInstanceOrDefault(). It no longer works because the method no longer exists.
            //  - Idea was to walk each IResolutionScope and defer to the default value only if no value was identified, meaning that resolution
            //      could take place without risk of a stackoverflow
            //sbc.Register(toInclude.GetType(), c => c.WithFactory(dr => dr.ScanScopesFor(toInclude.GetType(), null, null).item ?? toInclude));

            // Try just registering a default type
            //  - Below should be equivalent (I think)
            //  - Idea would be to defer to StashBox, and rely on the notion that it would walk the scope-hierarchy for me, 
            //      and fall back on the registration ONLY IF no scoped instance identified
            //  - Actual: Registration used under all circumstances. PutInstanceInScope instance appears to be ignored.
            //sbc.Register(toInclude.GetType(), c => c.WithInstance(toInclude));
            sbc.RegisterInstance(toInclude.GetType(), toInclude);

            // Try placing the instance into the scope at the top layer
            //  - Outer resolves to the expected class, but inner1/2 both resolve to the equivalent of new Args();
            //sbc.PutInstanceInScope(toInclude.GetType(), toInclude);

            var outer = sbc.Resolve<Args<IFoo>>();
            Args<IFoo> inner1 = null;
            Args<IFoo> inner2 = null;
            using (var scope = sbc.BeginScope())
            {
                inner1 = scope.Resolve<Args<IFoo>>();
                var toOverride = Args<IFoo>.Get(30);
                scope.PutInstanceInScope(typeof(Args<IFoo>), toOverride);
                inner2 = scope.Resolve<Args<IFoo>>();
            }

            Assert.AreEqual(toInclude.ArgList[0], outer.ArgList[0]);
            Assert.AreEqual(toInclude.ArgList[0], inner1.ArgList[0]);
            Assert.AreEqual(30, inner2.ArgList[0]);
        }

Am I missing something and this is actually the expected behaviour when operating in a scope (in which case what should I be doing instead to get a scenario where I get access to container registrations, but can locally override), or should at least one of these work?

z4kn4fein commented 4 years ago

Hi,

Ah yeah, I think I know what is going on here. The issue is that the first resolution creates and caches the factory delegate to serve your instance, however, this cache is not going to be invalidated when you put something into the scope to override services. I'm going to fix that and let you know when the next pre-release is ready.

Thanks for reporting!

z4kn4fein commented 4 years ago

Hey, I published a new pre-release version where this functionality should work properly, could you please give it a try? Thanks!

z4kn4fein commented 3 years ago

Closing due to inactivity, feel free to reopen this, or create a new issue when something related comes up.