Nested Container's are a powerful feature in StructureMap for service resolution and clean object disposal in the context of short lived operations. Nested Container's were introduced in version 2.6, but greatly improved in both performance (100X reduction in the time to create a nested container in a large application) and ahem lifecycle mechanics as a major goal of the 3.0 release.
History
The original use case and impetus for building this feature was a simplistic message handling system that dequeued
messages from a Sql Server table (please forget for a second the wisdom of using Sql Server as a queueing system), deserialized
the contents into a .Net object, then created the proper handler object for that message type and executed that handler -- all within
a single transaction. What we wanted at the time was a way to track and clean up all IDisposable
objects created during the lifespan of each
transaction. We also wanted a new type of object lifecycle where objects like the NHibernate ISession would be shared by every object
created during the lifetime of the nested container -- even if the ISession
was resolved lazily after the intial
resolution of the message handler. The result was what is now the nested container feature
of StructureMap.
Why Nested Containers over HttpContext or ThreadLocal Scoping?
Why not just use HttpContext
based lifecycles like we've always done in the past? Because HttpContext
is not supported by any
type of OWIN web host and will not be a part of ASP.Net vNext. Using a Nested Container per HTTP request is a better, lighterweight way
to scope services to an HTTP request without coupling your code to what will soon be legacy ASP.Net runtime code.
Who uses it?
At the time of this document, Nested Container's per HTTP request are supported by frameworks like FubuMVC, ASP.Net MVC through the StructureMap.MVC5 nuget package, and Web API with the StructureMap.WebApi2 nuget. Several service bus frameworks also use a StructureMap nested container per message invocation including FubuTransportation, MassTransit, and NServiceBus.
Creation
Creating a nested container is as simple as calling the IContainer.GetNestedContainer()
method as shown below:
public interface IWorker
{
void DoWork();
}
public class Worker : IWorker, IDisposable
{
public void DoWork()
{
// do stuff!
}
public void Dispose()
{
// clean up
}
}
[Fact]
public void creating_a_nested_container()
{
// From an IContainer object
var container = new Container(_ => { _.For<IWorker>().Use<Worker>(); });
using (var nested = container.GetNestedContainer())
{
// This object is disposed when the nested container
// is disposed
var worker = nested.GetInstance<IWorker>();
worker.DoWork();
}
}
Lifecycle Rules
While StructureMap supports several object instance lifecycles out of the box, in idiomatic usage of StructureMap the only common lifecyles are:
Transient
- The default lifecycle. A new object is created for a configured Instance on each request to the containerSingleton
- One instance is constructed and used over the entire Container lifetime
In the context of a Nested Container however, the Transient
scoping now applies to the Nested Container itself:
[Fact]
public void nested_container_behavior_of_transients()
{
// "Transient" is the default lifecycle
// in StructureMap
var container = new Container(_ => { _.For<IColor>().Use<Green>(); });
// In a normal Container, a "transient" lifecycle
// Instance will be built up once in every request
// to the Container
container.GetInstance<IColor>()
.ShouldNotBeTheSameAs(container.GetInstance<IColor>());
// From a nested container, the "transient" lifecycle
// is tracked to the nested container
using (var nested = container.GetNestedContainer())
{
nested.GetInstance<IColor>()
.ShouldBeTheSameAs(nested.GetInstance<IColor>());
// One more time
nested.GetInstance<IColor>()
.ShouldBeTheSameAs(nested.GetInstance<IColor>());
}
}
Instances
scoped to anything but Transient
or AlwaysUnique
are resolved as normal, but through the parent container:
[Fact]
public void nested_container_usage_of_singletons()
{
var container = new Container(_ => { _.ForSingletonOf<IColorCache>().Use<ColorCache>(); });
var singleton = container.GetInstance<IColorCache>();
// SingletonThing's are resolved from the parent container
using (var nested = container.GetNestedContainer())
{
nested.GetInstance<IColorCache>()
.ShouldBeTheSameAs(singleton);
}
}
See Object Lifecycles for more information on supported object lifecycles.
Overriding Services from the Parent
A nested container is a new Container object that still retains access to the parent container that created it so that it can efficiently share registrations, policies, and cached build plans. You can, however, register services into the nested container that override the parent container.
The FubuMVC web framework uses a nested container per HTTP request. During an HTTP request, FubuMVC injects services for the current HTTP request and response to a nested container before creating the actual services that will handle the request. The FubuMVC mechanics are conceptually similar to this code sample:
[Fact]
public void overriding_services_in_a_nested_container()
{
var container = new Container(_ =>
{
_.For<IHttpRequest>().Use<StandInHttpRequest>();
_.For<IHttpResponse>().Use<StandInHttpResponse>();
});
var request = new HttpRequest();
var response = new HttpResponse();
using (var nested = container.GetNestedContainer())
{
// Override the HTTP request and response for this
// nested container
nested.Configure(_ =>
{
_.For<IHttpRequest>().Use(request);
_.For<IHttpResponse>().Use(response);
});
var handler = nested.GetInstance<HttpRequestHandler>();
handler.Request.ShouldBeTheSameAs(request);
handler.Response.ShouldBeTheSameAs(response);
}
// Outside the nested container, we still have the original
// registrations
container.GetInstance<IHttpRequest>()
.ShouldBeOfType<StandInHttpRequest>();
container.GetInstance<IHttpResponse>()
.ShouldBeOfType<StandInHttpResponse>();
}
When handling requests for new services, a nested container first checks its own configuration if it has its own explicit registration for the request. If the nested container does have an explicit registration, it uses that registration. Otherwise, a nested container will attempt to build an object using the registered configuration of its parent container.
Lazy Resolution
Nested container object lifecycles equally apply to objects resolved lazily with
either Lazy<T>
, Func<T>
, or Func<string, T>
as shown below:
public class Foo
{
}
public class FooHolder
{
public IContainer Container { get; set; }
public Func<Foo> Func { get; set; }
public Lazy<Foo> Lazy { get; set; }
public FooHolder(IContainer container, Func<Foo> func, Lazy<Foo> lazy)
{
Container = container;
Func = func;
Lazy = lazy;
}
}
[Fact]
public void service_location_and_container_resolution_inside_nested_containers()
{
var container = new Container();
using (var nested = container.GetNestedContainer())
{
var holder = nested.GetInstance<FooHolder>();
// The injected IContainer is the nested container
holder.Container.ShouldBeTheSameAs(nested);
// Func<T> and Lazy<T> values will be built by
// the nested container w/ the nested container
// scoping
var nestedFoo = nested.GetInstance<Foo>();
holder.Func().ShouldBeTheSameAs(nestedFoo);
holder.Lazy.Value.ShouldBeTheSameAs(nestedFoo);
}
}
IContainer
object injected above was the nested container that
created the FooHandler
object. If you do want to use service location within an object, just take in
IContainer
as a constructor dependency and you will always get the correctly scoped IContainer
.Profiles
You can created nested containers from profile containers as shown in the sample below:
[Fact]
public void nested_container_from_profile_container()
{
var container = new Container(x =>
{
x.For<IColor>().Use<Red>();
x.Profile("Blue", _ => _.For<IColor>().Use<Blue>());
x.Profile("Green", _ => _.For<IColor>().Use<Green>());
});
using (var nested = container.GetProfile("Blue").GetNestedContainer())
{
nested.GetInstance<IColor>().ShouldBeOfType<Blue>();
}
using (var nested = container.GetNestedContainer("Green"))
{
nested.GetInstance<IColor>().ShouldBeOfType<Green>();
}
}
See Profiles and Child Containers for more information about using profiles.
Disposing Services
As stated above, disposing a nested container will also dispose all objects created with the default Transient lifecycle by the
nested container that implement the IDisposable
interface. That behavior is demonstrated
below:
[Fact]
public void nested_container_disposal()
{
var container = new Container(_ =>
{
// A SingletonThing scoped service
_.ForSingletonOf<IColorCache>().Use<ColorCache>();
// A transient scoped service
_.For<IColor>().Use<Green>();
// An AlwaysUnique scoped service
_.For<Purple>().AlwaysUnique();
});
ColorCache singleton = null;
Green nestedGreen = null;
Blue nestedBlue = null;
Purple nestedPurple = null;
using (var nested = container.GetNestedContainer())
{
// SingletonThing's are really built by the parent
singleton = nested.GetInstance<IColorCache>()
.ShouldBeOfType<ColorCache>();
nestedGreen = nested.GetInstance<IColor>()
.ShouldBeOfType<Green>();
nestedBlue = nested.GetInstance<Blue>();
nestedPurple = nested.GetInstance<Purple>();
}
// Transients created by the Nested Container
// are disposed
nestedGreen.WasDisposed.ShouldBeTrue();
nestedBlue.WasDisposed.ShouldBeTrue();
// Unique's created by the Nested Container
// are disposed
nestedPurple.WasDisposed.ShouldBeTrue();
// NOT disposed because it's owned by
// the parent container
singleton.WasDisposed.ShouldBeFalse();
}
For the sake of clarity, the classes used in the sample above are:
public interface IColor
{
}
public class Red : IColor
{
}
public class Purple : Blue { }
public class Blue : IColor, IDisposable
{
public bool WasDisposed;
public void Dispose()
{
WasDisposed = true;
}
}
public class Green : IColor, IDisposable
{
public bool WasDisposed;
public void Dispose()
{
WasDisposed = true;
}
}
public interface IColorCache
{
}
public class ColorCache : IColorCache, IDisposable
{
public bool WasDisposed;
public void Dispose()
{
WasDisposed = true;
}
}