Possible bug with nested containers and hybrid lifecycle vs transient lifecycles

240 views
Skip to first unread message

Samuel Poirier

unread,
Oct 9, 2014, 11:57:53 AM10/9/14
to structure...@googlegroups.com

Recently we have updated to the version 3.1.4.143 and I have encountered an issue which I am not sure if it’s a bug or a misunderstanding of how it works.

 

We have a wrapper around structuremap containers called ServiceLocator.Current

 

The following scenario passes when I am using all transient lifecycles for configuration, but as soon as I use another life cycle (hybrid in this case), the test won’t pass.

Here is the test scenario:

By using the simple structure:

 

internal class DummyDependencyA : IDummyDependency
   
{

   
}

   
internal class DummyDependencyB : IDummyDependency
   
{
   
}  

   
internal interface IDummyDependency
   
{
   
}  

   
internal interface IDummyInterface
   
{
       
IDummyDependency DummyDependency { get; }
   
}


   
internal class DummyImplementation : IDummyInterface
   
{
       
public IDummyDependency DummyDependency { get; private set; }

       
public DummyImplementation(IDummyDependency dummyDependency)
       
{
           
DummyDependency = dummyDependency;
       
}
   
}

       
[Test]
       
public void TestStructureMapInstanceEvictedDependency()
       
{

           

           
// Testing with different lifecycle

           
// Transiant is the only one that is working from what I've tested

           
var lifecycleEnum = LifecycleEnum.Hybrid;

 

           
// Wrapper call for CurrentContainer.Configure(x => x.For(type).LifecycleIs(GetLifecycle(lifecycle)).Use(resultType));

           
// Lifecycle used here is new HybridLifecycle()

           
ServiceLocator.Current.Register<IDummyInterface, DummyImplementation>(lifecycleEnum);

 

           
ServiceLocator.Current.Register<IDummyDependency, DummyDependencyA>(lifecycleEnum);

 

           
// Wrapper call for CurrentContainer.GetInstance<TService>();

           
var interface1 = ServiceLocator.Current.GetInstance<IDummyInterface>();

 

           
// Success

            interface1
.DummyDependency.GetType().ShouldBeExactlyType<DummyDependencyA>();

 

           
/*              

            public IServiceLocator BeginTransactionWithNewDiContainer()
            {
                var nestedContainer = CurrentContainer.GetNestedContainer();                            

                // ThreadStatic IContainer stack
                ContainerStack.Push(nestedContainer);            

                return this;

            }

             

               

            public void Dispose()
            {
                ContainerStack.Pop();
            }

             */


           
using (ServiceLocator.Current.BeginTransactionWithNewDiContainer())
           
{
               
var dummyInterface1 = ServiceLocator.Current.GetInstance<IDummyInterface>();

               
// Success

                dummyInterface1
.DummyDependency.GetType().ShouldBeExactlyType<DummyDependencyA>();                

               
var instance = new DummyDependencyB();

 

               
Console.WriteLine(ServiceLocator.Current.DescribeBuildPlan<IDummyDependency>());

 

               
Console.WriteLine(@"BEFORE RE-REGISTER");

               
Console.WriteLine(ServiceLocator.Current.WhatDoIHave(typeof(IDummyDependency)));

               
Console.WriteLine(ServiceLocator.Current.WhatDoIHave(typeof(IDummyInterface)));

 

               
// Override current registration with singleton instance for this container scope

               
ServiceLocator.Current.Register<IDummyDependency>(instance, lifecycleEnum);

 

               
Console.WriteLine(ServiceLocator.Current.DescribeBuildPlan<IDummyDependency>());

               

               
Console.WriteLine(@"AFTER RE-REGISTER");

               
Console.WriteLine(ServiceLocator.Current.WhatDoIHave(typeof(IDummyDependency)));

               
Console.WriteLine(ServiceLocator.Current.WhatDoIHave(typeof(IDummyInterface)));

 

               
ServiceLocator.Current.EjectAllInstancesOf<IDummyInterface>();

 

               
Console.WriteLine(ServiceLocator.Current.DescribeBuildPlan<IDummyDependency>());

 

               
Console.WriteLine(@"AFTER EJECT");

               
Console.WriteLine(ServiceLocator.Current.WhatDoIHave(typeof(IDummyDependency)));

               
Console.WriteLine(ServiceLocator.Current.WhatDoIHave(typeof(IDummyInterface)));

 

               
var dummyInterface = ServiceLocator.Current.GetInstance<IDummyInterface>();

 

               
// Success (it's a different instance)

                dummyInterface
.ShouldNotBe(dummyInterface1);

 

               
////////////////////

               
// FAIL HERE. dummyInterface.DummyDependency is still type of DummyDependencyA, even after the re-register

               
////////////////////

                dummyInterface
.DummyDependency.GetType().ShouldBeExactlyType<DummyDependencyB>();

 

               
var @interface = ServiceLocator.Current.GetInstance<IDummyInterface>();

               
@interface.DummyDependency.ShouldBe(instance);

           
}

 

           
ServiceLocator.Current.GetInstance<IDummyInterface>().DummyDependency.GetType().ShouldBeExactlyType<DummyDependencyA>();

       
}


 

