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?
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();
}
}
[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();
}
}
}
[Export(typeof(IPlugin))]
public class SomePlugin : IPlugin {
[Import] public ICommonObject CommonObject { get; set; }
public void Start() {
Console.WriteLine("SomeValue (from plugin): " + CommonObject.SomeValue);
}
}
SomeValue (from MainApp): foo
SomeValue (from plugin):
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