The short question is: Given a library warrants using a particular IOC container for its internals, when an application consumes that library, given the app warrants using an IOC container for wiring its dependencies, given if the the two containers are different, how can they play well together?
The scenario is, the application has classes defined that depend on types from the library. So when the application container attempts to build such a class, it needs to know how to resolve the type that lives in the library.
Here's the long winded question:
This question does seem to have been asked in different shapes and form before on SO, but I can't seem to find the answer I need so I am going to have a go at it with a hypothetical _over_simplified_ concrete example. We want to write a library for logging that users can include as a package in their solution to get logging functionality out of the box. The public interfaces the library exposes are..
public interface ILogger {}
public interface ITarget {}
Concrete implementations are
internal class Logger: ILogger { public Logger(ITarget target) {}}
internal class FileTarget : ITarget {}
Requirements are if the user includes our package and defines a class with a property of type ILogger
or has a ctor argument of type ILogger
then our library is responsible for injecting a concrete implementation for that interface into the user defined class. By default that injected logger will go to the file system for logging because the default implementation of an ITarget
injected into the ILogger
implementation is a FileTarget
by our library.
If the user decides to write a class implementing the ITarget
interface then our library will use that to inject into the Logger
class and not use its default FileTarget
implementation.
SO what I wish to demonstrate, is their is a bi-directional dependency here.
Our library depends on the user's assemblies, since it needs to scan the user's assemblies to load any extension points (i.e. an
ITarget
implementation) and inject those into its own objects ahead of any default implementations.The user's assemblies depends on the library, since if the user chooses to define a class with an
ILogger
interface as a dependency, then that user object should get a concrete reference to that interface provided at runtime by our library.
The easy solution is if the user and our library are both using the same IOC container, then problem is solved. But this is a strong assumption. What I wish to do is
- Use an IOC container with the library that caters best to the library's requirement, in my case its Ninject.
- At run time somehow provide a mechanism for the user to call via some API into my library that will ensure Ninject is fired up and it scans the user's assemblies, and wires everything taking into account all extension points.
So far so good, its perfectly achievable, but here comes the tricky part.
- if the user is also using Ninject, then problem automatically solved, since Ninject already knows how to resolve Interfaces living in our library. But what if the user decides to use his/her choice of IOC container?
I almost want to define some sort of child container functionality in the library with an interface like such
public interface IDependencyResolvingModule { T Get<T>(); []T GetAll<T>(); }
and provide an implementation that uses our library's choice of container (i.e. Ninect) to resolve the type requested in the two methods define above.
I want the user's IOC container to have some functionality where if it can't resolve a dependency (i.e. an ILogger
), it should hook into the IDependencyResolvingModule
implementation and ask for the dependency.
This way our library gets to use its choice of IOC Container, and the user's code has a way to resolve dependencies that its IOC container has no clue about. Wouldn't this solution work if IOC containers out there some how provided functionality to register singleton instances of any IDependencyResolverModules
found in assemblies in the executing assembly's dir and when they can't resolve a type, ask any of the singleton modules?
But barring a solution that requires every other IOC container to accommodate, how else can this be solved? SO the problem in a few lines is, when a third party assembly chooses to use an IOC container for its internals, what is an easy solution such that this library can simply provide a mechanism for an IOC container sitting outside to hook into and resolve dependencies that live in the library.