Here is the output on the console trace for result of my test in hybrid when it fails:

 

PluginType: BesLogic.Test.Model.IDummyDependency

 

Lifecycle: Hybrid

new DummyDependencyA()

 

BEFORE RE
-REGISTER

Nested Container: DEFAULT - Nested

 

 

=============================================================================================================

PluginType           Namespace               Lifecycle     Description                              Name    

-------------------------------------------------------------------------------------------------------------

IDummyDependency     BesLogic.Test.Model     Hybrid        BesLogic.Test.Model.DummyDependencyA     (Default)

=============================================================================================================

Nested Container: DEFAULT - Nested

 

 

===============================================================================================================

PluginType          Namespace               Lifecycle     Description                                 Name    

---------------------------------------------------------------------------------------------------------------

IDummyInterface     BesLogic.Test.Model     Hybrid        BesLogic.Test.Model.DummyImplementation     (Default)

===============================================================================================================

PluginType: BesLogic.Test.Model.IDummyDependency

Lifecycle: Object

Value: BesLogic.Test.Model.DummyDependencyB

 

AFTER RE
-REGISTER

Nested Container: DEFAULT - Nested

 

 

======================================================================================================================

PluginType           Namespace               Lifecycle     Description                                       Name    

----------------------------------------------------------------------------------------------------------------------

IDummyDependency     BesLogic.Test.Model     Object        Object:  BesLogic.Test.Model.DummyDependencyB     (Default)

======================================================================================================================

Nested Container: DEFAULT - Nested

 

 

===============================================================================================================

PluginType          Namespace               Lifecycle     Description                                 Name    

---------------------------------------------------------------------------------------------------------------

IDummyInterface     BesLogic.Test.Model     Hybrid        BesLogic.Test.Model.DummyImplementation     (Default)

===============================================================================================================

PluginType: BesLogic.Test.Model.IDummyDependency

Lifecycle: Object

Value: BesLogic.Test.Model.DummyDependencyB

 

AFTER EJECT

Nested Container: DEFAULT - Nested

 

 

======================================================================================================================

PluginType           Namespace               Lifecycle     Description                                       Name    

----------------------------------------------------------------------------------------------------------------------

IDummyDependency     BesLogic.Test.Model     Object        Object:  BesLogic.Test.Model.DummyDependencyB     (Default)

======================================================================================================================

Nested Container: DEFAULT - Nested

 

 

===============================================================================================================

PluginType          Namespace               Lifecycle     Description                                 Name    

---------------------------------------------------------------------------------------------------------------

IDummyInterface     BesLogic.Test.Model     Hybrid        BesLogic.Test.Model.DummyImplementation     (Default)

===============================================================================================================

 

 
Expected: <BesLogic.Test.Model.DummyDependencyB>

 
But was:  <BesLogic.Test.Model.DummyDependencyA>

 

Expected: <BesLogic.Test.Model.DummyDependencyB>  But was:  <BesLogic.Test.Model.DummyDependencyA>

   at
NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)   at NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression)   at BesLogic.Test.Utilities.AssertExtensions.ShouldBeExactlyType(Type o) in AssertExtensions.cs: line 66   at BesLogic.Test.Model.ModelTest.TestStructureMapInstanceEvictedDependency() in ModelTest.cs: line 405


 

 

When I run the test with lifecycle as Transient, it passes. Here are the console logs:

 

PluginType: BesLogic.Test.Model.IDummyDependency

 

Lifecycle: Transient

new DummyDependencyA()

 

BEFORE RE
-REGISTER

Nested Container: DEFAULT - Nested

 

 

=============================================================================================================

PluginType           Namespace               Lifecycle     Description                              Name    

-------------------------------------------------------------------------------------------------------------

IDummyDependency     BesLogic.Test.Model     Transient     BesLogic.Test.Model.DummyDependencyA     (Default)

=============================================================================================================

Nested Container: DEFAULT - Nested

 

 

===============================================================================================================

PluginType          Namespace               Lifecycle     Description                                 Name    

---------------------------------------------------------------------------------------------------------------

IDummyInterface     BesLogic.Test.Model     Transient     BesLogic.Test.Model.DummyImplementation     (Default)

===============================================================================================================

PluginType: BesLogic.Test.Model.IDummyDependency

Lifecycle: Object

Value: BesLogic.Test.Model.DummyDependencyB

 

AFTER RE
-REGISTER

Nested Container: DEFAULT - Nested

 

 

======================================================================================================================

PluginType           Namespace               Lifecycle     Description                                       Name    

----------------------------------------------------------------------------------------------------------------------

IDummyDependency     BesLogic.Test.Model     Object        Object:  BesLogic.Test.Model.DummyDependencyB     (Default)

======================================================================================================================

Nested Container: DEFAULT - Nested

 

 

===============================================================================================================

