Closed pksorensen closed 6 years ago
Here is a just unity example of same
[TestMethod]
public void Test10()
{
var c = new UnityContainer();
var c1 = c.CreateChildContainer();
var c2 = c.CreateChildContainer();
c1.RegisterType(typeof(IMygenericTest<>), typeof(MyGenericTest<>),new ContainerControlledLifetimeManager());
var t1 = c1.Resolve<IMygenericTest<int>>();
Assert.IsNotNull(t1);
c2.RegisterType(typeof(IMygenericTest<>), typeof(MyGenericTest<>), new ContainerControlledLifetimeManager());
var t2 = c2.Resolve<IMygenericTest<int>>();
Assert.IsNotNull(t2);
Assert.AreNotSame(t2, t1);
}
What should we expect here.
If this is expected, then how do i make this work in aspnet core when they have signleton registrations.
My app is hosting two services in the same process and having a shared root container, but different service providers.
I noticed this behavior because one api started resolving services from the other api
[TestClass] public class OpenGenericFixture {
public interface IMygenericTest<T>
{
}
public class MyGenericTest<T> : IMygenericTest<T>
{
}
[TestMethod]
public void OpenGenericTypesShouldNotBeSame()
{
var c = new UnityContainer();
var c1 = c.CreateChildContainer();
var c2 = c.CreateChildContainer();
c1.RegisterType(typeof(IMygenericTest<>), typeof(MyGenericTest<>), new ContainerControlledLifetimeManager());
var t1 = c1.Resolve<IMygenericTest<int>>();
Assert.IsNotNull(t1);
// c2.RegisterType(typeof(IMygenericTest<>), typeof(MyGenericTest<>), new ContainerControlledLifetimeManager());
var t2 = c2.Resolve<IMygenericTest<int>>();
Assert.IsNotNull(t2);
Assert.AreNotSame(t2, t1);
}
}
in this example i would expect var t2 = c2.Resolve<IMygenericTest<int>>();
to throw not regiatered exception
So heres the results:
I found that https://github.com/unitycontainer/container/blob/v5.x/src/UnityContainer.Resolution.cs#L64 when using GetRegistration is returning the same InternalRegistration for both calls to Resolve in the unit test above.
c1.RegisterType(typeof(IMygenericTest<>), typeof(MyGenericTest<>), new ContainerControlledLifetimeManager());
c2.RegisterType(typeof(IMygenericTest<>), typeof(MyGenericTest<>) ,new ContainerControlledLifetimeManager());
var t1 = c1.Resolve<IMygenericTest<int>>();
Assert.IsNotNull(t1);
var t2 = c2.Resolve<IMygenericTest<int>>();
Assert.IsNotNull(t2);
This then makes https://github.com/unitycontainer/container/blob/v5.x/src/Strategies/LifetimeStrategy.cs#L32 return the same ContainerControlledLifetimeManager at the second resolution instead of a new one and the value is reused instead of a new.
So, for child containers it looks like it uses the parent containers registrations.
Would the fix be to have child containers contain their own registrations and only resolve to parent registrations when it attempted its own registrations. I dont see other ways to fix this.
update
Looks like this is how its implemented currently.
Is this then what happens: When the first resolution happens, then it first ask child 1 for the registration which do not exist, then it asks the parent which dont exists but using GetOrAdd its now added to the parent. Then the second resolution will try against child 2 and then the parent which now has it and it resolves this.
this fixes the issue
private void SetupChildContainerBehaviors()
{
_registrations = new HashRegistry<Type, IRegistry<string, IPolicySet>>(ContainerInitialCapacity);
IsTypeRegistered = IsTypeRegisteredLocally;
GetRegistration = GetOrGetParentOrAddCurrent;
Register = AddOrUpdate;
GetPolicy = Get;
SetPolicy = Set;
ClearPolicy = Clear;
}
private IPolicySet GetOrGetParentOrAddCurrent(Type type, string name)
{
var a = Get(type, name);
if(a == null)
{
a = _parent.Get(type, name);
}
if (a == null)
a = GetOrAdd(type, name);
return a;
}
but broke
[TestMethod]
public void ChainOfContainers()
{
@ENikS Let me know if I am on track and if it should be moved to the Container repository.
I would argue that it makes sense that the behavior GetOrAdd on parent when not existing is wrong - but since it is a breaking change. How can we make this a opt in change and get it fixed now instead of a larger version bumb?
this sovled it. all tests passes including my new one.
private void SetupChildContainerBehaviors()
{
_registrations = new HashRegistry<Type, IRegistry<string, IPolicySet>>(ContainerInitialCapacity);
IsTypeRegistered = IsTypeRegisteredLocally;
GetRegistration = GetOrGetParentOrAddCurrent;
Register = AddOrUpdate;
GetPolicy = Get;
SetPolicy = Set;
ClearPolicy = Clear;
}
private IPolicySet GetOrGetParentOrAddCurrent(Type type, string name)
{
var a = Get(type, name);
if(a == null)
{
var parent = _parent;
while (a == null && parent != null)
{
a = parent.Get(type, name);
parent = parent._parent;
}
}
if (a == null)
a = GetOrAdd(type, name);
return a;
}
Given the following test
the test fails with "sameobj" indicating that h1 and h2 are the same.
I would expect that to be two different objects as its being build from two different child containers.
I added the same for normal classes which works fine. See the ITest1 resolution above.
I am looking into how generics are resolved