stakx stakx - 27 days ago 28
C# Question

DI (Autofac) in a plugin architecture: Is one separate DI container per plug-in OK?

I am trying to introduce DI (with Autofac) into an existing Windows Forms application.

This application has a basic plug-in architecture where each plugin displays its own form. On startup, the application scans registered assemblies for types that implement

IPlugin
, and then activates these using
Activator.CreateInstance
:

public interface IPlugin
{
Form MainForm { get; }
}


I cannot change this given framework. This means, each plugin class is instantiated through non-DI means, and it seems to me that I will therefore have to bootstrap a separate DI container for each plugin.

My question is, is creating a separate
ContainerBuilder
and container per plugin OK and still reasonably efficient? (There will be approx. 10 different plugins.) Or should there only be one DI container for the whole application?

I've provided some sample code of my current solution below.




using Autofac;
using System.Windows.Forms;

public class Plugin : IPlugin // instantiated by Activator
{
public Form MainForm { get; private set; }

public Plugin() // parameter-less constructor required by plugin framework
{
var builder = new ContainerBuilder();
builder.RegisterModule(new Configuration());
var container = builder.Build();

MainForm = container.Resolve<MainForm>();
// ^ preferred to new MainForm(...) because this way, I can take
// advantage of having dependencies auto-wired by the container.
}
}

internal class Configuration : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<MainForm>().SingleInstance();
// ... more plugin-specific registrations go here...
}
}

internal class MainForm : Form { /* ... */ }


I'm also not sure whether creating a container in the plugin constructor and then simply forgetting about it, but leaving it to do auto-wiring in the background, is OK?

Answer

You container usage should ideally follow the Register Resolve Release pattern (RRR). I know that you said that you can't change the current Activator.CreateInstance usage, but it can still be helpful to understand how it really ought to be.

If you didn't have that constraint, there should only be a single container instance, hosted by the parent application itself. This could then be used to compose all the plugins. This would enable the plugins to share dependencies. This is the route taken by MEF, which also addresses extensibility scenarios.

Now, since you can't do that, the next-best thing you can do is to have a container per plugin as you suggest. It mostly becomes an implementation detail at that point. In each plugin, you should still follow the RRR pattern.

Would it be inefficient? Unless you have a lot of plugins and create and destroy them all the time, a couple of different containers shouldn't matter much. However, it's better to measure than to create premature optimizations.

In this scenario, you can only share a container by making it static. However, this makes things more complicated than they need to be, so don't go that route unless absolutely necessary.