Index
So far I’ve shown how to do basic registration. This will let the container know of the type so that it will try to create it. This registration gives us the defaults that StructureMap assumes unless it is told otherwise. These defaults are reasonable for most types but they’re not always applicable. This post describes some of the options StructureMap provides for controlling how the container manages the lifecycle of instances.
By default StructureMap constructs instances transiently. Each time the instance is requested from the container a new instance is constructed and returned to the controller. We can demonstrate this with the following code snippet:
ObjectFactory.Initialize(initialise =>
{
initialise.For<ITransientService>().Use<TransientService>();
});
var firstTransientService = ObjectFactory.GetInstance<ITransientService>();
var secondTransientService = ObjectFactory.GetInstance<ITransientService>();
Console.WriteLine(string.Format("Transient instances are the same: {0}",
ReferenceEquals(firstTransientService, secondTransientService)));
When we run this we get the following output:
In general this is not an issue. Most types managed by StructureMap do not need to carry state between operations, and the construction of lightweight transient objects in .NET is very cheap. Unfortunately this doesn’t apply to all types. There are circumstances where we need to maintain state potentially across many operations. Some types are expensive to construct such that for performance reasons it simply isn’t feasible to keep creating new instances. Types may also wrap limited resources such that we may not be able to construct new instances if existing instances already exist. For this reason StructureMap supports lifecycles.
The lifecycle controls when new instances are created and how StructureMap maintains references to the. The transient default specifies that a new instance is created per request and that StructureMap will not track the instance. Additionally StructureMap provides the following lifecycles:
- Singleton
- Thread Local
- Unique Per Request
- Http Context
- Http Session
- Hybrid
- Hybrid Session
The Singleton lifecycle is a mechanism to provide Gang of Four Singleton semantics to an instance. Specifying this lifecycle causes StructureMap to create only a single instance of the concrete type. It holds a reference to this instance and provides it whenever that type is requested. We can specify this lifecycle by using the Singleton method in the fluent interface thusly:
initialise.For<ISingletonService>().Singleton().Use<SingletonService>();
If we now add to our example code the following:
var firstSingletonService = ObjectFactory.GetInstance<ISingletonService>();
var secondSingletonService = ObjectFactory.GetInstance<ISingletonService>();
Console.WriteLine(string.Format("Singleton instances are the same: {0}",
ReferenceEquals(firstSingletonService, secondSingletonService)));
We get the output:
The Singleton lifecycle is effective for when global state must be managed or an instance is expensive to create. It is also useful to manage cases where a limited set of resources must be controlled.
Implementing Singleton manually is generally a bad thing. Manual Singletons result in tight coupling and are difficult to test. The Singleton lifecycle gets around this problem by passing the problem of ensuring that there is only a single instance over to the container. The type marked with the Singleton lifecycle may be implemented as a standard instance type. It may have dependencies to be satisfied as can any other type managed by the container. The only consideration is that the type must be threadsafe when used in multi-threaded environments as the same instance will be provided on all threads.
Sometimes we wish to manage instances on a per-thread basis rather than globally and for this reason StructureMap provides the Thread Local lifecycle. This lifecycle creates and maintains an instance for each thread that requests the plugin type. Two requests on the same thread will receive the same instance whereas requests on different threads will be given different instances. This is often useful for tracking execution state within a thread or for managing expensive resources that are not threadsafe.
Using thread local storage in web applications is potentially problematic due to the way ASP.NET handles threads. StructureMap therefore provides lifecycles that use ASP.NET constructs to store data. These are Http Context and Http Session which quite naturally use the ASP.NET Context and Session storage respectively. Using these lifecycles gives instances the lifecycle of the associated ASP.NET storage. You may apply Http Context using the HttpContextScoped method. For Http Session there is no helper method but you can apply the scope by calling the LifecycleIs method (for which the Singleton and HttpContextScoped methods are convenient wrappers) as shown:
initialise.For<IHttpSessionService>().LifecycleIs(new HttpSessionLifecycle()).Use<HttpSessionService>();
The standard restrictions for using these ASP.NET constructs apply. In particular care should be taken in what is places in the Session.
In many circumstances code need to be agnostic as to what kind of environment that it runs in. Using Http Context and Http Session ties the code to ASP.NET preventing its use in client or service applications. This scenario is covered by the Hybrid and Hybrid Session lifecycles. These will use the ASP.NET Context or Session (respectively) if it is available. Otherwise they will use thread local storage. Using these lifecycles allows code to run in many environments but instances must adhere to the restrictions of both types of scope. Failure to adhere to the restrictions of both thread local storage and ASP.NET Context (or Session) means that the code will fail in one environment even if it works correctly in the other. The Hybrid lifecycle may be applied using the HybridHttpOrThreadLocalStorage method. Hybrid Session is applied using the code:
initialise.For<IHybridSessionService>().LifecycleIs(new HybridSessionLifecycle()).Use<HybridSessionService>();
Of the built in lifecycles this leaves Unique Per Request. The behaviour of this lifecycle is somewhat more subtle. To illustrate it take this (somewhat contrived) class that requires two instances of ITransientService.
public interface IRequiresMultipleTransientServices
{
void MultipleTransientDoStuff();
}
public class RequiresMultipleTransientServices : IRequiresMultipleTransientServices
{
private readonly ITransientService _firstTransientService;
private readonly ITransientService _secondTransientService;
public RequiresMultipleTransientServices(ITransientService firstTransientService,
ITransientService secondTransientService)
{
_firstTransientService = firstTransientService;
_secondTransientService = secondTransientService;
}
public void MultipleTransientDoStuff()
{
Console.WriteLine(string.Format("Service instances are the same: {0}",
ReferenceEquals(_firstTransientService, _secondTransientService)));
}
}
If we run this we get the output:
What we see here is that in the context of a single call to ObjectFactory.GetInstance<T> the container constructs the ITransientService instance only once. Generally this behaviour is desirable or at least neutral. However we may have a case where we need all the instances to be different, for instance if they carry state that could get corrupted if used through different references that are unaware of each other. For this scenario we have the Unique Per Request lifecycle, which can be applied with:
initialise.For<IUniquePerRequestService>().LifecycleIs(new UniquePerRequestLifecycle()).Use<UniquePerRequestService>();
We can demonstrate the effect with:
public interface IRequiresMultipleUniquePerRequestServices
{
void MultipleUniquePerRequestDoStuff();
}
public class RequiresMultipleUniquePerRequestServices : IRequiresMultipleUniquePerRequestServices
{
private readonly IUniquePerRequestService _firstUniquePerRequestService;
private readonly IUniquePerRequestService _secondUniquePerRequestService;
public RequiresMultipleUniquePerRequestServices(IUniquePerRequestService firstUniquePerRequestService,
IUniquePerRequestService secondUniquePerRequestService)
{
_firstUniquePerRequestService = firstUniquePerRequestService;
_secondUniquePerRequestService = secondUniquePerRequestService;
}
public void MultipleUniquePerRequestDoStuff()
{
Console.WriteLine(string.Format("Unique per request service instances are the same: {0}",
ReferenceEquals(_firstUniquePerRequestService, _secondUniquePerRequestService)));
}
Giving the output:
By applying the Unique Per Request Lifecycle we now get a new instance of the dependency everywhere its requested.
Although a single instance requesting multiple undifferentiated instances of the same dependency is unlikely and generally unhelpful it is not at all unusual that the same dependency will appear multiple times within the same dependency tree for a complex dependency graph. The separate scenario where a type wants multiple distinct concrete dependencies that implement the same plugin type will be discussed later in this series.
Now that we can control the lifecycle of an instance the next post in this series will show how we can control how instances are created.