Fork me on GitHub

Nested Containers (Per Request/Transaction) Edit on GitHub


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:

  1. Transient - The default lifecycle. A new object is created for a configured Instance on each request to the container
  2. Singleton - 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);
    }
}

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;
    }
}