简体   繁体   中英

Using MEF to create multiple instances of a plugin

How does one create multiple instance of an exported part in a MEF container upon demand?

The existing application is using MEF to compose its components and load several plugins from a directory. The Container.Compose() method is used to create single instances of each plugin/component when the application loads. Now, a new requirement come up that requires to load a given set of plugins multiple times.

The basic design is a device controller class that connects to a measurement device. The plugins are basically UI views that visualize the measurement data from that device in different ways. Thus, the plugins share the same instance of the device class. Here is a basic example of that idea. Two different plugins ( PluginA and PluginB ) implement the IPlugin interface and are exported as IPlugin . Each of them imports a shared instance of the Device class. The main program imports all IPlugin exports.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;

namespace MEF.Framwork
{
    [InheritedExport]
    interface IPlugin
    {
        string Name { get; }
        int ID { get; }
        Device Device { get; }
    }

    class PluginA : IPlugin
    {
        public string Name => "PluginA";
        static private int ct = 0;
        public int ID { get; private set; }

        public Device Device { get; set; }

        [ImportingConstructor]
        public PluginA(Device device)
        {
            Device = device;
            ID = ++ct;
            Console.WriteLine($"new {Name} #{ID}");
        }
    }

    class PluginB : IPlugin
    {
        public string Name => "PluginB";
        static private int ct = 0;
        public int ID { get; private set; }

        public Device Device { get; set; }

        [ImportingConstructor]
        public PluginB(Device device)
        {
            Device = device;
            ID = ++ct;
            Console.WriteLine($"new {Name} #{ID}");
        }
    }

    [Export]
    class Device
    {
        static private int ct = 0;
        public int ID { get; private set; }

        public Device()
        {
            ID = ++ct;
            Console.WriteLine($"new Device #{ID}");
        }
    }

    class Program
    {
        [ImportMany(typeof(IPlugin))]
        public IEnumerable<IPlugin> Plugins { get; set; }

        static void Main(string[] args)
        {
            new Program().Main();
        }

        void PrintPlugins()
        {
            Console.WriteLine($"Plugins:");
            foreach (var x in Plugins)
            {
                Console.WriteLine($" - {x.Name} #{x.ID}: Device #{x.Device.ID}");
            }
        }

        void Main()
        {
            var catalog = new ApplicationCatalog();
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);
            PrintPlugins();
            Console.ReadKey();
        }
    }
}

Now, the new requirement is to create a new independent set of plugins and a new shared Device object to connect and visualize the data of another device. In addition to have another complete independent set of plugins, a new plugin might be implemented later that uses all available device to do some combined analysis.

How can I create another set of those plugins including the Device using the MEF container? Is this even possible in this way?

I thought of creating a new container Container2 from the same catalog and compose another set of plugins. This seems to do exactly what I expect but how can the containers and it s instances/part be combined afterwards. If a new plugin is added later to use all available s instances/part be combined afterwards. If a new plugin is added later to use all available Device` instance, how would this be possible. This seems a little bit odd as the composing part of the application must already know that there are some plugins that are expected to be loaded multiple times.

        void Main()
        {
            var catalog = new ApplicationCatalog();
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);
            PrintPlugins();

            // Add Plugins from new Container2
            var container2 = new CompositionContainer(catalog);
            Plugins = Plugins.Concat(container.GetExportedValues<IPlugin>());
            PrintPlugins();

            // Remove Plugins from Container2
            var plugins2 = container.GetExportedValues<IPlugin>();
            Plugins = Plugins.Where(p => !plugins2.Contains(p));
            PrintPlugins();

            Console.ReadKey();
        }

Thanks for any input!

Just in case someone else is looking for a solution, here is what works for me:

I'm using the CompositionBatch to add/remove new items to/from the CompositionContainer . In order to resolve the dependencies correctly, I have removed the Export from all plugins and created a master or factory object that creates and exports all plugins:

The factory object would look somethin like this:

class DeviceFactory
{
    [Export]
    public Device Device { get; set; }

    [Export]
    public IPlugin PluginA;

    [Export]
    public IPlugin PluginB;

    public DeviceFactory()
    {
        Device = new Device();
        PluginA = new PluginA(Device);
        PluginB = new PluginB(Device);
    }
}

The add/remove functionality uses the CompositionBatch and stores all added objects in a dictionary for later removal. Also the added item will not be disposed by the container, so it might be disposes manually if required.

private Dictionary<object, ComposablePart> Parts = new Dictionary<object, ComposablePart>();
void Add(object value)
{
    var batch = new CompositionBatch();
    var part = batch.AddPart(value);
    Parts.Add(value, part);
    Container.Compose(batch);
}
void Remove(object value)
{
    var batch = new CompositionBatch();
    batch.RemovePart(Parts[value]);
    Container.Compose(batch);
}

This seems to work fine in my case, but leaves still a few open issues.

First, one has to take care about lifetime of the object. Here one need to dispose the added objects manually when the container is disposed.

Second, I have not found any method to remove the initial instance of the plugin from the container. But it should be possible to exclude the first instance from the initial call to Container.ComposeParts() and add the instance via the mentioned Add() method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM