Search code examples
.net-coresingletoncompositionmef2

Create Singleton instance in MEF2


I'm creating an application with multiple plugins using MEF2 (Microsoft.Composition). These plugins should import some common object and they should all share the same instance of this object... so a typical Singleton.

However, when I [Import] this common object into my plugins they all get their own copy instead of a shared one.

In .NET Framework MEF1, all object were created as singletons by default. This does not seem to be the case for .NET Core MEF2.

How can I make sure that all my plugins get the same singleton instance of my common object?

Sample code

Startup

static void Main(string[] args) {
    ContainerConfiguration containerConfig = new ContainerConfiguration()
        .WithAssembly(Assembly.GetExecutingAssembly())
        .WithAssembly(typeof(ICommonObject).Assembly);

    using (CompositionHost container = containerConfig.CreateContainer())             {
        _mainApp = container.GetExport<MainApp>();
        _mainApp.Start();
    }
}

MainApp

[Export(typeof(MainApp))]
public class MainApp {
    [Import] public ICommonObject CommonObject { get; set; }
    [ImportMany] public IEnumerable<IPlugin> Plugins { get; set; }

    public void Start() {
        CommonObject.SomeValue = "foo";
        Console.WriteLine("SomeValue (from MainApp): " + CommonObject.SomeValue);

        foreach (IPlugin plugin in Plugins) {
            plugin.Start();
        }
    }
}

Plugin

[Export(typeof(IPlugin))]
public class SomePlugin : IPlugin {

    [Import] public ICommonObject CommonObject { get; set; }

    public void Start() {
        Console.WriteLine("SomeValue (from plugin): " + CommonObject.SomeValue);
    }
}

Output

SomeValue (from MainApp): foo
SomeValue (from plugin):

Solution

  • After much trial and error I seem to have finally found a solution myself.

    The trick seems to be to use ConventionBuilder. This has an extension method called .Shared() which makes all objects derived from a specific type into a Singleton.

    For my code examples, just add the following to the top of the Startup code:

    ConventionBuilder conventions = new ConventionBuilder();
    conventions.ForTypesDerivedFrom<ICommonObject>()
        .Export<ICommonObject>()
        .Shared();
    
    ContainerConfiguration containerConfig = new ContainerConfiguration()
        .WithAssembly(Assembly.GetExecutingAssembly(), conventions);
    

    For some reason, the object implementing ICommonObject doesn't even need an [Export] attribute. In any case, the output from the example is now:

    SomeValue (from MainApp): foo
    SomeValue (from plugin): foo