Introduction
Matacontainer enables developers to configure dependency injection infrastructure without being dependent on a particular implementation. Key Metacontainer Use Cases include:
- Implementing Factory pattern using the ‘child container per factory’ philosophy
- Integrating existing configuration-and-factory based libraries with dependency injection infrastructure
Design
When designing the Metacontainer, we have decided to define it as an extension to the Common Service Locator[^]. We added the following methods:
- Register mapping from service to implementation
- Register mapping from service to implementation creation delegate
- Create child container
As you can see, there are only three new methods added to the IServiceLocator
interface and, by adding these methods, a myriad of new usage scenarios are enabled. By the way, to provide a programmer-friendly interface, we also defined a set of extension methods to the IMetaContainer
interface. These additional methods provide type-safe equivalents of base methods by using generic parameters.
The MetaContainer project was started by us, but it is meant to be developed by the community. We have prepared three most commonly used (as we believe) implementations:
These implementations should be treated as reference implementations whose main goal is to show the possibilities of MetaContainer. We encourage all container implementers to provide their own implementations which, we believe, could be tuned better, than our reference product, to a particular container. On the other hand, although designed as a proof-of-concept, implementations provided by us are fully tested and can be safely used in your code, and we will make efforts to provide some support for them.
Factory Pattern
When developing an application using the Inversion of Control paradigm, one can face the necessity of creating a factory object for crating instances of some class hierarchy. In the traditional environment, a factory method is responsible only for choosing which implementation to create in a particular case, based on the arguments provided by the caller. In the Inversion of Control environment, though, there are two additional tasks for a factory class:
- Injecting dependencies to factory-produced objects
- Enabling dependency injection infrastructure to inject interceptors into factory-produced objects
Classic solutions of these problems include:
- Using a main application container to create instances in the factory method. The main disadvantage of this method is the requirement that the factory-produced classes must be known and configured in the app-level container.
- Creating instances using a simple
new
operator and then passing the constructed instance to the container for injecting interceptors. Disadvantages of this approach include resolving dependencies of the produced objects at the level of the factory and the necessity of providing abstraction over the injecting interceptors functionality.
The MetaContainer enables us to develop a much better solution. It is based on the ability of the factory to create a child container of the main application container (represented as a IMetaContainer
interface) and store it privately. This factory-level container can be filled with mappings specific for the class hierarchy the factory is created for. These mappings are unavailable to external entities, ensuring that no one can depend on particular implementation classes.
Integrating configuration-and-factory based libraries
There are a lot of existing libraries that are based on the pattern of combining XML-based configuration with static factory facades. These factories usually produce instances of classes implementing well-known interfaces which client programmers use to interact with them.
A canonical example of this approach is the Enterprise Library[^] up to version 4.0. Because the way Enterprise Library was designed was an official guidance of the Patterns & Practices group for library architects, many other libraries were implemented the same way.
Our goal in integrating those existing libraries into the dependency injection infrastructure is to enable the container to resolve dependencies on library abstractions. To achieve this, we have to register in the container the mappings of these abstractions to the methods which can create their implementations. These methods, of course, can be based on existing factory functionality built into the library. The MetaContainer lets us do this. All one have to do is to invoke the piece of code which performs the configuration scan and registers all the necessary mappings.