The reason that Simple Injector does not support child containers is because:
- Child containers are very performance heavy, which does not fit Simple Injector's strategy to be
fast by default.
- Promotes composition root scattering, because child containers are often configured on a per-request basis, making it harder to maintain and grasp the registration part of your application.
- Simple Injector promotes the notion of having an
immutable container, while child containers promote the notion of making and chancing registrations at any point in time. This doesn't match with our
overall design philosophy.
- Makes it impossible for Simple Injector to supply you with useful
diagnostic results, because parts of the registrations are done at a later point in time.
- For some containers (such as Unity), using child containers are the mechanism to created components with a scoped lifestyle (using the
HierarchicalLifetimeManager if I'm not mistaken). In Unity, registering instances per-web-request, simply means creating a child container and having that instance registered as singleton within that child container (hierarchical). Simple Injector
on the other hand has the notion of scoped lifestyles to do this.
Your question is quite broad, which makes it hard to be very specific, but I'll try to explain how to 'do things' in Simple Injector. In general however, I haven't come across a scenario that requires child containers at all.
The first thing that struck me however when reading your question is that I suspect a violation of the
Liskov Substitution Principle
in your application. Perhaps I completely misinterpreted how your application looks, but it seems as if you have one part (area) of your application that uses a different database than the other parts do, while all application
code uses the same abstraction. I could vision you having some sort of
which is registered globally to the
, while that particular area gets a
Again, perhaps I'm completely wrong, but if this is the scenario, you can't really blame Simple Injector for not getting this right. You are violating the Liskov Substitution Principle (LSP), one of the 5
. What the basically says is that any subtype (CentralDatabaseUnitOfWork and UserManagementUnitOfWork) of the same abstraction (IUnitOfWork) should be interchangeable for one another, without breaking the application. For instance, if you
have an ILogger abstraction with DatabaseLogger and FileLogger implementations, you should be able to swap implementations around, without consumers of ILogger to know anything about it. Clients should not care to what destination the log is written (if ever
written), but swapping those implementations around should not cause any consumer to break. But this will clearly happen if you inject a CentralDatabaseUnitOfWork in code that expects to work with the user management database, because they both have a completely
different database schema. If on the other hand you swap databases that contain an identical schema, swapping implementations will not be a problem, and will not break the consuming code.
According to the LSP this is a problem, and in fact it is a problem, because you will see that these violations make your DI configuration much harder. Forget about Simple Injector for a moment; it makes registration more complex, no matter what container you
use. And it makes the code more complex, because just by looking at the abstraction, you don't know which implementation you expect. You find out which implementation you expect by looking at the code that uses it. You will see lines of code like
from user in this.UnitOfWork.Repository<User>() where ...
The solution is often simple. If both implementations are not interchangable, they should not be part of the same abstraction. So give each implementation its own abstraction, such as
. This completely removes the ambiguity that you have in your DI registration, because now you don't need to have a different registration for a certain area. You just register
both implementations accordingly:
But okay, this assumption was a bit an educated guess. Or well.. not really educated, but I shoot myself in the foot by doing this wrong in the past multiple times, so let's say it's an experienced guess, or painful foot guess :-)
At a high level, I think there are three ways how apply this kind of scoping in Simple Injector:
The most common way is to use scoped lifestyles. Most of the time the lifestyle of such 'child singleton' instance is within a quite clearly defined scope. Usually this is around a request, such as a web request, timer pulse of a windows service, or directly
after the user pushes a button in a desktop application. Simple Injector contains quite a few
, such as Web Request, Web API Request, Per WCF Operation, Lifetime Scope, and Execution Context Scope. All of those lifestyles make use of the
class, which could be seen as the child container container part, with the exception that you can't make registrations in a scope. It's just a cache. Some of the scoped lifestyles manage this scope implicitly for you (such
as Web Request, Web API Request and WCF Operation), while others force you to explicitly begin and dispose such scope (these are Lifetime Scope and Execution Context Scope), just as you would do when you call
So if such instance must live during such clearly defined request, use one of the Scoped Lifestyles.
Another options is to define multiple containers. This is something you can do when your application runs multiple modules that contain a high level of isolation. In this case you can see each module as its own application, but you just happen to run them in
the same app domain. In that scenario it makes sense to give each module its own container instance and optionally have one extra container instance for the 'shell' application that forwards calls to the modules. This is actually something we do in the application
I'm working on right now. Do note most applications should be considered a module on their own, and thus you should have only one container in such application. When you talk about an 'area' however, this might sound like something that lives in isolation.
Splitting up an existing application in modules however, can be daunting. Often there are quite some shared dependencies that you need to split or move to a shared library. In our application we have a shell container and five module containers, and all six
containers share some services that are registered as 'application wide singleton'. The trick here is simply to manually new up those singletons and register them as instance in all containers. Pro tip: nobody ever said that all components should be auto-wired
by your container.
Simple Injector allows you to create your own scope and own scoped lifestyle. For instance, you can create a lifestyle such as a 'per area lifestyle', but it implementation depends on your exact needs. Since I'm not completely sure about your requirements,
I'm unable to be very specific in how to write such lifestyle.