2

I'm building a WPF (desktop) application, utilizing dependency injection, a DI container and the Register-Resolve-Release pattern. My application also loads plugins from separate assemblies during startup, and the plugins are registered with the DI-container. I resolve my entire object graph once in the composition root at startup, however, I'm having some issues with resolving my plugins, and I'm wondering whether it is OK to inject the DI container into a factory to resolve unknown types?.

My plugins all:

  • implement a common interface, IPlugin
  • need to be transient (since multiple instances can live simultaneously)
  • are dependent on runtime values to configure them when they are being initialized
  • might, or might not, have other dependencies injected through their constructor

I have started to implement a PluginFactory to initialize the plugins when I need them, since, as Mark Seemann says: "Any place where you need a run-time value to construct a particular dependency, Abstract Factory is the solution."

However, I cannot just new() the plugins in my factory, since I don't know their types and dependencies.

I have thought of several solutions:

  1. I can inject the DI container into the constructor of the PluginFactory, resolve the plugins at runtime and call it a day. However, that goes against the RRR-pattern.

  2. I can inject IEnumerable<IPlugin> plugins through the constructor of the PluginFactory. However, this would violate the transient requirement, since I would only have one copy of each instance[*Wrong]. I can solve this by implementing ICloneable on all my plugins and clone them in the factory, however, this is both hard to do correct with dependencies and clutters all the plugins.

[*Wrong] Edit: Note, according to the selected answer, this is wrong! Therefore, option #2 is the best option, as long as you register the lifetime of the plugins to be transient.

  1. I can inject the plugin types rather than instances into the PluginFactory and use the Activator.CreateInstance<T>() to create the instances. I could even define a common constructor signature for the plugins and pass parameters to the Activator to initialize them with dependencies. However, this violates my requirement that the plugins have different dependencies, and it also leads to the constrained construction anti-pattern.

  2. Finally, building on 3. I can inject plugin types and use reflection to sort out the dependencies of the plugins. However, this sounds like it is a lot of work and makes me wonder whether I'm trying to build or copy the resolve methodology from DI containers. The latter is something I definitely don't want to do or see the point with.

So, out of my alternatives in prefer #1, even though that goes against the RRR pattern.

What would be the best way to instantiate my plugins (I'm leaning towards #1). Have I missed some alternatives entirely? Or have I perhaps misjudged some of my other alternatives?

Community
  • 1
  • 1
Gedde
  • 1,150
  • 10
  • 19
  • "However, this would violate the transient requirement, since I would only have one copy of each instance.". That completely depends on which DI library you use. In [Simple Injector](https://simpleinjector.org) `IEnumerable` dependencies are truly provided as streams, which means that the container is asked to resolve that instance every time you iterate the enumerable. This preserves the lifestyle of your plugins. – Steven Sep 20 '14 at 14:53
  • That is very interesting. I assumed too much here, and the container is obviously smarter than me. Could you please add this comment as an answer? I feel the comment is answering my real issue in a better than the other answer you provided :) – Gedde Sep 20 '14 at 16:13
  • It depends on your container of choice. Which library are you currently using? – Steven Sep 20 '14 at 16:21
  • SimpleInjector :) If you provide a short snippet explaining how to set the life time of the individual plugins after registering them (with RegisterAll) that would be great. – Gedde Sep 20 '14 at 16:48

2 Answers2

4

Since you are already referencing Mark Seemann, take a look at his Composition Root article and his Service Locator role vs mechanics article. Long story short, it's okay to reference/inject the container into infrastructure components that are part of your Composition Root.

So the solution is to define an IPluginFactory abstraction and place it in a core library of your application. Within your composition root (note: see the CR as a layer, not a class) you can create an implementation for this abstraction and here its okay to inject the container.

Steven
  • 166,672
  • 24
  • 332
  • 435
3

In Simple Injector, injected IEnumerable<T> instances behave as streams. This means that every time you iterate the enumerable, the container is asked again for an instance. The Simple Injector documentation states:

