Out of the box, the core StructureMap assembly supports these lifecycles:
- Transient -- The default lifecycle. A new object is created for each logical request to resolve an object graph from the container.
- Singleton -- Only one object instance will be created for the container and any children or nested containers created by that container
- ContainerScoped -- Only one object instance will be created for unique root, child, or nested container
- AlwaysUnique -- A new object instance is created every time, even within the same object graph
- ThreadLocal -- Only one object instance will be created for the currently executing Thread
Transient
Older versions of StructureMap referred to Transient as PerRequest, which might be a more accurate reflection of how this lifecycle behaves but
causes some confusion with ASP.NET HTTP scoping. The easiest way to think of Transient is that a single object instance will be created for each top level
call to Container.GetInstance()
(or any other object resolution method on the IContainer
interface). Transient objects resolved from a nested container, Transients are scoped to the lifecycle
of the nested container itself. See Nested Containers (Per Request/Transaction) for more information.
StructureMap's behavior for transient objects that implement IDisposable
changed in 4.0 to introduce an "opt-in" tracking mode. Please see StructureMap and IDisposable for the details.
The following unit test demonstrates how Transient lifecycles work in both root and nested containers.
[Fact]
public void Transient()
{
var c = new Container(x => { x.For<IService>().Use<Service>().Transient(); });
// In a normal container, you get a new object
// instance of the Service class in subsequent
// requests
c.GetInstance<IService>()
.ShouldNotBeTheSameAs(c.GetInstance<IService>())
.ShouldNotBeTheSameAs(c.GetInstance<IService>());
// Within a nested container, 'Transient' now
// means within the Nested Container.
// A nested container is effectively one request
using (var nested = c.GetNestedContainer())
{
nested.GetInstance<IService>()
.ShouldBeTheSameAs(nested.GetInstance<IService>())
.ShouldBeTheSameAs(nested.GetInstance<IService>());
}
}
Also note that a transient dependency will
be created exactly once in an object graph resolved from IContainer.GetInstance(Type)
. Imagine that you are building an
object graph with various objects that all need to apply some work to a shared unit of work object (think NHibernate's ISession, Entity Framework's DbContext, RavenDb's IDocumentSession):
public interface IUnitOfWork
{
}
public class DefaultUnitOfWork : IUnitOfWork
{
}
public class Worker1
{
public IUnitOfWork Uow { get; set; }
public Worker1(IUnitOfWork uow)
{
Uow = uow;
}
}
public class Worker2
{
public IUnitOfWork Uow { get; set; }
public Worker2(IUnitOfWork uow)
{
Uow = uow;
}
}
public class WorkerCoordinator
{
public IUnitOfWork Uow { get; set; }
public Worker1 Worker1 { get; set; }
public Worker2 Worker2 { get; set; }
public WorkerCoordinator(IUnitOfWork uow, Worker1 worker1, Worker2 worker2)
{
Uow = uow;
Worker1 = worker1;
Worker2 = worker2;
}
}
[Fact]
public void transient_scoped_Instance_is_built_once_per_resolution_to_the_Container()
{
var container = new Container(_ => { _.For<IUnitOfWork>().Use<DefaultUnitOfWork>(); });
var coordinator = container.GetInstance<WorkerCoordinator>();
// The IUnitOfWork object instance is the same for
// all the objects in the object graph that had
// a constructor dependency on IUnitOfWork
coordinator.Uow
.ShouldBeTheSameAs(coordinator.Worker1.Uow);
coordinator.Uow
.ShouldBeTheSameAs(coordinator.Worker2.Uow);
coordinator.Worker1.Uow
.ShouldBeTheSameAs(coordinator.Worker2.Uow);
}
AlwaysUnique
Very simply, using the AlwaysUnique means that a new object instance will be created every single time a configured Instance is either requested from a Container or as a dependency to another object. The AlwaysUnique lifecycle is a "fire and forget" operation as the object instances are neither tracked nor disposed by StructureMap.
[Fact]
public void Always_Unique()
{
var c = new Container(x => { x.For<IService>().Use<Service>().AlwaysUnique(); });
// In a normal container, you get a new object
// instance of the Service class in subsequent
// requests
c.GetInstance<IService>()
.ShouldNotBeTheSameAs(c.GetInstance<IService>())
.ShouldNotBeTheSameAs(c.GetInstance<IService>());
// Within a nested container, 'Transient' now
// means within the Nested Container.
// A nested container is effectively one request
using (var nested = c.GetNestedContainer())
{
nested.GetInstance<IService>()
.ShouldNotBeTheSameAs(nested.GetInstance<IService>())
.ShouldNotBeTheSameAs(nested.GetInstance<IService>());
}
// Even in a single request,
var holder = c.GetInstance<ServiceUserHolder>();
holder.Service.ShouldNotBeTheSameAs(holder.User.Service);
}
Singleton
StructureMap 3.0 fixed the dreaded singletons with nested container's bug that was so problematic in 2.6.
[Fact]
public void singletons_are_disposed_when_the_container_is_disposed()
{
var container = new Container(_ =>
{
_.ForSingletonOf<DisposableSingleton>();
});
// As a singleton-scoped object, every request for DisposableSingleton
// will return the same object
var singleton = container.GetInstance<DisposableSingleton>();
singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
singleton.WasDisposed.ShouldBeFalse();
// now, dispose the Container
container.Dispose();
// the SingletonThing scoped object should be disposed
singleton.WasDisposed.ShouldBeTrue();
}
Do note that objects created as the singleton scope will be disposed when the Container is disposed if they
implement the IDisposable
interface:
public interface IUnitOfWork
{
}
public class DefaultUnitOfWork : IUnitOfWork
{
}
public class Worker1
{
public IUnitOfWork Uow { get; set; }
public Worker1(IUnitOfWork uow)
{
Uow = uow;
}
}
public class Worker2
{
public IUnitOfWork Uow { get; set; }
public Worker2(IUnitOfWork uow)
{
Uow = uow;
}
}
public class WorkerCoordinator
{
public IUnitOfWork Uow { get; set; }
public Worker1 Worker1 { get; set; }
public Worker2 Worker2 { get; set; }
public WorkerCoordinator(IUnitOfWork uow, Worker1 worker1, Worker2 worker2)
{
Uow = uow;
Worker1 = worker1;
Worker2 = worker2;
}
}
[Fact]
public void transient_scoped_Instance_is_built_once_per_resolution_to_the_Container()
{
var container = new Container(_ => { _.For<IUnitOfWork>().Use<DefaultUnitOfWork>(); });
var coordinator = container.GetInstance<WorkerCoordinator>();
// The IUnitOfWork object instance is the same for
// all the objects in the object graph that had
// a constructor dependency on IUnitOfWork
coordinator.Uow
.ShouldBeTheSameAs(coordinator.Worker1.Uow);
coordinator.Uow
.ShouldBeTheSameAs(coordinator.Worker2.Uow);
coordinator.Worker1.Uow
.ShouldBeTheSameAs(coordinator.Worker2.Uow);
}
ContainerScoped
New in StructureMap 4.0 is the ContainerScoped lifecycle that was designed specifically for compliance with the new ASP.Net DNX adapter model.
ContainerScoped in this case means that a registration will be built once per Container
, such that the root container, any child or profile
container, and every single nested container will build its own object instance.
The acceptance test for ContainerScoped
is shown below:
[Fact]
public void container_scoping_with_root_child_and_nested_container()
{
var container = new Container(_ =>
{
_.ForConcreteType<Disposable>().Configure.ContainerScoped();
});
var child = container.CreateChildContainer();
var nested = container.GetNestedContainer();
// Always the same object when requested from the root container
var mainDisposable = container.GetInstance<Disposable>();
mainDisposable
.ShouldBeTheSameAs(container.GetInstance<Disposable>());
// Always the same object when requested from a child container
var childDisposable = child.GetInstance<Disposable>();
childDisposable
.ShouldBeTheSameAs(child.GetInstance<Disposable>());
// Always the same object when requested from a nested container
var nestedDisposable = nested.GetInstance<Disposable>();
nestedDisposable
.ShouldBeTheSameAs(nested.GetInstance<Disposable>());
// It should be a different object instance for
// all three containers
mainDisposable
.ShouldNotBeTheSameAs(childDisposable);
mainDisposable
.ShouldNotBeTheSameAs(nestedDisposable);
childDisposable
.ShouldNotBeTheSameAs(nestedDisposable);
// When the nested container is disposed,
// it should dispose all the container scoped objects,
// but not impact the other containers
nested.Dispose();
nestedDisposable.WasDisposed.ShouldBeTrue();
childDisposable.WasDisposed.ShouldBeFalse();
mainDisposable.WasDisposed.ShouldBeFalse();
// Same for the child container
child.Dispose();
childDisposable.WasDisposed.ShouldBeTrue();
mainDisposable.WasDisposed.ShouldBeFalse();
// Same for the main container
container.Dispose();
mainDisposable.WasDisposed.ShouldBeTrue();
}
ThreadLocal
The ThreadLocalStorage based lifecycle is seldom used, but the easiest example of using it and explanation is the integration test:
public class ThreadLocalStorageLifecycleTester
{
public ThreadLocalStorageLifecycleTester()
{
_lifecycle = new ThreadLocalStorageLifecycle();
container = new Container(x => x.For<Rule>(Lifecycles.ThreadLocal).Use(() => new ColorRule("Red")));
}
private ThreadLocalStorageLifecycle _lifecycle;
private ColorRule _rule1;
private ColorRule _rule2;
private ColorRule _rule3;
private Container container;
private void findRule1()
{
_rule1 = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
var rule = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
_rule1.ShouldBeTheSameAs(rule);
}
private void findRule2()
{
_rule2 = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
var rule = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
_rule2.ShouldBeTheSameAs(rule);
}
private void findRule3()
{
_rule3 = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
var rule = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
_rule3.ShouldBeTheSameAs(rule);
rule = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
_rule3.ShouldBeTheSameAs(rule);
rule = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
_rule3.ShouldBeTheSameAs(rule);
rule = container.GetInstance<Rule>().ShouldBeOfType<ColorRule>();
_rule3.ShouldBeTheSameAs(rule);
}
[Fact]
public void object_has_been_created()
{
container.Model.For<Rule>().Default.ObjectHasBeenCreated().ShouldBeFalse();
var r1 = container.GetInstance<Rule>();
container.Model.For<Rule>().Default.ObjectHasBeenCreated().ShouldBeTrue();
}
[Fact]
public void FindUniqueInstancePerThread()
{
var t1 = new Thread(findRule1);
var t2 = new Thread(findRule2);
var t3 = new Thread(findRule3);
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
_rule1.ShouldNotBeTheSameAs(_rule2);
_rule1.ShouldNotBeTheSameAs(_rule3);
_rule2.ShouldNotBeTheSameAs(_rule3);
(_rule1.ID != _rule2.ID).ShouldBeTrue();
(_rule1.ID != _rule3.ID).ShouldBeTrue();
(_rule2.ID != _rule3.ID).ShouldBeTrue();
}
}