Object Lifetime Management¶
Object Lifetime Management is the concept of controlling the number of instances a configured service will have and the duration of the lifetime of those instances. In other words, it allows you to determine how returned instances are cached. Most DI libraries have sophisticated mechanisms for lifestyle management, and Simple Injector is no exception with built-in support for the most common lifestyles. The three default lifestyles (transient, scoped and singleton) are part of the core library. Implementations for the scoped lifestyle can be found within some of the extension and integration packages. The built-in lifestyles will suit about 99% of cases. For anything else custom lifestyles can be used.
Below is a list of the most common lifestyles with code examples of how to configure them using Simple Injector:
Lifestyle | Description | Disposal |
---|---|---|
Transient | A new instance of the component will be created each time the service is requested from the container. If multiple consumers depend on the service within the same graph, each consumer will get its own new instance of the given service. | Never |
Scoped | For every request within an implicitly or explicitly defined scope. | Instances will be disposed when their scope ends. |
Singleton | There will be at most one instance of the registered service type and the container will hold on to that instance until the container is disposed or goes out of scope. Clients will always receive that same instance from the container. | Instances will be disposed when the container is disposed. |
Many different platform and framework-specific flavors are available for the Scoped lifestyle. Please see the Scoped section for more information.
Further reading:
Transient Lifestyle¶
This example instantiates a new IService implementation for each call, while leveraging the power of Auto-Wiring.
container.Register<IService, RealService>(Lifestyle.Transient);
// Alternatively, you can use the following short cut
container.Register<IService, RealService>();
The next example instantiates a new RealService instance on each call by using a delegate.
container.Register<IService>(
() => new RealService(new SqlRepository()),
Lifestyle.Transient);
Simple Injector considers Transient registrations to be lasting for only a short time; temporary, i.e. short lived and not reused. For that reason, Simple Injector prevents the injection of Transient components into Scoped and Singleton consumers as they are expected to be longer lived, which would otherwise result in Lifestyle Mismatches.
Singleton Lifestyle¶
There are multiple ways to register singletons. The most simple and common way to do this is by specifying both the service type and the implementation as generic type arguments. This allows the implementation type to be constructed using automatic constructor injection:
container.Register<IService, RealService>(Lifestyle.Singleton);
You can also use the RegisterInstance<T>(T) method to assign a constructed instance manually:
var service = new RealService(new SqlRepository());
container.RegisterInstance<IService>(service);
There is also a RegisterSingleton<T> overload that takes an Func<T> delegate. The container guarantees that this delegate is called only once:
container.Register<IService>(
() => new RealService(new SqlRepository()),
Lifestyle.Singleton);
// Or alternatively:
container.RegisterSingleton<IService>(() => new RealService(new SqlRepository()));
Alternatively, when needing to register a concrete type as singleton, you can use the parameterless RegisterSingleton<T>() overload. This will inform the container to automatically construct that concrete type (at most) once, and return that instance on each request:
container.RegisterSingleton<RealService>();
// Which is a more convenient short cut for:
container.Register<RealService, RealService>(Lifestyle.Singleton);
Scoped Lifestyle¶
The Scoped lifestyle behaves much like the Singleton lifestyle within a single, well-defined scope or request. A Scoped instance, however, isn’t shared across scopes. Each scope has its own cache of associated dependencies.
The Scoped lifestyle is useful for applications where you run a single operation in an isolated manner. Web applications are a great example, as you want to run a request in isolation from other requests. The same can hold for desktop applications. A press of a button can be seen as a form of a request, and you might wish to isolate such request. This can be done with the use of the Scoped lifestyle.
In frameworks where Simple Injector supplies out-of-the-box integration for (see the integration guide), the integration package will typically wrap a scope around a request on your behalf. This is what we call an implicitly defined scope, as you are not responsible for defining the scope–the integration package is.
In other situations, where there is no integration package available or, alternatively, you wish to start an operation outside the facilities that the integration package provides, you should start your own scope. This can happen when you wish to run an operation on a background thread of a web application, or when you want start a new operation when running inside a Windows service. When you manage the scope yourself, we call this an explicitly defined scope, as you are directly responsible for the creation and disposing of that scope.
Simple Injector contains the following scoped lifestyles:
Lifestyle | Description | Disposal |
---|---|---|
Thread Scoped | Within a certain (explicitly defined) scope, there will be only one instance of a given service type A created scope is specific to one particular thread, and can’t be moved across threads. | Instance will be disposed when their scope gets disposed. |
Async Scoped | There will be only one instance of a given service type within a certain (explicitly defined) scope. This scope will automatically flow with the logical flow of control of asynchronous methods. | Instance will be disposed when their scope gets disposed. |
Web Request | Only one instance will be created by the container per web request. Use this lifestyle in ASP.NET Web Forms and ASP.NET MVC applications. | Instances will be disposed when the web request ends. |
WCF Operation | Only one instance will be created by the container during the lifetime of the WCF service class. | Instances will be disposed when the WCF service class is released. |
Web Request and WCF Operation implement scoping implicitly, which means that the user does not have to start or finish the scope to allow the lifestyle to end and to dispose cached instances. The Container does this for you. With the Thread Scoped and Async Scoped lifestyles on the other hand, you explicitly define a scope (just like you would do with .NET’s TransactionScope class).
Most of the time, you will only use one particular scoped lifestyle per application. To simplify this, Simple Injector allows configuring the default scoped lifestyle in the container. After configuring the default scoped lifestyle, the rest of the configuration can access this lifestyle by calling Lifestyle.Scoped, as can be seen in the following example:
var container = new Container();
// Set the scoped lifestyle one directly after creating the container
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
// Use the Lifestyle.Scoped everywhere in your configuration.
container.Register<IUserContext, AspNetUserContext>(Lifestyle.Scoped);
container.Register<MyAppUnitOfWork>(() => new MyAppUnitOfWork("constr"),
Lifestyle.Scoped);
Just like Singleton registrations, instances of scoped registrations that are created by the container will be disposed when the their scope ends. Scoped lifestyles are especially useful for implementing patterns such as the Unit of Work.
Order of disposal¶
When a component A depends on component B, B will be created before A. This means that A will be disposed before B (assuming both implement IDisposable). This allows A to use B while A is being disposed.
Retrieving list of container-created disposables¶
By calling Scope.GetDisposables, the scope’s created, and cached, Scoped instances that implement IDisposable are returned. This list of instances will get disposed automatically, when the Scope instance is disposed.
Retrieving the disposable instances can be especially beneficial whenever you require asynchronous disposal. It is impossible for Simple Injector to apply asynchronous disposal, because that requires a framework-supplied abstraction that allows asynchronous disposal, e.g an IAsyncDisposable. Such abstraction however does not exist (yet).
To mitigate this, you can define your own abstraction that allows disposable objects to flush themselves asynchronously, in such way that their Dispose() method will not cause any blocking operations. Using the Scope.GetDisposables method, the following code can be used before disposing the Scope instance:
foreach (var flushable in scope.GetDisposables().OfType<IAsyncFlushable>().Reverse())
{
await flushable.FlushAsync();
}
The same method can be applied when asynchronously disposing instances that are created with the Singleton lifestyle. Those instances are not stored in a request-specific Scope instance, but stored for the lifetime of the container. The following code can be used before disposing the Container instance:
var disposabless = container.ContainerScope.GetDisposables();
foreach (var flushable in disposables.OfType<IAsyncFlushable>().Reverse())
{
await flushable.FlushAsync();
}
container.Dispose();
Thread Scoped Lifestyle¶
SimpleInjector.Lifestyles.ThreadScopedLifestyle is part of the Simple Injector core library. The following examples shows its typical usage:
var container = new Container();
container.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle();
container.Register<IUnitOfWork, NorthwindContext>(Lifestyle.Scoped);
Within an explicitly defined scope, there will be only one instance of a service that is defined with the Thread Scoped lifestyle:
using (ThreadScopedLifestyle.BeginScope(container))
{
var uow1 = container.GetInstance<IUnitOfWork>();
var uow2 = container.GetInstance<IUnitOfWork>();
Assert.AreSame(uow1, uow2);
}
Outside the context of a thread scoped lifestyle, i.e. using (ThreadScopedLifestyle.BeginScope(container)) no instances can be created. An exception is thrown when a thread scoped registration is requested outside of a scope instance.
Scopes can be nested and each scope will get its own set of instances:
using (ThreadScopedLifestyle.BeginScope(container))
{
var outer1 = container.GetInstance<IUnitOfWork>();
var outer2 = container.GetInstance<IUnitOfWork>();
Assert.AreSame(outer1, outer2);
using (ThreadScopedLifestyle.BeginScope(container))
{
var inner1 = container.GetInstance<IUnitOfWork>();
var inner2 = container.GetInstance<IUnitOfWork>();
Assert.AreSame(inner1, inner2);
Assert.AreNotSame(outer1, inner1);
}
}
Async Scoped Lifestyle (async/await)¶
This lifestyle is meant for applications that work with the new asynchronous programming model.
SimpleInjector.Lifestyles.AsyncScopedLifestyle is part of the Simple Injector core library. The following examples shows its typical usage:
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<IUnitOfWork, NorthwindContext>(Lifestyle.Scoped);
Within an explicitly defined scope, there will be only one instance of a service that is defined with the Async Scoped lifestyle:
using (AsyncScopedLifestyle.BeginScope(container))
{
var uow1 = container.GetInstance<IUnitOfWork>();
await SomeAsyncOperation();
var uow2 = container.GetInstance<IUnitOfWork>();
await SomeOtherAsyncOperation();
Assert.AreSame(uow1, uow2);
}
Outside the context of an active async scope no instances can be created. An exception is thrown when this happens.
Scopes can be nested and each scope will get its own set of instances:
using (AsyncScopedLifestyle.BeginScope(container))
{
var outer1 = container.GetInstance<IUnitOfWork>();
await SomeAsyncOperation();
var outer2 = container.GetInstance<IUnitOfWork>();
Assert.AreSame(outer1, outer2);
using (AsyncScopedLifestyle.BeginScope(container))
{
var inner1 = container.GetInstance<IUnitOfWork>();
await SomeOtherAsyncOperation();
var inner2 = container.GetInstance<IUnitOfWork>();
Assert.AreSame(inner1, inner2);
Assert.AreNotSame(outer1, inner1);
}
}
Web Request Lifestyle¶
The ASP.NET Integration NuGet Package is available (and available as SimpleInjector.Integration.Web.dll in the default download) contains a WebRequestLifestyle class that enable easy Per Web Request registrations:
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
container.Register<IOrderRepository, SqlOrderRepository>(Lifestyle.Scoped);
Async Scoped lifestyle vs. Web Request lifestyle¶
The lifestyles and scope implementations Web Request and Async Scoped in Simple Injector are based on different technologies. AsyncScopedLifestyle works well both inside and outside of IIS. i.e. It can function in a self-hosted Web API project where there is no HttpContext.Current. As the name implies, an async scope registers itself and flows with async operations across threads (e.g. a continuation after await on a different thread still has access to the scope regardless of whether ConfigureAwait() was used with true or false).
In contrast, the Scope of the WebRequestLifestyle is stored within the HttpContext.Items dictionary. The HttpContext can be used with Web API when it is hosted in IIS but care must be taken because it will not always flow with the async operation, because the current HttpContext is stored in the IllogicalCallContext (see Understanding SynchronizationContext in ASP.NET). If you use await with ConfigureAwait(false) the continuation may lose track of the original HttpContext whenever the async operation does not execute synchronously. A direct effect of this is that it would no longer be possible to resolve the instance of a previously created service with WebRequestLifestyle from the container (e.g. in a factory that has access to the container) - and an exception would be thrown because HttpContext.Current would be null.
The recommendation is to use AsyncScopedLifestyle in applications that solely consist of a Web API (or other asynchronous technologies such as ASP.NET Core) and use WebRequestLifestyle for applications that contain a mixture of Web API and MVC.
AsyncScopedLifestyle offers the following benefits when used in Web API:
- The Web API controller can be used outside of IIS (e.g. in a self-hosted project)
- The Web API controller can execute free-threaded (or multi-threaded) async methods because it is not limited to the ASP.NET SynchronizationContext.
For more information, check out the blog entry of Stephen Toub regarding the difference between ExecutionContext and SynchronizationContext.
WCF Operation Lifestyle¶
The WCF Integration NuGet Package is available (and available as SimpleInjector.Integration.Wcf.dll in the default download) contains a WcfOperationLifestyle class that enable easy Per WCF Operation registrations:
var container = new Container();
container.Options.DefaultScopedLifestyle = new WcfOperationLifestyle();
container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
container.Register<IOrderRepository, SqlOrderRepository>(Lifestyle.Scoped);
For more information about integrating Simple Injector with WCF, please see the WCF integration guide.
Per Graph Lifestyle¶
Compared to Transient, there will be just a single instance per explicit call to the container, while Transient services can have multiple new instances per explicit call to the container. This lifestyle is not supported by Simple Injector but can be simulated by using one of the Scoped lifestyles.
Instance Per Dependency Lifestyle¶
This lifestyle behaves the same as the built-in Transient lifestyle, but the intend is completely different. A Transient instance is expected to have a very short lifestyle and injecting it into a consumer with a longer lifestyle (such as Singleton) is an error. Simple Injector will prevent this from happening by checking for lifestyle mismatches. With the Instance Per Dependency lifestyle on the other hand, the created component is expected to stay alive as long as the consuming component does. So when the Instance Per Dependency component is injected into a Singleton component, we intend it to be kept alive by its consumer.
This lifestyle is deliberately left out of Simple Injector, because its usefulness is very limited compared to the Transient lifestyle. It ignores lifestyle mismatch checks and this can easily lead to errors, and it ignores the fact that application components should be immutable. In case a component is immutable, it’s very unlikely that each consumer requires its own instance of the injected dependency.
Per Thread Lifestyle¶
This lifestyle is deliberately left out of Simple Injector because it is considered to be harmful. Instead of using Per-Thread lifestyle, you will usually be better of using the Thread Scoped Lifestyle.
Per HTTP Session Lifestyle¶
This lifestyle is deliberately left out of Simple Injector because it is be used with care. Instead of using Per HTTP Session lifestyle, you will usually be better of by writing a stateless service that can be registered as singleton and let it communicate with the ASP.NET Session cache to handle cached user-specific data.
Hybrid Lifestyle¶
Simple Injector has no built-in hybrid lifestyles, but has a simple mechanism for defining them:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
defaultLifestyle: new ThreadScopedLifestyle(),
fallbackLifestyle: new WebRequestLifestyle());
container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
container.Register<ICustomerRepository, SqlCustomerRepository>(Lifestyle.Scoped);
In the example a hybrid lifestyle is defined wrapping the Thread Scoped Lifestyle and the Web Request Lifestyle. This hybrid lifestyle will use the ThreadScopedLifestyle, but will fall back to the WebRequestLifestyle in case there is no active thread scope.
A hybrid lifestyle is useful for registrations that need to be able to dynamically switch lifestyles throughout the lifetime of the application. The shown hybrid example might be useful in a web application, where some operations need to be run in isolation (with their own instances of scoped registrations such as unit of works) or run outside the context of an HttpContext (in a background thread for instance).
Please note though that when the lifestyle doesn’t have to change throughout the lifetime of the application, a hybrid lifestyle is not needed. A normal lifestyle can be registered instead:
bool runsOnWebServer = ReadConfigurationValue<bool>("RunsOnWebServer");
var container = new Container();
container.Options.DefaultScopedLifestyle =
runsOnWebServer ? new WebRequestLifestyle() : new ThreadScopedLifestyle();
container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
container.Register<ICustomerRepository, SqlCustomerRepository>(Lifestyle.Scoped);
Developing a Custom Lifestyle¶
The lifestyles supplied by Simple Injector should be sufficient for most scenarios, but in rare circumstances defining a custom lifestyle might be useful. This can be done by creating a class that inherits from Lifestyle and let it return Custom Registration instances. This however is a lot of work, and a shortcut is available in the form of the Lifestyle.CreateCustom.
A custom lifestyle can be created by calling the Lifestyle.CreateCustom factory method. This method takes two arguments: the name of the lifestyle to create (used mainly by the Diagnostic Services) and a CreateLifestyleApplier delegate:
public delegate Func<object> CreateLifestyleApplier(
Func<object> transientInstanceCreator)
The CreateLifestyleApplier delegate accepts a Func<object> that allows the creation of a transient instance of the registered type. This Func<object> is created by Simple Injector supplied to the registered CreateLifestyleApplier delegate for the registered type. When this Func<object> delegate is called, the creation of the type goes through the Simple Injector pipeline. This keeps the experience consistent with the rest of the library.
When Simple Injector calls the CreateLifestyleApplier, it is your job to return another Func<object> delegate that applies the caching based on the supplied instanceCreator. A simple example would be the following:
var sillyTransientLifestyle = Lifestyle.CreateCustom(
name: "Silly Transient",
// instanceCreator is of type Func<object>
lifestyleApplierFactory: instanceCreator =>
{
// A Func<object> is returned that applies caching.
return () => instanceCreator.Invoke();
});
var container = new Container();
container.Register<IService, MyService>(sillyTransientLifestyle);
Here we create a custom lifestyle that applies no caching and simply returns a delegate that will on invocation always call the wrapped instanceCreator. Of course this would be rather useless and using the built-in Lifestyle.Transient would be much better in this case. It does however demonstrate its use.
The Func<object> delegate that you return from your CreateLifestyleApplier delegate will get cached by Simple Injector per registration. Simple Injector will call the delegate once per registration and stores the returned Func<object> for reuse. This means that each registration will get its own Func<object>.
Here’s an example of the creation of a more useful custom lifestyle that caches an instance for 10 minutes:
var tenMinuteLifestyle = Lifestyle.CreateCustom(
name: "Absolute 10 Minute Expiration",
lifestyleApplierFactory: instanceCreator =>
{
TimeSpan timeout = TimeSpan.FromMinutes(10);
var syncRoot = new object();
var expirationTime = DateTime.MinValue;
object instance = null;
return () =>
{
lock (syncRoot)
{
if (expirationTime < DateTime.UtcNow)
{
instance = instanceCreator.Invoke();
expirationTime = DateTime.UtcNow.Add(timeout);
}
return instance;
}
};
});
var container = new Container();
// We can reuse the created lifestyle for multiple registrations.
container.Register<IService, MyService>(tenMinuteLifestyle);
container.Register<AnotherService, MeTwoService>(tenMinuteLifestyle);
In this example the Lifestyle.CreateCustom method is called and supplied with a delegate that returns a delegate that applies the 10 minute cache. This example makes use of the fact that each registration gets its own delegate by using four closures (timeout, syncRoot, expirationTime and instance). Since each registration (in the example IService and AnotherService) will get its own Func<object> delegate, each registration gets its own set of closures. The closures are therefore static per registration.
One of the closure variables is the instance and this will contain the cached instance that will change after 10 minutes has passed. As long as the time hasn’t passed, the same instance will be returned.
Since the constructed Func<object> delegate can be called from multiple threads, the code needs to do its own synchronization. Both the DateTime comparison and the DateTime assignment are not thread-safe and this code needs to handle this itself.
Do note that even though locking is used to synchronize access, this custom lifestyle might not work as expected, because when the expiration time passes while an object graph is being resolved, it might result in an object graph that contains two instances of the registered component, which might not be what you want. This example therefore is only for demonstration purposes.