PluginType          Namespace               Lifecycle     Description                                 Name    

---------------------------------------------------------------------------------------------------------------

IDummyInterface     BesLogic.Test.Model     Transient     BesLogic.Test.Model.DummyImplementation     (Default)

===============================================================================================================

PluginType: BesLogic.Test.Model.IDummyDependency

Lifecycle: Object

Value: BesLogic.Test.Model.DummyDependencyB

 

AFTER EJECT

Nested Container: DEFAULT - Nested

 

 

======================================================================================================================

PluginType           Namespace               Lifecycle     Description                                       Name    

----------------------------------------------------------------------------------------------------------------------

IDummyDependency     BesLogic.Test.Model     Object        Object:  BesLogic.Test.Model.DummyDependencyB     (Default)

======================================================================================================================

Nested Container: DEFAULT - Nested

 

 

===============================================================================================================

PluginType          Namespace               Lifecycle     Description                                 Name    

---------------------------------------------------------------------------------------------------------------

IDummyInterface     BesLogic.Test.Model     Transient     BesLogic.Test.Model.DummyImplementation     (Default)

===============================================================================================================


 

Am I doing anything wrong?

Jeremy Miller

unread,
Oct 9, 2014, 12:12:13 PM10/9/14
to structure...@googlegroups.com
Using a "Hybrid" lifecycle makes no sense for a nested container to begin with. If you're using nested containers, the only life cycles that are relevant are the default "PerRequest" lifecycle and the much rarer "Unique" lifecycle. A nested container will just delegate to the parent container for every other lifecycle.

You probably want to closely watch whether or not you're ejecting instances from a nested container or the parent container in your example. Nested containers should be short lived (per transaction, per service bus message, per http request). I don't think there is a reason to eject directly from a nested container, and even if you did, it would only impact the nested container and not the parent container.

I think that using a nested container effectively eliminates the need for the hybrid or any kind of HttpContext specific lifecycle if you just use a nested container per request.

At this point, HttpContext, ThreadLocal, and the old "Hybrid" life cycles are just there for some modicum of backward compatibility and older frameworks like WebForms/WinForms.

I think I'd advise against the static facade over the Container too, especially with the way you're putting nested containers in a stack. 

- Jeremy
...

Samuel Poirier

unread,
Oct 9, 2014, 1:26:07 PM10/9/14
to structure...@googlegroups.com
Ok, so if I understand correctly, for a web mvc project, instead of using hybrid lifecycles, I should create a nested container per Http requests (somewhere in the begin request) to manage my "http context scoped" instances. (even remove StructureMap.Web from my references)

Currently, if I am registering some plugin family as HttpContextScoped or HybridHttpOrThreadLocalScoped. To have the equivalent behaviours within the http request nested container, which lifecycle would I need to select?

ObjectLifecycle
SingletonLifecycle
ThreadLocalStorageLifecycle
TransientLifecycle
UniquePerRequestLifecycle

And if within the same request, I need another nested container to temporarily change the registered type of a plugin family for an other one, how can I make sure I would get the newly registered type within that second nested container?

Thanks a lot for your help,

- Samuel

Jeremy D. Miller

unread,
Oct 9, 2014, 2:12:26 PM10/9/14
to structure...@googlegroups.com
Check out the StructureMap.MVC5 nuget if you’re using ASP.Net or the Web API nuget from the same author (don’t remember the name offhand).

You just want to use the default lifecycle (Transient) if you want the nested container behavior. If you do that, the objects created by the nested container during the request are effectively scoped to the nested container itself and disposed when the nested container itself is disposed. The idea is to *not* make users have to worry so much about explicitly configuring life cycles to get the behavior that you’d generally want.

Hope that helps,

Jeremy


-- 
You received this message because you are subscribed to the Google Groups "structuremap-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to structuremap-us...@googlegroups.com.
To post to this group, send email to structure...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/structuremap-users/805f4eed-185f-4979-a42c-ea4182e67266%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Samuel Poirier

unread,
Oct 9, 2014, 2:42:55 PM10/9/14
to structure...@googlegroups.com
Ahh! There you go!

That's exactly what I was looking for! I thought that Transient was always a new instance (i thought it was the same thing as UniquePerRequest) but now I get the difference! Thanks a lot!

        [Test]
       
public void TestStructureMapLifecycles()
       
{
           
ILifecycle lifecycle = new TransientLifecycle();


           
var container = new Container(x =>
           
{
                x
.For<IDummyInterface>(lifecycle).Use<DummyImplementation>();
                x
.For<IDummyDependency>(lifecycle).Use<DummyDependencyA>();
           
});


           
IContainer childContainer = container.GetNestedContainer();

           
// Success!
            container
.GetInstance<IDummyInterface>().ShouldNotBe(container.GetInstance<IDummyInterface>());

           
// Success!
            childContainer
.GetInstance<IDummyInterface>().ShouldBe(childContainer.GetInstance<IDummyInterface>());
       
}
To unsubscribe from this group and stop receiving emails from it, send an email to structuremap-users+unsub...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages