views:

32

answers:

1

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?

+2  A: 

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.

Mark Seemann
Thanks for replying, @Mark. If I understand you correctly, my example code already follows the RRR pattern... right? (With the exception that I don't release the container, as it needs to stay alive at least as long as the root component `MainForm`. I suppose, the ideal way to do this would be to make plugins `IDisposable` and release the container in the `Dispose` method.)
stakx
There's nothing in your code that suggests that you don't follow RRR, but it's hard to tell. In any case, keep in mind that the SingleInstance lifestyle only defines a container-scoped Singleton. It's not a true Singleton, so you can't share the MainForm in this way.
Mark Seemann