Lightweight Adaptation – Coming Soon
The type-to-type mapping of an IoC container obscures the fact that in reality, IoC configuration describes object graphs.
Perhaps this is why it is sometimes difficult to reason about how component instances will come together at runtime?
The problem dealt with in this article fits this description – a simple object graph that is unintuitive to build with a typical container.
Back to the Gang of Four…
The classic “Gang of Four”-style Adapter pattern ‘adapts the interface of one class to another’. Common scenarios where adapter-like structures appear are, for example:
-
where a wrapper is needed in order to work efficiently with an underlying abstraction;
-
between different representations of the same thing, e.g. the endless variations on ILog; or,
-
between corresponding elements in different logical models
We’ll use a fairly broad definition of the pattern, in the context of an image editing application.
A ToolbarButton
has an ICommand
that is invoked when the button is clicked. ToolbarButton
is an adapter for ICommand
that allows the command to be invoked from the user interface.
Here we’ve attached an instance of ToolbarButton
to an instance of SaveCommand
, which implements ICommand
. The classes are:
public interface ICommand
{
void Execute();
}
public class SaveCommand : ICommand
{
public void Execute()
{
// Save the current image
}
}
public class ToolbarButton
{
ICommand _command;
public ToolbarButton(ICommand command)
{
_command = command;
}
public void Click()
{
_command.Execute();
}
}
So… from an IoC perspective, what is interesting about this example?
Adapting Multiple Implementations
An application has more than one kind of command. An image editor may have Save, Open and a whole host of other commands:
But, even though all of the command implementations are the different, the widget representing them on the user interface is implemented by the same component.
To bring the example together let’s add an EditorWindow
that accepts all of the toolbar buttons as an enumerable dependency.
public class EditorWindow
{
public EditorWindow(IEnumerable<ToolbarButton> toolbarButtons)
{
// Left to the reader’s imagination
}
}
Our intention here is that for each ICommand
in the container, a ToolbarButton
will be created to wrap it, and all of these will be passed along to the EditorWindow
via the IEnumerable<T>
relationship type .
These might be registered with Autofac in the following way:
var builder = new ContainerBuilder();
builder.RegisterType<SaveCommand>().As<ICommand>();
builder.RegisterType<OpenCommand>().As<ICommand>();
builder.RegisterType<ToolbarButton>();
builder.RegisterType<EditorWindow>();
using (var container = builder.Build())
{
var window = container.Resolve<EditorWindow>();
window.Show();
}
Yet, there is something wrong. The static structure of our code is correct, but as I lamented earlier, the object graph isn’t what we expect.
The problem is that, as far as the container is concerned, there is only one ToolbarButton
component. This will be initialised with whatever happens to be the default implementation of ICommand
. Autofac’s last-in-wins policy means that OpenCommand
will be used as the default implementation of ICommand
, and SaveCommand
will be ignored.
This happens because the container builds object graphs top-down; first it looks for ToolbarButton
, then it looks for an ICommand
to satisfy its dependencies.
Registering Adapters
What we’d really like is to create multiple ToolbarButtons
, each attached to a different underlying ICommand
implementation.
To configure this, Autofac 2.2 essentially lets us switch the composition of ToolbarButton
from top-down to bottom-up. First we find all of the ICommand
implementations, and then we construct a ToolbarButton
for each.
Configuration of the container is the same as before, except we change:
builder.RegisterType<ToolbarButton>();
to:
builder.RegisterAdapter<ICommand, ToolbarButton>(cmd => new ToolbarButton(cmd));
The RegisterAdapter()
method takes two types as parameters: the service to adapt from, and the component type to adapt to.
Autowiring isn’t supported for the adapter component in this scenario (yet), so we include a lambda expression describing how the adapter is constructed given one of the adapted instances.
Overloads of RegisterAdapter()
exist for passing in parameters and an IComponentContext
from which the adapter’s other dependencies can be resolved if necessary.
Aside: @joshuamck suggested an alternative syntax that I’d also considered, along the lines of builder.RegisterType<ToolbarButton>().Adapting<ICommand>()
. I think this is more in line with the intent of Autofac’s builder syntax, so depending on how the implementation goes we might see this in the 2.2 release version.
Composing with Metadata
In the example, commands can be given metadata to allow the command name to be displayed on the button.
First the commands are given names:
builder.RegisterType<SaveCommand>()
.As<ICommand>()
.WithMetadata("Name", "Save File");
builder.RegisterType<OpenCommand>()
.As<ICommand>()
.WithMetadata("Name", "Open File");
Then, the name parameter is added to the ToolbarButton constructor and the metadata is consumed:
builder.RegisterAdapter<Meta<ICommand>, ToolbarButton>(cmd =>
new ToolbarButton(cmd.Value, (string)cmd.Metadata["Name"]));
Adapters, like other relationship types in Autofac, compose nicely with each other as the use of Meta<T>
shows.
Wrapping Up*
*Excuse this terrible pun.
Lightweight adapters are a nice little helper coming in Autofac 2.2. While these scenarios don’t arise every day, when they do, container support is very convenient.
Download an example (including an Autofac 2.2 preview build) here.