The single best source for information about the particulars of child container behavior is to look through the acceptance tests for child containers and profiles. from the code in GitHub.
Child Containers
The easiest way to explain a child container is to just show it in action:
[Fact]
public void show_a_child_container_in_action()
{
var parent = new Container(_ =>
{
_.For<IWidget>().Use<AWidget>();
_.For<IService>().Use<AService>();
});
// Create a child container and override the
// IService registration
var child = parent.CreateChildContainer();
child.Configure(_ =>
{
_.For<IService>().Use<ChildSpecialService>();
});
// The child container has a specific registration
// for IService, so use that one
child.GetInstance<IService>()
.ShouldBeOfType<ChildSpecialService>();
// The child container does not have any
// override of IWidget, so it uses its parent's
// configuration to resolve IWidget
child.GetInstance<IWidget>()
.ShouldBeOfType<AWidget>();
}
Child Containers are a mechanism to make a completely new Container
that can override some of the parent Container
's registrations but still
fall back to the parent Container
to fulfill any request that is not explicitly configured to the child container. The behavior of a child container
in how it resolves services and allows you to override the parent container is very similar to a nested container, but the crucial difference is in how the two concepts handle lifecycles and calling IDisposable.Dispose().
A couple salient facts about child containers that should (knock on wood) dispel the confusion about when and why to use them versus a nested container:
- Child Containers do not change how the default transient lifecycle behaves.
- Child Containers (and Profiles) were intended to be used to establish different service resolutions for different subsystems of the running system or to isolate registration overrides for specific types of users or customers (multi-tenancy).
Profiles
Profiles were completely redesigned as part of the big 3.0 release.
Profiles are just named child containers that may be configured upfront through Registry
configurations.
Profiles are one of the oldest features that date back to the very beginning of StructureMap. Originally profiles were conceived of as
a way to vary StructureMap registrations by development environment as the code moved from running locally on a developer's box to testing
servers to production. While that usage is still valid, it is probably more common to use profiles to define overrides for how StructureMap
should resolve services in different modes of the application (connected vs offline) or different types of system users.
[Fact]
public void Add_default_instance_with_concrete_type()
{
IContainer container = new Container(registry =>
{
registry.Profile("something", p =>
{
p.For<IWidget>().Use<AWidget>();
p.For<Rule>().Use<DefaultRule>();
});
});
var profile = container.GetProfile("something");
profile.GetInstance<IWidget>().ShouldBeOfType<AWidget>();
profile.GetInstance<Rule>().ShouldBeOfType<DefaultRule>();
}
Child Containers and Singletons
If you register a new Instance
directly to a child container, that registration is really scoped as a
singleton within the usage of that particular child container:
[Fact]
public void singletons_to_child_container_are_isolated()
{
var parentContainer = new Container(_ =>
{
_.For<IDependency>().Use<Dependency>();
});
var child1 = parentContainer.CreateChildContainer();
child1.Configure(x =>
{
x.ForSingletonOf<IRoot>().Use<Root>();
});
var child2 = parentContainer.CreateChildContainer();
child2.Configure(x =>
{
x.ForSingletonOf<IRoot>().Use<Root>();
});
// IRoot is a "singleton" within child1 usage
child1.GetInstance<IRoot>().ShouldBeSameAs(child1.GetInstance<IRoot>());
// IRoot is a "singleton" within child2 usage
child2.GetInstance<IRoot>().ShouldBeSameAs(child2.GetInstance<IRoot>());
// but, child1 and child2 both have a different IRoot
child1.GetInstance<IRoot>()
.ShouldNotBeTheSameAs(child2.GetInstance<IRoot>());
}
Creating a Nested Container from a Child Container
It is perfectly valid to create a nested container from a child container for short-lived requests or transactions:
[Fact]
public void nested_container_from_child()
{
var parent = new Container(_ =>
{
_.For<IWidget>().Use<AWidget>();
_.For<IService>().Use<AService>();
});
// Create a child container and override the
// IService registration
var child = parent.CreateChildContainer();
child.Configure(_ =>
{
_.For<IService>().Use<ChildSpecialService>();
});
using (var nested = child.GetNestedContainer())
{
nested.GetInstance<IService>()
.ShouldBeOfType<ChildSpecialService>();
}
}
Example: Child Containers in Automated Testing
My shop has been somewhat successful in writing automated integration tests in a whitebox testing style where we happily "swap out" a handful of external services with a stubbed service that we can happily control to either setup system state or measure the interaction of our system with the stub (outgoing emails etc) -- with the canonical example being a web service that our biggest application has to call for authentication purposes that is frequently unavailable during our development efforts and not quite reliable even when it is available:.
That's great, and the stubs certainly help the testing efforts be more productive. Until the stateful stubs we inject in one test end up bleeding into another test as de facto shared state and making those tests fail in ways that were very difficult to diagnose. To combat this problem of test isolation, we've introduced the idea of using a clean child container per integration test so that the test harness tools can happily swap in stubs without impacting other tests in the test suite.
In action, that usage looks something like this:
public class TheRealService : IService { }
public class StubbedService : IService { }
[Fact]
public void in_testing()
{
var container = new Container(_ =>
{
_.For<IService>().Use<TheRealService>();
});
// Create a new child container for only this test
var testContainer = container.CreateChildContainer();
// Override a service with a stub that's easier to control
var stub = new StubbedService();
testContainer.Inject<IService>(stub);
// Now, continue with the test resolving application
// services through the new child container....
}