MEF 010

Dec 16, 2008 at 6:43 PM
Edited Dec 16, 2008 at 6:51 PM
I'm trying to do something simple with MEF that I know I'm missing a fundamental on.

Assuming a WinForms app, I have a Program class with a form. I want to display the available plugins for the app. For demo purposes, the plugins are embedded in the assembly but would normally be external assembly files (using the DirectoryCatalog to load).

(note: I've gone back and marked everything as public so ignore the internal or private references)

Here's my Program class:

{code:c#}
static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Compose();
            Application.Run(new Form1());
        }

        private static void Compose()
        {
            var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            container.Compose();
        }
    }
{code:c#}

So I have a host but it's the application, not the main form. Inside the form when it loads I have it talk to a PluginNotifier class to retrieve an enumeration of Plugin classes and add them to a listbox:

{code:c#}
        private void Form1_Load(object sender, EventArgs e)
        {
            var plugins = new PluginNotifier();
            foreach (var plugin in plugins)
            {
                listBox1.Items.Add(plugin.ToString());
            }
        }
{code:c#}

The PluginNotifier class has a collection properties that implement IPlugin. This is marked with the Import tag:

{code:c#}
    internal class PluginNotifier : IEnumerable<IPlugin>
    {
        [Import]
        public IEnumerable<IPlugin> Plugins { get; set; }

        public IEnumerator<IPlugin> GetEnumerator()
        {
            return Plugins.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
{code:c#}

IPlugin simply exposes a name property:

{code:c#}
    internal interface IPlugin
    {
        string Name { get; set; }
    }
{code:c#}

BasePlugin implements IPlugin and provides that name as an auto-property:

{code:c#}
    internal class BasePlugin : IPlugin
    {
        public string Name { get; set; }
    }
{code:c#}

Finally we have a plugin, here's a sample which exports IPlugin:

{code:c#}
    [Export(typeof(IPlugin))]
    internal class Plugin1 : BasePlugin
    {
        public Plugin1()
        {
            Name = "Plugin 1";
        }
    }
{code:c#}

This hopefully trivial sample isn't working as I'm sure I'm missing something key. The Plugins property on the PluginNotifier class is always null (so GetEnumerator() fails).

What am I missing here?

Dec 16, 2008 at 10:32 PM
Hi Bil

In your form's load event you are creating PluginNotifierDirectly with the new command. Because you are creating it directly, it needs to be added to the to the container and composed. To do this you would use the CompositionServices.CreateAttributedPart method which returns a Part, then add it to the container by calling AddPart and then call Compose. Using this approach requires the container to be passed in, which may be undesirable.

Another way to do this which is simpler is to add a collection property if Plugins on the form decorated with an [Import] as your Plugin notifier is simply exposing a collection. (You could import the PluginNotifier if you wanted to, but it seems like your only use currently is to exposed the collection) Then instead of creating the form, retrieve it from the container. To do this you would do the following.

1. Add an [Export] to the Form1 class.
2. Add the property to the form.

[Import]
public IEnumerable<IPlugin> Plugins {get;set;}

3. Change Main and Compose to use the following code.

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var Form1 = Compose();
            Application.Run(Form1);
        }

        private static Form1 Compose()
        {
            var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            var Form1 = container.GetExportedObject<Form1>(); //Get a Form1 instance, no need to call compose unless manually adding to the container
            return Form1
        }
4. In your Form1_Load change the code to go against the Plugins property directly.

Now when the app starts up, the Form will be composed with each of the plugins.

Dec 19, 2008 at 4:01 PM
@Glenn: Thanks for the post. I did something different (still using the Application.Run(new Form1())) that worked (basically moving the composition into the form itself). I'm not too happy with it because I didn't want the form to be responsible for infrastructure. I didn't know about this GetExportedObject call but it looks like a better solution. Thanks for the sample. Is there going to be a P&P guide or something on MEF at some point (I would assume yes) rather than disparate posts and things we're going to have to Google later to find ways of doing things?
Dec 19, 2008 at 7:02 PM
@Glenn: What would the Compose method look like if I were using the DirectoryPartCatalog? I can't seem to get it to work with anything but the AttributedAssemblyPartCatalog. Thanks.
Dec 31, 2008 at 2:05 PM

The DirectoryPartCatalog just checks a folder for assemblies (.dll and .exe) files which it then inspects for parts. If you need more than one catalog u can use an AggregatingComposablePartCatalog

var catalog =
    new AggregatingComposablePartCatalog();
catalog.Catalogs.Add(new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryPartCatalog("c:\"));

var container =
   new CompositionContainer(catalog);