Note: Simple Injector preserves the lifestyle of instances that are returned from an injected IEnumerable<T> instance. In reality you should not see the injected IEnumerable<IValidator<T>> as a collection of implementations—you should consider it a stream of instances. Simple Injector will always inject a reference to the same stream (the IEnumerable<T> itself is a singleton) and each time you iterate the IEnumerable<T>, for each individual component, the container is asked to resolve the instance based on the lifestyle of that component. Regardless of the fact that the [consuming component] is registered as singleton, the validators it wraps will each have their own specific lifestyle.

The container will return the instance based on its lifestyle. If the instance is registered as transient, it means that you get a new instance every time you iterate the collection. In a sense, enumerables injected by Simple Injector behave as factories. Here's a little test that shows this behavior:

[TestMethod]
public void EnumerablesBehaveAsStreams()
{
    // Arrange
    var container = new Container();

    container.Collection.Register<ILogger>(typeof(SqlLogger), typeof(FileLogger));

    IEnumerable<ILogger> loggers = container.GetAllInstances<ILogger>();

    // Act
    ILogger sqlLogger1 = loggers.First();
    ILogger sqlLogger2 = loggers.First();

    // Assert
    Assert.AreNotSame(sqlLogger1, sqlLogger2);
}

Some developers are confused by this, but I'd argue that this is the only correct behavior, because IEnumerable<T> is the definition for a stream, and Simple Injector respects that and, therefore, gives you a stream. There are some interesting advantages in doing this. For instance, it allows enumerables to be injected into singletons, while the lifestyle of the elements is preserved.

If you want to register a collection of plugins, this would be the way to do it:

List<Type> pluginTypes = LoadPluginTypes();

container.Collection.Register<IPlugin>(pluginTypes);

Collection.Register forwards the resolving of the registered types back to the container and, therefore, respects the lifestyle in which each type is registered. In the example above we didn't register those types separately. This means that all elements are assumed to be transient by default. But you can override this as follows:

List<Type> pluginTypes = LoadPluginTypes();

container.Register(pluginTypes.First(), Lifestyle.Singleton);

container.Collection.Register<IPlugin>(pluginTypes);

Now the first plugin in the collection is registered as singleton, while the others are still transient. If you want to register them all with the same lifestyle, you can do the following:

List<Type> pluginTypes = LoadPluginTypes();

pluginTypes.ForEach(type => container.Register(type, Lifestyle.Singleton));

container.Collection.Register<IPlugin>(pluginTypes);

Another options is to pass on a collection of Registration instances into the Collection.Register method:

List<Type> pluginTypes = LoadPluginTypes();

container.Collection.Register(typeof(IPlugin),
    from type in pluginTypes
    select Lifestyle.Singleton.CreateRegistration(
        typeof(IPlugin), type, container));

In this case, there's no practical difference between the previous registration, but registering Registration instances becomes interesting when you want to do more advanced things such as sharing the same registration instance over multiple abstractions.

But the stream behavior can be very useful in your factory, because you can simply inject an IEnumerable<IPlugin> into your factory and the lifestyle will preserved:

public class PluginFactory : IPluginFactory
{
    private readonly IEnumerable<IPlugin> plugins;

    public PluginFactory(IEnumerable<IPlugin> plugins)
    {
        this.plugins = plugins;
    }

    public IPlugin GetPluginByName(string name)
    {
        return this.plugins.Single(plugin => plugin.Name == name);
    }
}

The only thing to watch here is that, if you are interested in returning one plugin at a time by filtering over the collection, iterating the collection will produce many plugin instances, even though you might only be interested in one. In the example above, Single will cause all plugins to be created, while you only return one. More efficient would be to do First, because that stops when a plugin matches the criteria and this prevents all later plugins from being created.

Simple Injector is really fast, so the collection can become quite big before this typically becomes a problem, but it is something to watch out for. If your intention is to filter the list and always select one, a different approach might be better.

Steven
  • 166,672
  • 24
  • 332
  • 435