Search code examples
c#pluginsdependency-injectionsimple-injector

Creating plugin instances with context using DI


I'm refactoring our application to include Dependency Injection (via constructor injection) and have run into a tricky corner-case:

We currently have ImageViewer objects that when instantiated search the assembly for ImageViewerPlugin (an abstract base class) instances, instantiating them using reflection. This is done in the constructor of the ImageViewer using a method (called in a loop for all concrete plugin types) that is similar to:

private ImageViewerPlugin LoadPlugin(Type concretePluginType)
{
  var pluginConstructor = concretePluginType.GetConstructor(
    BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public,
    null,
    new[] { typeof(ImageViewer) },
    null);

  return (ImageViewerPlugin) pluginConstructor.Invoke(
    new object[] { constructorParameter });
}

The ImageViewerPlugin class looks roughly like this:

internal ImageViewerPlugin
{
  protected ImageViewer _viewer;
  protected ImageViewerPlugin(ImageViewer viewer)
  {
    _viewer = viewer;
  }
}

A concrete implementation looks roughly like this:

internal AnImageViewerPlugin
{
  public AnImageViewerPlugin(ImageViewer viewer) : base(viewer)
  {
  }
}

Each ImageViewer instance has its own collection of ImageViewerPlugin instances.

Now that the application being refactored to use a DI container and constructor injection I'm discovering that these plugins have dependencies (which were previously hidden through the use of global static classes) that need to be resolved by the DI container, but I'm not sure how to do that without using a Service Locator (an anti-pattern).

The most sensible solution seems to be to create these plugin instances using DI. This would allow me to add extra constructor parameters to have the dependencies they rely on injected via constructor injection. But if I do that, how can I pass a specific viewer parameter value while having the rest of the parameter values injected?

I thought an ImageViewerPluginFactory would help to achieve this but can't see how to implement such a factory as each plugin is likely to have a different constructor signature.

How can I solve this scenario? Or am I approaching this totally the wrong way?


Solution

  • So you have an ImageViewer that depends on a collection of ImageViewerPlugin instances, which each depend on n ImageViewer that depends on a collection of ImageViewerPlugin instances, which each depend on n ImageViewer that depends on a collection of ImageViewerPlugin instances, which each depend on an ... well you get the picture :-)

    This is a circular reference. Leaving the whole DI container thing aside, how would you create this hierarchy when doing this manually? With constructor injection you can't. It can be seen from the following example:

    var plugin = new ImageViewerPlugin( [what goes here?] );
    var viewer = new ImageViewer(plugin);
    

    You will have to break this dependency cycle somehow and the general advice is to fall back on property injection in this case:

    var plugin = new ImageViewerPlugin();
    var viewer = new ImageViewer(plugin);
    
    // Property injection
    plugin.Viewer = viewer;
    

    But even more, you should take a good look at the design of the application, since a circular reference often is an indication of a problem in the design. For instance, take a good look what behavior the plugin needs from the viewer and what behavior the viewer needs from the plugins. You might be able to extract this to another class, as follows:

    var imageServices = new ImageServices();
    var plugin = new ImageViewerPlugin(imageServices);
    var viewer = new ImageViewer(imageServices, plugin);
    

    This solves the problem completely, but it depends on your situation whether this is feasible.

    With the last solution, registration with Simple Injector would be rather straight forward. When breaking the dependency using property injection, you can make use of the RegisterInitializer(Action) method. It allows you to break the dependency cycle. For instance:

    container.RegisterInitializer<ImageViewer>(viewer =>
    {
        foreach (var plugin in viewer.Plugins)
        {
            plugin.Viewer = viewer;
        }
    });