Declarative Context Adapters
The first post in this series introduced the problem of accessing IoC container features dynamically.
We brushed over two common patterns:
- Global Container (or Static Service Locator)
- Injected Context
Before we move on from this topic, I'd like to look at one more pattern in this theme that may not be in common use, but deserves some attention anyway.
This pattern I've labeled Declarative Context Adapter, and it builds on the strengths of Injected Context while moving from imperative implementation to declarative configuration.
After composition, a component requires dynamic access to features of the container that hosts it (the context.)
Commonly, this involves dynamically creating or locating instances from the container (e.g. see ControllerProvider in this article.)
- Provide a domain-specific interface to the required functionality.
- Describe how this interface should interact with the container using metadata or configuration.
- A compliant implementation is supplied based on the metadata.
One example of a Declarative Context Adapter is the generated factories feature of Autofac.
This feature uses .NET delegate types as the 'domain-specific interfaces'. The Shareholding.Factory delegate type below plays this role:
In this example, we want invocations of the Factory delegate to return instances of the Shareholding class created and wired up by the container. This example is a bit contrived, since usually there is some abstraction involved, but hey, it is just an example :)
The ContainerBuilder's method-chaining API allows the mapping between the delegate to the container's Resolve() method to be specified:
This is specified via the RegisterGeneratedFactory() method.
The only declarative configuration in this example apart from the delegate type itself, is the TypedService parameter that tells Autofac which service from the container should be returned whenever the delegate is invoked.
(Additional configuration options can describe parameter mappings for the delegate parameters, however these aren't available in the current release builds.)
Other components that need to instantiate Shareholdings can now have instances of Shareholding.Factory injected into them.
To call this technique a 'pattern' is a bit premature, since the Autofac feature is the only implementation you can put your hands on.
The same functionality could be implemented on different containers, perhaps via AOP. It would also be preferable to support interfaces as well as delegate types.
While the examples have been related to instantiation or service lookup, context adapters could be defined for other container features like lifetime management or even component registration. Anywhere that Injected Context is used, it should be possible to generate a configuration-driven alternative.
The tiny example from above can be downloaded here if you'd like to experiment with it.