With the release of Simple Injector v5, I made an error of judgement. To support asynchronous disposal of the Container
and Scope
objects I added a dependency from the core library to the Microsoft.Bcl.AsyncInterfaces NuGet package. Unfortunately, this proved to be a very painful mistake. In this blog post I’ll explain why I choose to take this dependency, why this was a mistake, and how this finally got fixed in v5.2.
Double Trouble
With the introduction of .NET Core 3, Microsoft added a new IAsyncDisposable
interface as asynchronous counterpart of IDisposable
. In order enable asynchronous disposal in older framework versions (i.e. .NET Core 2.0 and .NET 4.6.1), Microsoft published the Microsoft.Bcl.AsyncInterfaces NuGet package. Where the package’s net461
and netstandard2.0
targets contain assemblies that specify—among other things—the IAsyncDisposable
interface, the netstandard2.1
target assembly uses type forwarding to reference the framework’s version.
The publication of the AsyncInterfaces NuGet packages allowed Simple Injector v5.0 to start using the IAsyncDisposable
interface. This allowed Simple Injector to asynchronously dispose of classes implementing IAsyncDisposable
. It also allowed users to dispose of both Scope
and Container
in an asynchronous fashion.
Historically, the Simple Injector core library hasn’t taken a dependency on an external NuGet package. And for good reason. Because external dependencies can cause conflicts, such as those pesky binding-redirect issues that we’re all too familiar with. This is an unfortunate restriction and there have been many times I wished I could just pull in some external package to simplify my work. The very useful System.Collections.Immutable package is one such example.
Unfortunately, there is no easy way of allowing Simple Injector to asynchronously dispose of registered types without taking a dependency on AsyncInterfaces. This left me with two options:
- Add this dependency to the core and implement asynchronous disposal directly in the core library
- Create a new NuGet package (e.g.,
SimpleInjector.Async
) that adds extensions that allow asynchronous disposal of Scope
and Container
.
I decided to go with the first option as the second would result in a less discoverable solution. The former option allows implementing IAsyncDisposable
directly on Container
and Scope
; the latter would likely rely on extension methods, to allow something similar to the following: await scope.AsAsyncDisposable().DisposeAsync();
.
This new feature was implemented and introduced in Simple Injector v5. This is when the trouble started.
Diamonds are forever
Within a few days of the release of v5, developers began reporting binding-redirect issues. This was caused by the multitude of versions that exist for the AsyncInterfaces package and its dependencies. Developers were using other libraries and framework parts that took a dependency on AsyncInterfaces, or its dependencies System.Threading.Tasks.Extensions and System.Runtime.CompilerServices.Unsafe. When one of the used application dependencies references a different version of such package (and in particular when the package’s contained assembly has a different assembly version) binding-redirect issues can appear.
In an ideal world, the NuGet package manager automatically solves binding-redirect issues for us by adding the required plumbing in the application’s configuration file. But for some reason, the package manager fails to do this. Instead, we have to manually add binding redirects to our configuration files. And this seems especially confusing with regards to AsyncInterfaces and its sub dependencies, as their assembly versions do not match the NuGet package version. The dependency chain of AsyncInterfaces seems to be in the list of common troublemakers.
With the introduction of .NET Core, there’s the idea that binding-redirect issues are thing of the past. A more-recent bug report, however, demonstrated that this isn’t always the case, demonstrating that these issues won’t easily go away if we wait long enough.
The pain experienced with these dependencies can be solved by setting the correct binding redirects in the application’s configuration file. To help Simple Injector users, I started posting binding direct examples that developers could copy-paste to fix their problem. But even using these examples, developers struggled, and I recently got stuck with this on an application I was developing. Whatever binding redirects I tried, after analyzing the assembly versions of the used NuGet packages, the application would crash with a dreaded “Could not load file or assembly X or one of its dependencies” exception. This was the moment that I started to realize the gravity of this Diamond Dependency dilemma that AsyncInterfaces and his little helpers caused. Action was required. This instigator had to go.
Diamond Dependency
My application took a dependency on both Simple Injector and System.Collections.Immutable. Those two libraries, however, both depended (indirectly) on the previously mentioned CompilerServices.Unsafe. Simple Injector did so via AsyncInterfaces and Tasks.Extensions, while Immutable depended on CompilerServices.Unsafe via System.Memory. This is an example of a Diamond Dependency.
A Diamond Dependency is a dependency chain of at least four libraries where library A (e.g. my application) depends on libraries B (Simple Injector) and C (Immutable). B and C than depend on the final library D (Unsafe). A problem emerges when those middle libraries require different versions of this final library D. This is called the Diamond Dependency Conflict.
If it quacks like a duck
But removing the AsyncInterfaces dependency was easier said than done. Removing the asynchronous disposal feature was not an option; developers already depend on that feature, often—but not always —implicitly by using one of the ASP.NET Core integration packages. And we can certainly expect asynchronous disposal to become more common soon, long before the Diamond Dependency problem will disappear (i.e. before Simple Injector can drop support for .NET 4 and .NET Core 2).
Over the last 6 months I have conducted two experiments that tried duck typing to allow removing the AsyncInterfaces dependency from the core library. With duck typing, instead of depending on interfaces, you take a more dynamic approach where you accept any type that conforms to a certain signature. The C# compiler takes this approach in many places, for instance with the foreach
, using
, and async using
keywords. Both trials were discontinued because of the complexity they would introduce into the library, especially when taking performance into consideration. But after I recently experienced the seriousness of the situation myself, I knew removing the party crasher was the only viable solution to this problem. And so, I had to release the Quacken!
After multiple days of trying, failing, testing, improving, asking, things started to quack like duck. The meat and potatoes of the implementation is inside the Scope
class (which now is littered with compiler directives). Scope
now does the following:
- It tries to figure out if a tracked instance implements an interface named “System.IAsyncDisposable”. Whether that interface is The Real Thing ™ or just some self-defined surrogate is irrelevant. If the instance implements that interface, it is stored for disposal, as would happen for ‘normal’
IDisposable
instances.
- During asynchronous disposal of the
Scope
, the Scope
will invoke the instance’s DisposeAsync
method. The returned object (typically Task
or ValueTask
) will be awaited.
Of course, performance must be taken into consideration, considering that Reflection calls are slow. Such a performance penalty would perhaps be acceptable during disposing of the Container
, but certainly not when disposing of a Scope
, as an application might create and dispose of thousands of Scope
instances per second. And so Scope
implements the following caching:
- Whether a checked type implements
IAsyncDisposable
or not. This way only one call to Type.GetInterfaces().Where(i => i.FullName == "System.IAsyncDisposable")
is required.
- When the
IAsyncInterface
is detected for the first time, it will be stored internally. This allows any subsequent checks to call asyncDisposableType.IsAssignableFrom(type)
instead of the slower GetInterfaces().Where(...)
again.
- The call to
IAsyncDisposable.DisposeAsync().AsTask()
is compiled using expression trees, just as the rest of Simple Injector does for object composition under the covers. This makes calling DisposeAsync
(almost) as fast as a native interface call. The .NET Standard 2.1 version, btw, completely skips all this duck typing nonsense and just natively calls IAsyncDisposable
because, as I mentioned previously, with .NET Core 3 that interface is recognized natively.
The greatest disadvantage of this approach, from a user’s perspective, is that I had to remove the DisposeAsync
methods from Container
and Scope
in the pre-.NET Standard 2.1 builds. Because not only did the removal of AsyncInterfaces mean no reference to IAsyncDisposable
, it also removed the reference to ValueTask
, which the IAsyncDisposable.DisposeAsync
method returns. For a while I played with the idea of the other builds to have an DisposeAsync
method that would simply return Task
. This would allow developers to use C#’s async using
syntax on Container
and Scope
. But I quickly realized that the different signature of the DisposeAsync
method (the return type is part of the signature) would cause MissingMethodExceptions
.
To prevent incompatible signatures, while still allowing both the Container
and Scope
to be disposed of asynchronously, methods with completely different names needed to be added. This is why you’ll find DisposeContainerAsync()
and DisposeScopeAsync()
methods on Container
and Scope
respectively. I’m the first to agree that this is bats-ugly, but it’s the best I could come up with.
On the flip side, however, because of the use of duck typing, Simple Injector can now support asynchronous disposal on all of its builds. Where previously asynchronous disposal was only supported on the net461
and netstandard2.0
builds of Simple Injector, with the introduction of Simple Injector v5.2, asynchronous disposal is supported on net45
and netstandard1.0
as well. Although its not possible to reference AsyncInterfaces’ IAsyncDisposable
in your application, you can simply define System.IAsyncDisposable
somewhere in your application, and it just works. Here’s an example:
namespace System
{
public interface IAsyncDisposable
{
Task DisposeAsync();
}
}
Even though the official IAsyncDisposable
interface exposes ValueTask
rather than Task
, Simple Injector accepts this alternative definition anyway. As long as the interface is called “System.IAsyncDisposable” and there’s a method named “DisposeAsync” which either returns Task
or ValueTask
, everything will just run smoothly. This allows you to start using asynchronous disposal until you can migrate to your code base to .NET Core 3 or .NET 5.
All the sweat and tears I poured over my keyboard in the past weeks to get this fixed are now dried up and materialized in the Simple Injector code v5.2 code base. This will certainly not fix all your binding-redirect issues but will at least ensure that Simple Injector is not amplifying the problem any longer.
Happy injecting.
It’s been 10 years since the birth of Simple Injector, and three years since we released Simple Injector 4.0. The number of features that mean bumping the major version number have been piling up on the backlog, and so we started work on the next major release a few months ago. And it’s finally here! We’ve removed legacy methods, improved performance, fixed bugs, added features, and continued to push the library towards a strategy of best-practice.
There are quite a few breaking changes, which will likely impact you when migrating from v4 to v5. There are two changes in particular that you should be aware of: the handling of unregistered concrete types and auto-verification. Please read on to understand what has changed and why.
In this blog post I describe the most prominent changes and their rational, starting with how v5 stops resolving unregistered concrete types.
Unregistered concrete types are no longer resolved.
Simple Injector has always promoted best practices, and this is an evolving process. Over the years, for instance, we figured out it was better to require:
- an active scope for resolving scoped instances
- collections to be registered, even if empty
- container-uncontrolled collections to be wrapped with transient decorators solely
These insights where gained during the development process and for each we decided to introduce a breaking change because we felt the breaking change was worth it.
Resolving unregistered concrete types is a similar case. While the ability to resolve and inject unregistered types can be an appealing concept, we have noticed that developers often trip over this behavior. In the past, we have introduced new verification features (such as the Short-Circuited Dependencies diagnostic warning) to help reduce issues but errors still occur.
Changing this behavior has been long on my radar and is something I discussed with the other contributors even before the release of v4—over three years ago. Unfortunately, at that time, we were too close to the release of v4 and needed more time to assess the impact to our users. That’s why we postponed the change to v5. Instead, we introduced a switch in v4 that allowed disabling this behavior and started promoting disabling this behavior in the documentation. This would allow new users and new projects to use the new settings and existing users to migrate at their own pace.
With the introduction of v5, we flipped the switch, meaning that resolution of unregistered concrete types is now disabled by default. We advise you keep the default behavior as-is and ensure you register all concrete types directly. If your current application heavily depends on unregistered concrete types being resolved, you can restore the old behavior by setting Container.Options.ResolveUnregisteredConcreteTypes
to true
. For more details, check the documentation.
But this is not the only big change we made. Another important change that will likely impact you is auto verification.
The container is now automatically verified when first resolved.
Just as it’s a good idea to explicitly register all types up front, we have learned that it is a good idea to trigger container verification automatically on first resolve.
Many developers using Simple Injector don’t realize its full potential and forget to call Container.Verify()
. We often see developers run into problems that a call to Verify()
would have prevented. This is why in v5 we decided to automatically trigger full verification including diagnostics when the very first registration is resolved.
This wasn’t an easy call, though, and is a severe breaking change. It’s severe, because the change in behavior can be easily overlooked.
When upgrading to Simple Injector v5, check whether your code base deliberately skips verification because of performance concerns. And when this is the case, you should suppress auto verification.
There are two likely scenarios where you would want to suppress verification:
- Running integration tests where each test creates and configures a new
Container
instance. In that case verifying the container in each test might cause the integration test suite to slow down considerably because Verify()
is a costly operation.
- Running a large application where start-up time is important. For big applications, the verification process could take up a considerate amount of time. In such a case you would prevent the application from verifying on startup, and instead move the
Verify
call to a unit/integration test. That allows fast application start-up.
Disabling auto-verification can be done by setting Container.Options.EnableAutoVerification
to false
.
Although auto verification and disabled unregistered-type resolution are the two changes that will impact most users, there are other changes, such as the discontinued support for .NET 4.0.
No more .NET 4.0
We have dropped support for .NET 4.0: the minimum supported versions are now .NET 4.5 and .NET Standard 1.0.
.NET 4.0 was released on 12 April 2010, which is more than a decade ago. It has been superseded with .NET 4.5 on 15 August 2012—now almost 8 years ago. It’s time to let go of .NET 4.0, even though there may be some Simple Injector users that are stuck to .NET 4.0.
Developing software is always about finding a balance. Keeping older versions supported comes with costs—even for an open-source project. Perhaps even especially for open-source projects where development is done in free time (and free time is precious).
The introduction of support for .NET Standard introduced complexity in the library, caused by the changing Reflection API. This new Reflection API was later added to .NET 4.5, but that lead to the use of #if
preprocessor directives, additional build outputs and risk of introducing errors. This is complexity we wanted to get rid of, but that meant ditching support for .NET 4.0.
This comes with the risk of frustrating developers that maintain old applications and still want to enjoy improvements in Simple Injector. We’re truly sorry if this frustrates your project, and hope that Simple Injector v4 serves you well until you can migrate to .NET 4.5 and beyond.
If you feel frustrated by the removal of .NET 4.0, perhaps the next change will cheer you up.
Less first-chance exceptions
More recently, Microsoft made some changes to Visual Studio that have impacted Simple Injector users. One of those changes is how Visual Studio handles first-chance exceptions by default.
When debugging an application, not all exceptions have to be dealt with. When a third-party or framework library catches an exception and continues, you can safely ignore that exception. Where older versions of Visual Studio didn’t show these exceptions by default, newer versions of Visual Studio automatically stop the debugger and popup the exception window. This can be really confusing because it’s not always immediately clear whether the first-chance exception is being dealt with by the library component or is one that breaks your application. Admittedly, I have wasted many hours because of this, because even Microsoft libraries throw exceptions that they recover from themselves!
In Simple Injector 4, there are times where the library would throw an exception that it caught elsewhere and handled itself. This design worked well in the older versions of Visual Studio. But since stopping at first-chance exceptions is the new norm, the behavior is problematic for our users. Not only does it cause confusion, getting those constant exception popups during startup can be really annoying.
In v5 we changed the APIs that would throw and catch exceptions. They now follow the ‘try-parse’ pattern and return a boolean
. This does mean, however, it’s a breaking change. In case you have a custom IConstructorSelectionBehavior
or IDependencyInjectionBehavior
implementation, you will need to change your implementation.
It was impossible for us to completely remove all first-chance exceptions from the library and there are still edge cases where exceptions are caught—most notably in the generics sub system. There are some situations where Simple Injector can’t correctly determine generic type constraints, which means that it relies on the framework to communicate the existence of such a constraint. This part of the .NET Reflection API lacks a ‘try-parse’ API and we’re stuck with catching exceptions (there currently is a proposal to add such method in .NET 5.0, but untill now the Microsoft team is not very supportive). The chances, however, of you hitting this are very slim, because it only happens under very special conditions.
Simplified registration of disposable components
When it comes to analyzing object graphs, Simple Injector always erred on the side of safety.
Lifestyle Mismatches (a.k.a. Captive Dependencies) are, by far, the most common DI pitfall to deal with. Detecting these mismatches is something Simple Injector had done for a very long time now. Simple Injector prevents you from accidentally injecting a short-lived dependency into a longer-lived consumer.
Simple Injector considers a Transient component’s lifetime to be shorter than that of a Scoped component. This is because a single Scope
could theoretically live for a very long time. If you wish, you could leave your scope open for the complete duration of a long-running operation or even for the duration of the application, which would make an injected Transient component live for as long as well. This is the reason the injection of a Transient into a Scoped was blocked by default and reported by the Simple Injector’s Diagnostics sub system.
In practice, however, scopes are usually wrapped around a single (web) request, which makes their lifetime very limited and deterministic. In these cases, injecting a Transient into a Scoped is relatively risk-free.
Additionally, the Transient lifestyle also behaves quite differently compared to the Scoped lifestyle. Transient components are not tracked by Simple Injector and, therefore, can’t be disposed of. You likely encountered the ”{your class} is registered as transient but implements IDisposable” error before. Registering it as Scoped fixes the issue but would cause the Lifestyle Mismatch error when that registration contains Transient dependencies.
Because of the low risk of injecting a Transient into a Scoped, this strict behavior causes more confusion and frustration than that it prevents errors and is why we have decided to relax this behavior. By default, Simple Injector v5 allows you to inject transients into Scoped components. If you would prefer to revert to the old behavior you can set Container.Options.UseStrictLifestyleMismatchBehavior
to true
.
Simple Injector has historically always been one of the top performers when it comes to speed of resolving. Very early on we decided that performance is a feature and were able to have great performance while adding new features.
We’re now at a practical limit of what’s achievable from a performance perspective. There are areas where performance can theoretically be improved, but it has no practical use, because you wouldn’t notice the difference when running a real application.
There is one area, however, where performance could still be improved, and this is startup time. As I discussed above, registering and verifying a big application can take a considerable amount of time. While running a performance analysis during the development of v5, we noticed a few hotspots that caused a considerable slowdown in performance during the registration phase. These were due to using some of the slower Reflection calls. After building an optimized POC, we noticed a performance boost of up to 60%, which is very significant, especially for big applications.
Unfortunately, this improvement meant we had to introduce a breaking change. But the performance gain is significant, and we felt it worth the risk. Only few developers will be affected by this change. You will only be affected by this breaking change if you’ve created your own lifestyles. If this is the case, please review the release notes closely to see what we’ve changed.
Asynchronous disposal
Another great improvement is the ability for Simple Injector to asynchronously dispose registered components. The ASP.NET Core integration package in Simple Injector v4 made sure that components implementing IAsyncDisposable
were disposed at the end of a web request. This, however, only worked within the context of a web request, and only components could be disposed that implemented both IAsyncDisposable
and IDisposable
, which might not always be the case.
In v5 we have now integrated this feature into the core library. Not only did this simplify the ASP.NET integration package and remove the IDisposable
limitation, it also means that asynchronous disposal is available everywhere. For example, when running background operations in ASP.NET Core, or when running a Console Application.
The v5 ASP.NET Core integration package automatically calls Scope.DisposeAsync()
when a web request ends. In other cases, you will need to call Scope.DisposeAsync()
manually (or use the new C# async using
keyword).
Please note that this feature is only available in the .NET 4.6.1, .NET Standard 2.0, and .NET Standard 2.1 versions of Simple Injector.
The last big new feature is the ability to inject a dependency’s metadata into a consumer. In v5 you can now write this:
public class Foo
{
public Foo(DependencyMetadata<IDependency> metadata) { }
}
public class Bar
{
public Bar(IList<DependencyMetadata<IDependency>> metadata) { }
}
The class Foo
will receive a DependencyMetadata<T>
, which is a new Simple Injector type. This metadata gives access to the dependency’s InstanceProducer, its implementation type, and allows the type to be resolved by calling GetInstance().
The example’s Bar
class, on the other hand, receives a list of DependencyMetadata<T>
instances, which is useful for the injection of lists of dependencies.
Admittedly, this is a rather advanced feature that not many users will need. It’s meant to be used inside infrastructure components (i.e. classes that are part of the Composition Root). Infrastructure components sometimes require more information about the dependency and need to be able to lazily resolve it. The feature is not meant to be used outside the Composition Root, because that would cause your application to take a dependency on Simple Injector, which is something we advise against.
For a more elaborate example of this this feature, see the documentation.
For a complete list of all the breaking changes, new features and bug fixes, please view the release notes.
For the last months we’ve been working on the next major release of Simple Injector, and it is finally here. We have removed legacy methods, simplified working with the library, and fixed many bugs and quirks.
In contrast to the impact that v3 had for developers, we expect most developers to update without having to make any code changes when upgrading from the latest v3.x to v4.0. There are quite some breaking changes through, but most of them are in more specialized parts of the library that you use when extending Simple Injector, such as writing custom Lifestyles, which is something most developers don’t do.
Our goal has always been to let the API guide you as much as possible through the breaking changes and how to fix them. In most cases removed parts of the API still exist, but are marked with [Obsolete(error: true)] attribute with expressive messages that explain what to do instead. This will cause your compiler to show a compilation error with (hopefully) a clear message describing the action to take. This should make it easier for you to migrate from v3.x to v4.0.
Before you upgrade to v4.0, please make sure you upgrade to the latest v3.x version of Simple Injector first.
With the release of v4.0 we moved to .NET Standard in favour of PCL. This means we removed support for PCL in version 4. Since most new platforms embrace the new .NET Standard, this shouldn’t be a problem. As long as your platform supports .NET Standard, Simple Injector v4 will run happily.
New Features
With this release we introduced many small and big simplifications to the API, some of which are:
- The integration of the common
LifetimeScopeLifestyle
and ExecutionContextScopeLifestyle
as part of the core library. These lifestyles have been renamed to the more obvious ThreadScopedLifestyle
and AsyncScopedLifestyle
, and the old SimpleInjector.Extensions.* NuGet packages have been deprecated.
- The deprecation of framework-specific lifestyles
WebApiRequestLifestyle
and AspNetRequestLifestyle
in favor of the new built-in AsyncScopedLifestyle
.
- The automatic and transparent reuse of registrations for classes that are registered with multiple interfaces. Simple Injector detected these kinds of problems, calling them Torn Lifestyles, but in Simple Injector v4 we completely removed this problem altogether, making it something the user hardly ever has to think about.
- Several overloads added to simplify common scenarios.
On top of that, we removed some small parts of the API that could cause ambiguity and could lead to hidden, hard to detect errors. In some situations, e.g. when making conditional registrations, the user was able to make decisions on the service type of the consuming component, but this was unreliable, because such component could be registered with multiple interfaces. This could make the conditional registration invalid, where it was impossible for Simple Injector to warn the user about this. We removed these ambiguous properties and force the user to use the property containing the implementation type instead. We added some convenient extension methods on System.Type to make it easier to extract an abstraction from such implementation type, namely: IsClosedTypeOf<T>
, GetClosedTypeOf<T>
and GetClosedTypesOf<T>
.
We improved the Diagnostic sub system once more. The biggest improvement is the detection of Short Circuited Dependencies. This is something that we were doing since v2, but there were situations in the past where Short Circuited Dependencies weren’t detected. We fixed that in this release.
For a complete list of all the breaking changes, new features and bug fixes, please view the release notes.
Jul 6, 2016 by Steven and Peter
For the last couple of years, Microsoft has been building the latest version of the .NET platform, branded .NET Core. One of the core components of this new framework is a DI library. Unfortunately, Microsoft made the mistake of defining a public abstraction for its DI library. In our previous blog post we described why the existence of this abstraction leads to all sorts of problems.
The goal of this blog post is to explain how you can effectively limit exposure to this abstraction and instead apply proven practices that promote structure, design and maintainability within your application. The summary of this blog post is the following:
TLDR;
Refrain from using a self-developed or third-party provided adapter for the .NET Core DI abstraction. Isolate the registration of application components from the framework and third-party components. Pursue a SOLID way of working and allow your application registrations to be verified and diagnosed by Simple Injector, without concern for incompatibilities with the framework and third-party components.
Microsoft wants its users to start off using the default container and then replace, if you want, with a third-party DI library. This advice of having one container instance that builds up both framework components, third-party components and application components stems from the idea that it is useful for framework components to be injected into application components. Having a single container makes it easy for the container build up object graphs that are a mixture of application and framework components.
Although developers might find this appealing, it’s important to realize that this a violation of the Dependency Inversion Principle (DIP), which states that:
abstracts are owned by the upper/policy layers.
In other words, in order to conform to the DIP, your application code should not depend on framework abstractions. Typically, code that depends on framework abstractions should exist wholly in the Composition Root. The DIP and ISP promote the use of abstractions tailored to your application’s needs and the creation of adapter implementations. Instead of having a framework or external library dictate the size and shape of abstractions, the application under development should define what’s best for its particular needs. Not only does this result in clean and testable code, it makes the code more flexible and reusable.
The SOLID principles are of great guidance here, and since the DIP states that your application (upper) layer should only depend on its own abstractions, building up mixed object graphs is an anti-pattern. Having one container build up mixed object graphs leads developers to violate the SOLID principles and will undoubtedly cause pain in the long run.
Instead of aiming for one DI library that builds everything up (one container to rule them all), you should keep these two worlds separate: framework components should be built up by the framework’s container, application components should be built up using your container of choice. To integrate or bridge the two worlds you define small focused adapters on each side. Use the framework’s provided extension points to intercept the creation of root types and forward the creation of those types to your container. On the other side of the container divide you define implementations for application-tailored abstractions, which call-back into framework and third-party library code. A well-designed framework will have all the necessary abstractions in place for you to intercept. ASP.NET Core MVC already contains all the required hooks. Third-party tool developers should follow the same practice.
Some developers feel uncomfortable with the notion of two containers in single application. But if you view the built-in framework container as a configuration system for the framework, having an independent container for your own application components is a non-issue. Every framework has its own configuration system. ASP.NET Web Forms has its own configuration system (mainly XML based) and MVC & Web API have their own code-first configuration systems. In a sense, nothing much has changed; ASP.NET Core still has a configuration system, be it one that includes an internal container-like structure. Apparently this container gives them a lot of flexibility, which is great. But we never had the need to completely swap out a framework’s configuration system before, so why should we need to for ASP.NET Core?
So how does this work? If you don’t want to swap out the built-in configuration system for .NET Core, what should you do? As said before, good practice is to use the framework’s supplied extension points and override as necessary to redirect/intercept the creation of certain types.
The main interception point for ASP.NET Core MVC is the IControllerActivator
abstraction. This abstraction allows intercepting the creation of MVC controller types. An implementation for Simple Injector is trivial:
public sealed class SimpleInjectorControllerActivator : IControllerActivator
{
private readonly Container container;
public SimpleInjectorControllerActivator(Container c) => container = c;
public object Create(ControllerContext c) =>
container.GetInstance(c.ActionDescriptor.ControllerTypeInfo.AsType());
public void Release(ControllerContext c, object controller) { }
}
To replace the built-in controller activator, you configure the Core container:
services.AddSingleton<IControllerActivator>(
new SimpleInjectorControllerActivator(container));
Although trivial to implement, we do provide an out-of-the-box implementation for you in our ASP.NET Core MVC integration package to make your life easier. As a matter of fact, over time we will supply you with with all the convenient methods that allow you to make bootstrapping as seamless as possible. We might not provide you with integration packages for all existing frameworks, but plugging in Simple Injector will always be trivial when the designers provided you with the correct interception points.
What this means is that all framework components and third-party components can keep being composed by the built-in DI container and your application will register and resolve your components through Simple Injector.
Many developers incorrectly assume that having one container for the framework’s internal configuration and another for the application components will mean re-registering hundreds of framework and third-party library components in the application container, but this is simply not necessary. First of all, as we already established, those registrations shouldn’t be in the application container because no application component should directly depend on those abstractions. Secondly, your application will only need to interact with a handful of those services at most, so you’ll handle the abstractions you are actually interested in. Thirdly, trying to get all the framework’s registrations inside your application container brings you back to square one: back to the Conforming Container with all its complications, downsides, and incompatibilities.
Examples
Let’s say you have a component that needs access to the HttpContext
instance, because you want to extract the name of the user from the current request being executed. Since the HttpContext
can be acquired using the Microsoft.AspNetCore.Http.IHttpContextAccessor
abstraction, your component requires this abstraction as a constructor argument and your code might look something like this:
public sealed class CustomerRepository : ICustomerRepository
{
private readonly IUnitOfWork uow;
private readonly IHttpContextAccessor accessor;
public CustomerRepository(IUnitOfWork uow, IHttpContextAccessor accessor)
{
this.uow = uow;
this.accessor = accessor;
}
public void Save(Customer entity)
{
entity.CreatedBy = this.accessor.HttpContext.User.Identity.Name;
this.uow.Save(entity);
}
}
There are, however, several problems with this approach:
- The component now takes a dependency on an ASP.NET Core MVC abstraction, which makes it impossible to reuse this component outside the context of ASP.NET Core MVC.
- The component has explicit knowledge about how to get the user name for the application.
- The code that gets the user name will likely be duplicated throughout the application.
- The component becomes much harder to test, because of the train wreck in the
Save
method.
One of the main problems is that the IHttpContextAccessor
abstraction isn’t designed for the specific needs of this component. The needs of this component are not to access the current HttpContext
, its need is to get the name of the user on whose behalf the code is running. We should create a specific abstraction for that specific need:
public interface IUserContext
{
string Name { get; }
}
With this abstraction, you can simplify your component to the following:
public sealed class CustomerRepository : ICustomerRepository
{
private readonly IUnitOfWork uow;
private readonly IUserContext userContext;
public CustomerRepository(IUnitOfWork uow, IUserContext userContext)
{
this.uow = uow;
this.userContext = userContext;
}
public void Save(Customer entity)
{
entity.CreatedBy = userContext.Name;
uow.Save(entity);
}
}
What you have achieved here is that you:
- Decoupled your component from the framework code; it can be reused outside of ASP.NET.
- Prevented this component to have explicit knowledge about how to retrieve the current user’s name.
- Prevented this code from being duplicated throughout the application.
- Reduced test complexity.
- Made the code simpler.
Since you have decoupled your component from the framework code, you can now reuse the component. For instance, it’s quite common to want to run part of your code base in a background Windows Service where there is obviously no HttpContext
. To make this work you will create an adapter implementation for IUserContext
that is specific to the type of application you are building. For your ASP.NET application, you will need an adapter implementation that contains the original code that retrieves the user’s name. For a Windows Service, you might return the name of the system user.
Here’s the adapter implementation for ASP.NET:
public sealed class AspNetUserContext : IUserContext
{
private readonly IHttpContextAccessor accessor;
public AspNetUserContext(IHttpContextAccessor a) => accessor = a;
public string Name => accessor.HttpContext.Context.User.Identity.Name;
}
As you can see, this adapter implementation is straightforward, all it does is getting the HttpContext
for the current request and the user name is determined from the context, as you saw before.
This component can be registered in your application container as follows:
var accessor =
app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
container.RegisterSingleton<IUserContext>(new AspNetUserContext(accessor));
The app variable here is ASP.NET Core’s IApplicationBuilder
abstraction that gets injected into the Startup.Configure
method.
What you see here is that the AspNetUserContext
adapter depends directly on the IHttpContextAccessor
abstraction. You can do this because IHttpContextAccessor
is one of the framework’s abstractions that are known to be registered as Singleton. For most framework and third-party services, however, we will have no idea what lifestyle it is registered with, and therefore, resolving them directly using the ApplicationServices
property of IApplicationBuilder is a pretty bad idea.
Due to another design flaw, ASP.NET Core allows resolving Scoped instances through the ApplicationServices
property, but returns those components as Singletons! In other words, if you were to request any framework and third-party services through ApplicationServices
, the chances are that you would get a stale instance that would break your application at runtime—and ASP.NET Core will not inform you of that error. Instead of throwing an exception, ASP.NET Core will fail silently and leave your application in a potentially invalid state, maybe causing an ObjectDisposedException
or worse. This is actually yet another incompatibility with Simple Injector; Simple Injector blocks these types of invalid resolves by throwing an exception.
UPDATE: ASP.NET Core 2.0 mitigates the resolution of Scoped instances from the root container, when running in development mode. However, it will still not detect the resolution of any disposable Transients from the root container. This will still lead to memory leaks.
Instead of using the ApplicationServices
property, it would be better to resolve services using the HttpContext.RequestServices
property. The following adapter shows an example when dealing with framework dependencies with a lifestyle that is either not Singleton or unknown:
public sealed class AspNetAuthorizerAdapter : IAuthorizer
{
private readonly Func<IAuthorizationService> provider;
public AspNetAuthorizerAdapter(Func<IAuthorizationService> provider)
{
this.provider = provider;
}
// Implementation here
}
This is an adapter for a hypothetical IAuthorizer
abstraction. Instead of depending on ASP.NET’s IAuthorizationService
directly, this adapter depends on Func<IAuthorizationService>
, which allows the correctly Scoped service to be resolved at runtime. This adapter can be registered as follows:
container.RegisterSingleton(
new AspNetAuthorizerAdapter(
GetAspNetServiceProvider<IAuthorizationService>(app)));
The AspNetAuthorizationAdapter
is created and registered as Singleton. The registration makes use of the convenient GetAspNetServicesProvider<T>
helper method that allows creating the provider delegate:
private static Func<T> GetAspNetServiceProvider<T>(IApplicationBuilder app)
{
var accessor =
app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
return () =>
{
var context = accessor.HttpContext
?? new InvalidOperationException("No HttpContext");
return context.RequestServices.GetRequiredService<T>();
};
}
When supplied with an IApplicationBuilder
instance, the GetAspNetServiceProvider
method will create a Func<T>
that allows resolving the given service type T
from the RequestServices
collection according to its proper scope.
NOTE: With the introduction of ASP.NET Core integration package for Simple Injector v4, we added GetRequestService<T>()
and GetRequiredRequestService<T>()
extension methods on IApplicationBuilder
that allow retrieving request services just like the previous GetAspNetServiceProvider<T>()
method does. With the introduction of v4.1, we added the notion of auto crosswiring, which simplifies this process even more.
Using logging in your application
Besides a DI library, .NET Core ships with a logging library out-of-the-box. Any application developer can use the built-in logger abstraction directly in their applications. But should they? If you look at the ILogger
abstraction supplied to us by Microsoft, it’s hard to deny that the abstraction is very generic in nature and might very well not suit your application’s specific needs. The previous arguments still hold: application code should be in control over the abstraction.
The SOLID principles guide you towards defining an application-specific abstraction for logging. The exact shape of this abstraction will obviously differ from application to application (but look at this example for inspiration). Again, a simple adapter implementation can do the transition from application code to framework code:
// your application's logging abstraction
public interface ILog { void Log(LogEntry e); }
public sealed class DotNetCoreLoggerAdapter : ILog
{
private readonly Microsoft.Extensions.Logging.ILogger logger;
public DotNetCoreLoggerAdapter(ILogger logger) => this.logger = logger;
public void Log(LogEntry e) =>
logger.Log(ToLevel(e.Severity), 0, e.Message, e.Exception,
(s, _) => s);
private static LogLevel ToLevel(LoggingEventType s) =>
s == LoggingEventType.Warning ? LogLevel.Warning :
s == LoggingEventType.Error ? LogLevel.Error :
LogLevel.Critical;
}
This DotNetCoreLoggerAdapter
can be registered as singleton as follows:
container.RegisterSingleton<ILog>(
new DotNetCoreLoggerAdapter(loggerFactory.CreateLogger("Application")));
But there are other options when it comes to integrating logging with Simple Injector.
Conclusion
By creating application-specific abstractions, you prevent your code from taking unnecessary dependencies on external code, making it more flexible, testable and maintainable. You can define simple adapter implementations for the abstractions you need to use, while hiding the details of connecting to external code. This allows your application to use your container of choice (and supports a container-less approach). This approach is part of a set of principles and practices that is been taught by experts like Robert C. Martin and others for decades already. Don’t ignore these practices, embrace them and be both productive and successful.
Jun 30, 2016 by Steven and Peter
For the last couple of years Microsoft has been building the latest version of the .NET platform: .NET Core. .NET Core is a complete redesign of .NET, with the goals of being truly cross-platform and cloud friendly. We’ve been following the development of .NET Core closely and have released .NET Core compatible versions of Simple Injector since RC1. With the release of Simple Injector v3.2 we now officially support .NET Core.
As you may be aware Microsoft has added its own DI library as one of its core components. Some would yell “finally!” The omission of such a component has spawned many open source DI libraries for .NET. Simple Injector obviously being one of them.
Don’t get us wrong here, we applaud Microsoft for promoting DI as a core practice in .NET and it will likely lead to many more developers practicing DI, which is a win for our industry. The problem, however, starts with the abstraction Microsoft has defined on top of their built-in DI container. Compared to the previous Resolve abstractions, such as IDependencyResolver and IServiceProvider, this new abstraction adds a Register API on top IServiceCollection. With the definition of this abstraction, it is Microsoft’s vision that other (more feature rich) DI libraries could plug-in into the platform, while application developers, third-party tool builders, and framework developers use the standardized abstraction to add their registrations. This would allow application developers a standard for integrating their DI library of choice.
At first sight having an abstraction might seem like sound advice—a common saying in our industry is that there are few problems in software that can’t be solved by adding a (extra) layer of abstraction. In this instance though their reasoning is flawed. DI experts have been warning Microsoft about this problem from the beginning, without success. Mark Seemann quite accurately described the problems with this approach in general here, where IMO the main points of his reasoning are:
- It pulls in the direction of the lowest common denominator
- It stifles innovation
- It makes it more difficult to avoid using a DI container
- It introduces versioning hell
- If adapters are supplied by contributors, the adapters may have varying quality levels, and may not support the latest version of the Conforming Container.
These are real issues we are facing today with the new .NET Core DI abstraction. DI containers often have very unique and incompatible features when it comes to their registration API. Simple Injector, for instance, is very carefully designed in a way that enables the identification of numerous configuration errors. One very prominent example—but there are many more—is Simple Injector’s diagnostic abilities. This is one of the features that is fundamentally incompatible with the expectations that consumers of the DI abstraction will have. So what are the expectations consumers will have of the new abstraction?
Consumers of the DI abstraction can be divided into three groups. Framework components, third-party libraries, and application developers; especially framework components and third-party libraries, which are now expected to add their own registrations through the common abstraction. Because it is nigh on impossible for these two groups of developers to test their code with all the available adapters, they will test their code solely with the built-in container. And while using the built-in container, these developers will (and arguably should) implicitly expect the standardized behaviour of the built-in container—no matter which adapter is used. In other words, it is the built-in container that defines both the contract and the behaviour of the abstraction. Every implemented adapter must be an exact superset of the built-in container. Deviating from the norm is not allowed because it would break third-party tools that depend on the behaviour of the default, built-in container.
Simple Injector’s diagnostic and verification abilities is one of the many features that make Simple Injector users extremely productive. It detects problems that would be detected much later in the development cycle when using a different DI library. But running the diagnostics on both application and third-party registrations will cause problems because it is unlikely that all the external parties will automatically “play nice” with Simple Injector’s diagnostics. There is every chance they will define registrations that Simple Injector finds suspicious even though they have (hopefully) tested the registrations are fine for their specific case with the default container. It would be impossible for a hypothetical adapter for Simple Injector to distinguish between third-party registrations and application registrations.
Switching off diagnostics completely would remove one of Simple Injector’s most important safety nets, whilst leaving the diagnostics system in place would likely cause false-positives from the third-party tooling that would each need to be suppressed by application developers. As these third-party registrations are mostly hidden to the application developer, working around these issues could be daunting, frustrating and sometimes even impossible. One might argue that it would be good for Simple Injector to detect problems with third-party tools, but contacting those same tool developers to explain the “problem” would probably lead to fingers being pointed at us, because we “obviously” provided the user with an “incompatible” adapter.
Simple Injector’s diagnostic abilities is just one of the many incompatibilities that we would face when writing an adapter for .NET Core’s DI abstraction. Other incompatibilities include:
Making a fully compatible adapter for Simple Injector requires removing many prominent features, and thereby changing the existing behaviour of the Simple Injector library to something that would violate the guiding principles that underpin our vision. This is not an attractive solution. Not only would it introduce major breaking changes, it would remove features and behaviours that make Simple Injector unique and it is this complete set of features that many developers love about Simple Injector. In this sense having an adapter “stifles innovation” as Mark Seemann describes.
With Simple Injector we made many innovations and not only would the adapter make Simple Injector almost useless to our users, it would restrict us from future improvement and innovation. Some might view Simple Injector’s philosophy as radical, but we think otherwise—we designed Simple Injector in a way that we think serves our users best. And the NuGet download count on the Simple Injector package indicates that many developers agree with us. Conforming to the defined Register API would prevent us from serving our users.
Although Simple Injector’s view may diverge from the norm more than most other containers, the simple act of defining this common abstraction blocks future DI libraries with an even more radical or innovative viewpoint from being used at all—it stifles innovation for future libraries. Just imagine one of the other containers introducing the same kind of verification that Simple Injector provides? Such feature can’t be introduced without breaking the contract of the DI abstraction. The mere act of having such an adapter blocks progress in our industry.
With this explanation, we hope we’ve also made it clear that Microsoft’s DI abstraction isn’t even the lowest common denominator, because the lowest common denominator implies compatibility with all DI libraries! As we expressed here the chances are that none of the existing DI libraries are fully compatible with the defined abstraction. The Autofac maintainers for instance, realized they have some quite severe incompatibility issues and eventually came to the same conclusion as we did. The Autofac maintainers publically stated that their adapter is not 100% compatible with Microsoft’s DI abstraction:
there will definitely be behavior differences between the Autofac DI container and the Microsoft DI container. We’re not trying to behave identically – if you choose to use Autofac as your backing container, you’re also choosing the Autofac behaviors. The difference in behavior you found is one of probably many
But while application developers do explicitly choose to use a particular container, like Autofac, framework and third-party library developers don’t. And when those latter developers depend on abstraction behavior that Autofac implements differently, it can break the application in very subtle ways.
Considering that Microsoft’s DI Container is heavily influenced by Autofac’s design, it is a telling sign that even Autofac can’t comply with the abstraction.
A similar story comes from the maintainer of StructureMap that stated:
ASP.Net Core DI compliance has been a huge pain in the ass to the point where I’ve openly wondered if the ASP.Net team has purposely tried to sabotage and wipe out all the existing ecosystem of IoC containers
UPDATE: Other maintainers of DI containers are also starting to notice how the new DI abstraction is stifling innovation. The developer of the LightInject DI Container, for instance, had to completely disable one of its library’s compelling features to allow his adapter to be used in a vanilla ASP.NET Core v2.2 application, to prevent it from completely crashing at startup.
UPDATE (2019-09): Due to similar incompatibilities with the built-in container, the Castle Windsor maintainers were forced to take a similar integration approach to ours, which we describe in our next blog post. In other words, Castle Windsor’s integration works around the ASP.NET Core DI abstraction as well.
This wouldn’t be so bad if Microsoft’s DI library was a feature-rich implementation that contained features like Simple Injector’s verification and diagnostic services so that we all use the same fully featured DI library. Sadly, the implementation is far from feature rich, Microsoft itself has described their implementation as a
minimalistic DI container [that] is useful in the cases when you don’t need any advanced injection capabilities
To make matters worse, since the built-in container defines the contract of the abstraction, adding new features to the built-in container will break all existing adapters! Third-party developers who use the abstraction will only test with the built-in container and when their libraries depend on a feature added to the built-in container that is not yet supported by an adapter, things will fail and the application developer is screwed. This is one aspect of the versioning hell that Mark Seemann discusses in his blog post. Not only is their current implementation “minimalistic,” it can never evolve to a feature rich, completely usable DI container, because they’ve painted themselves in a corner: every future change is breaking change that will piss everyone off.
UPDATE (2023-07): This is exactly what happened with the introduction of .NET 8, where Microsoft introduces keyed registrations to their DI Container. This —once more— frustrates maintainers of DI Container adapters, that now all have you upgrade their integration packages, again.
A better solution is to avoid using the abstraction and its adapters entirely. As Mark Seemann quite accurately explained here and here, reusable libraries and frameworks may not need to use a DI container at all.
Unfortunately, the mere act of defining an abstraction will make it much harder to avoid using it. By defining an abstraction and actively promoting its use, Microsoft is leading thousands of third-party library developers and framework developers to stop thinking about defining the right library and the right framework abstractions (Mark’s articles clearly describes this). They no longer think about this because Microsoft leads them to believe that the whole world needs one common abstraction. We have seen new factory interfaces for MVC appear very late in the game (such as the IViewComponentActivator
abstraction prior to RC2). And if we see the MVC team make these kinds of mistakes till very late in the development cycle, what can we expect from all those developers who are starting to build on top of the new .NET platform?
UPDATE: More than three years later, a similar issue popped up with the new ASP.NET Core 3 Razor Components, where Microsoft forgot to introduce an IComponentActivator
abstraction. Although the issue was reported six months before ASP.NET Core 3 was released, Microsoft decided not to add this abstraction, making it impossible for users of containers like Simple Injector and Castle Windsor to integrate with Razor Components. One would have hoped that Microsoft would keep its promise to add the “appropriate composition roots” (read: the “required abstractions”). This unfortunately proves that even framework developers stopped thinking about defining the right framework abstractions.
Conclusion
The definition of a DI abstraction is a painful mistake by Microsoft that will haunt us for many years to come. It has already stifled innovation, has introduced versioning hell, and frustrates many developers. The abstraction is incompatible with many, if not all, DI libraries and, against expert advice, Microsoft chose to retain the abstraction, dividing the world into incompatible and partially compatible containers, leading to endless issue reports for the adapter libraries that implement the DI abstraction and third-party libraries that use the abstraction.
Our view is that, as an application developer, you should refrain from using an adapter and in the next article we will explain more thoroughly how to approach this and why, even with a compatible container, it is the smarter way forward.
Stay tuned
A DI container is a tool that allows constructing the graphs of classes that contain an application’s behaviour (a.k.a. components or injectables). When you apply Dependency Injection in your systems the DI container can simplify the process of object construction and can, when used correctly, improve the maintainability of the start-up path (a.k.a. the Composition Root) of your application. But a DI container is not mandatory when you apply Dependency Injection.
Applying Dependency Injection without a DI container is called Pure DI. When you use Pure DI you define the structure of your object graphs explicitly in code and this code is still centralized in the Composition Root just as it is when using a DI container. Dependency Injection does not discourage the use of the new
keyword to construct components; it promotes the centralization of the use of the new
keyword.
In this article, Mark Seemann shows the advantage of Pure DI over using a container: with Pure DI the compiler can verify the object graph. Mark makes some good points that for smaller applications Pure DI can be more beneficial than the use of containers, while larger applications can take advantage of convention over configuration which can help a lot in making your Composition Root maintainable. Mark even shows how Pure DI can help in finding configuration mistakes like Captive Dependencies.
The primary benefit of Pure DI is that it allows your code to fail fast (in this case the system fails at compile time). Detecting failures early is crucial when it comes to lowering development cost, because tracking down bugs is obviously much easier in a system that fails fast.
Although I do agree with Mark’s reasoning, it’s important to realize that Pure DI isn’t a silver bullet that detects all configuration mistakes. On the contrary, it’s quite easy to overlook problems such as Captive Dependencies as the Composition Root starts to grow. If you were to switch from a DI container to Pure DI and you were expecting your code to fail fast, you might be in for an unpleasant surprise when the first bugs appear. This can happen because the C# compiler can only do a few simple checks on your behalf, such as:
- Check whether the number of arguments supplied to a constructor match
- Check whether the types supplied to a constructor match
The compiler is unable to perform the following checks:
- Are null values supplied to constructors?
- Do constructor invocations fail?
- Are dependencies injected into a component with a longer lifetime (the so called Captive Dependencies)?
- Are dependencies that are expected to have a certain lifestyle created more than once for the duration of that lifetime? (Problems known as Torn Lifestyle and Ambiguous Lifestyle)
- Are disposable components not disposed when they go out of scope?
All these issues are relatively easy to spot when the number of components in the application is really small, but once that number starts to grow it’s very easy to lose track. A really strict coding style within your Composition Root does help but can easily go wrong when a team of developers is maintaining the Composition Root (opposed to having one single DI expert who has a really close watch on these types of issues).
It’s hard to define a threshold in application size for when a DI container outweighs Pure DI. In the business systems I help create, we almost always use a DI container for the central application (which often is a web application), while using Pure DI for small (background) Windows Services and Console applications as they typically use just a fraction of the total business layer. Once the Composition Root starts to grow, tools that can verify and diagnose the correctness of the Composition Root become extremely valuable.
It is unfortunate that most DI containers have a limited set of capabilities when it comes to verifying their configuration (causing your application to fail silently). Simple Injector deals with all the previously stated issues and more. In Simple Injector it’s just a matter of following good practices and calling Container.Verify()
once you have completed the configuration of the container. Verification of your configuration gives you an increased level of confidence that all known object graphs are wired correctly at application start-up. Simple Injector can give more certainty than Pure DI, while keeping the benefits of, among other things, convention over configuration.
After months of preparation and development we have finally released Simple Injector v3.0. In version 3 we are breaking from the past: we have removed legacy methods, simplified parts of the API and added some compelling new features.
We expect that almost every developer will have to make changes to their composition root when upgrading to v3. We did our best to make the upgrade process easy but please be prepared to make changes to your code.
The driver for making these breaking changes is that parts of the API have evolved over time and in doing so have grown confusing (e.g. RegisterOpenGeneric
and RegisterManyForOpenGeneric
). In the pursuit of keeping Simple Injector simple we felt obligated to improve the consistency of the API. These decisions have not been taken lightly because we hate breaking your code. Our driving force is, however, a simpler and more compelling library for everyone.
Our goal was to let the API guide you as much as possible through the breaking changes and how to fix them. In most cases removed parts of the API still exist, but are marked with [Obsolete(error: true)]
attribute with expressive messages that explain what to do instead. This will cause your compiler to show a compilation error with (hopefully) a clear message describing the action to take. This should make it easier for you to migrate from v2.x to v3.0.
Before you upgrade to v3.0, please make sure you upgrade to the latest 2.8 version of Simple Injector first. Some beta testers reported that there were some changes between minor versions of the 2.x branch that broke code and/or unit tests. Upgrading in two steps should make the process much easier.
Besides the clean-up of the API, Simple Injector is now much stricter when it comes to diagnosing your configuration. When calling Verify()
, Simple Injector 3 will automatically run its diagnostics and it will throw an exception when any diagnostic error occurs. Even without calling Verify()
, Simple Injector will always check for pesky Lifestyle Mismatches when resolving instances from the container and will throw an exception when such a mismatch is detected. These exceptions are intended to provide the quickest feedback on configuration mistakes that we believe you should resolve. From experience we know that this can save you from wasting many hours debugging problems later.
Breaking Changes
The most prominent breaking changes are the changes to the public API and these can prevent your code from compiling.
Here is a cheat sheet containing a mapping for the most prominent API changes. On the left side are the old v2 API calls, on the right side the related method to use in v3. Note that in most cases the compiler errors will guide you through the process.
v2 API |
v3 API |
RegisterSingle |
RegisterSingleton |
RegisterAll |
RegisterCollection |
RegisterSingleDecorator |
RegisterDecorator(Lifestyle.Singleton) |
RegisterAllOpenGeneric |
RegisterCollection |
RegisterManyForOpenGeneric |
Register / RegisterCollection |
RegisterOpenGeneric |
Register |
RegisterSingleOpenGeneric |
Register(Lifestyle.Singleton) |
For a complete list of all the breaking changes, please see the release notes.
New Features
As well as the breaking changes there are many other big and small improvements to the library. The most prominent of these are:
- the addition of a
Lifestyle.Scoped
property;
- support for conditional and contextual registrations using the new
RegisterConditional
methods.
Register
overloads now accept open generic types.
RegisterCollection(Type, IEnumerable<Registration>)
now accepts open generic types.
Container.Register(Type, IEnumerable<Assembly>)
and Container.RegisterCollection(Type, IEnumerable<Assembly>)
overloads have been added to simplify batch registration.
- the
Container
class now implements IDisposable
to allows disposing singletons.
The new Lifestyle.Scoped
is a small feature that can make your Composition Root much cleaner. Most applications use a combination of three lifestyles: Transient
, Singleton
, and some scoped lifestyle that is particularly suited for that application type. For example an ASP.NET MVC application will typically use the WebRequestLifestyle
; a Web API application will use the WebApiRequestLifestyle
. Instead of using the appropriate RegisterXXX
extension method of the appropriate integration package, you can now do the following:
var container = new Container();
// Just define the scoped lifestyle once.
container.Options.DefaultScopedLifesyle = new WebRequestLifestyle();
container.Register<IUserContext, AspNetUserContext>(Lifestyle.Scoped);
container.Register<IUnitOfWork, DbContextUnitOfWork>(Lifestyle.Scoped);
Not only does this make your code much cleaner, it also makes it easier to pass the container on to some methods that add some layer-specific configuration to the container. For instance.
public static void BootstrapBusinessLayer(Container container) {
// Registrations specific to the business layer here:
container.Register<IUnitOfWork, DbContextUnitOfWork>(Lifestyle.Scoped);
}
The Lifestyle.Scoped
property makes it easy for the business layer bootstrapper to add registrations using the application’s scoped lifestyle without having to know which lifestyle it actually is. This simplifies reuse of this bootstrapper across the applications in your solution.
Another great improvement is the addition of the RegisterConditional
methods. These method overloads allow conditional registration of types and registration of contextual types. Take the following conditional registrations:
container.RegisterConditional<ILogger, NullLogger>(
c => c.Consumer.ImplementationType == typeof(HomeController));
container.RegisterConditional<ILogger, SqlLogger>(c => !c.Handled);
This particular combination of registrations ensures that a NullLogger
is injected into HomeController
and all other components get a SqlLogger
.
For advanced scenarios one of the RegisterConditional
overloads accepts an implementation type factory delegate. Take a look at the following example:
container.RegisterConditional(typeof(ILogger),
c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
c => true);
This example registers a non-generic ILogger
abstraction that will be injected as a closed generic version of Logger<T>
, where T
is determined based on its context (in this case the consuming component). In other words when your HomeController
depends on an ILogger
it will actually get a Logger<HomeController>
.
For a complete list of all the breaking changes, new features and bug fixes, please view the release notes.
Happy injecting.
With Simple Injector 3 we are breaking with the past and working hard to simplify the API to better represent our ideals and, of course, the name of our library. This means that we are introducing breaking changes in v3 that will undoubtedly impact any developer migrating from v2.
The driver for making these breaking changes is that areas of the API have evolved over time and in so doing have grown confusing (e.g. RegisterOpenGeneric
and RegisterManyForOpenGeneric
). In the pursuit of keeping Simple Injector simple we felt obligated to improve the consistency of the API. These decisions have not been taken lightly because we hate breaking your code. Our driving force is, however, a simpler Simple Injector for everyone.
We are doing our best to make the transition as seamless as possible and we need feedback on this effort. We are looking for Simple Injector users to test the beta of Simple Injector 3 and tell us what they think.
If you can help then please upgrade your NuGet packages to 3.0.0-beta4 and try to compile your code (it will probably fail!). Our main strategy is to guide users with obsolete messages that should be presented as descriptive compiler errors. Once you have fixed any errors please call Verify() on your container, (as you are hopefully already doing) and test your application to ensure everything resolves as expected. To discuss any element of beta testing please join us on gitter. We would usually expect the upgrade process to take no more than a half hour.
Please note that we don’t expect you to do our testing for us. Simple Injector has an extensive suite of unit tests and we don’t expect to have introduced many new bugs.
Here’s a list of the most prominent breaking changes we are introducing:
- Calling
Verify()
will now automatically diagnose the container’s configuration for common configuration mistakes and exceptions will be thrown. We felt that not enough developers were explicitly calling Analyzer.Analyze()
to check for diagnostic warnings, leading to a common source of bugs. So we decided to integrate this into Verification in the hope that this will guide our users to the pit of success. The old Verification behaviour can be triggered by calling Verify(VerificationOption.VerifyOnly)
.
- Even if you don’t call
Verify()
, the v3 the container will always check for lifestyle mismatches when resolving an instance and will throw an exception if there is a mismatch in the object graph. This more strict behaviour can be suppressed globally but (for obvious reasons) we advise against doing so.
- The
RegisterSingle
methods have been renamed to RegisterSingleton
as we felt there was some ambiguity in the name RegisterSingle
, especially in combination with methods like RegisterAll
, RegisterCollection
and RegisterManyForOpenGeneric
. We considered removing these method completely, but the benefits of doing so did not compare favourably to the pain of fixing this as a breaking change.
- The
RegisterAll
methods have been renamed to RegisterCollection
. Just as with the RegisterSingle
methods, new developers experienced confusion in their naming. The new name expresses more clearly what the methods do.
- The
RegisterManyForOpenGeneric
extension methods from the SimpleInjector.Extensions
namespace have been replaced with new Register
and RegisterCollection
overloads on the Container
. When doing one-to-one mappings, Register(Type, IEnumerable<Assembly>)
can be used, and when registering collections RegisterCollection(Type, IEnumerable<Assembly>)
can be used.
- The
RegisterSingleDecorator
extension methods have been removed. Decorators can be registered by calling RegisterDecorator
while supplying the Lifestyle.Singleton
.
- The
RegisterOpenGeneric
extension methods have been removed. The Container.Register
methods have been extended to accept open generic types. This removes the superficial difference between the registration of open generic types and other registrations. (This difference originally had a technical background, but we allowed the internal difference to effect the user experience.) Do note that there is a behavioural difference between RegisterOpenGeneric
and Container.Register
. RegisterOpenGeneric
registers each generic type as fall-back, which means it will apply that type if no other registration exists for that type. Conversely, Container.Register
will not register a fall-back, it will, instead, test for overlapping registrations. If the fall-back behaviour is required, the new RegisterConditional
methods can be used. The new RegisterConditional
methods allow supplying a delegate that allows signaling a registration as fall-back registration.
IConstructorVerificationBehavior
and IConstructorVerificationBehavior
have been merged and replaced with IDependencyInjectionBehavior
.
In the majority of cases the compiler error messages should guide you through the migration. If you find a scenario that is unclear or it takes time to figure out please let us know. We want to make the migration as seamless as possible.
Besides the above list of breaking changes, we have some compelling new features that may be of interest:
- A
Lifestyle.Scoped
property was added to simplify registration of scoped lifestyles, since in most applications you would typically only use one specific scoped lifestyle. You can now use Register<IService, Impl>(Lifestyle.Scoped)
instead of having to call RegisterPerWebRequest<IService, Impl>()
for example. This also simplifies reuse in your composition root, when the same configuration is reused over multiple application types (such as MVC and WCF).
- As noted above
RegisterConditional
methods are added to the Container
to allow registering types based on contextual information such as information about the consumer in which they are injected.
- Batch registration made easier by adding overloads of the
Register
method that accept either a collection of types or a collection of assemblies.
- As explained above, the
Register
methods now accept open generic types.
- The container now implements
IDisposable
. This allows cached singletons to be disposed when the container gets disposed.
RegisterCollection(Type, IEnumerable<Registration>)
now accepts open generic service types as well.
The Simple Injector weblog is where we—the Simple Injector contributors—will write about Simple Injector, Dependency Injection, best practice and software design in general.
Simple Injector is strongly opinionated, and so are we; we love talking about software design and principles and from now on we’ll be doing it right here.
We’ll keep you updated about new Simple Injector features, releases, background stories and anything else we decide to write about.
Welcome to The Simple Injector Blog!