Tag Archives: MEF

Using the Managed Extensibility Framework (MEF) to implement a third-party plugin solution. Part 2–Plugin Support in the Application

Now that I have a feature-complete application from my perspective, I am ready to add plugin support so that other developers can extend the functionality of my application.  I don’t want to make my application open source, so I want to just expose enough information about my application to them that they are able to create plugins.

In order to give my application access to MEF, I need to add a reference to the appropriate namespace within my windows forms application.  The namespace I need to reference is System.ComponentModel.Composition.  Once I’ve done that, I need to add a new project to my solution of type “Class Library.”  This project will be my “Plugin API” and will compile to a .dll file that I can distribute to developers as a part of my SDK so that they can create plugins that adhere to my standard.  After creating my new project, I need add a reference to that project from within my Windows Forms project.

At this point, my application solution looks like this:

Solution Explorer

Here are the contents of IPlugin.cs in the MyPluginApp.Plugins project:

using System;

namespace MyPluginApp.Plugins
{
    //This interface defines what we expect a plugin developer to impliment in order for our application to make use of their plugin
    public interface IPlugin
    {
        //This method's signature matches the MyButtonClickedEventHandler in our application
        void HandleNumbers(decimal num1, decimal num2);
    }

    //MEF allows for metadata to be exported along with the object itself, we need a separate interface to receive any metatdata that we expect
    // plugin developers to include as well.
    public interface IPluginMetaData
    {
        string Name { get;}
    }
}

Next I need to update my UI so that it is able to display the list of available plugins and allow the user enable/disable them at will.  I’ve opted to use a checked list box to achieve that goal.  The plugins will load initially disabled, and the user simply needs to check the box next to the plugin in order to enable it, similarly unchecking the box will cause the plugin to be disabled.

Here is my New UI

MyPluginApp

And now the code for the form, including the code to enable plugin support.  New lines of code are highlighted for your convenience.

using System;
using System.Windows.Forms;

using System.Collections.Generic;
using MyPluginApp.Plugins;  //Our plugin namespace
using System.ComponentModel.Composition.Hosting; //MEF namespace

namespace MyPluginApp
{
    public partial class Form1 : Form
    {
        private delegate void MyButtonClickedEventHandler(decimal num1, decimal num2);
        private event MyButtonClickedEventHandler MyButtuttonClicked;

        private readonly Dictionary<string, IPlugin> _plugins = new Dictionary<string, IPlugin>();

        public Form1()
        {
            InitializeComponent();

            this.LoadPlugins();
        }

        //the Leave event handler for both text boxes
        private void textNum_Leave(object sender, EventArgs e)
        {
            decimal d;
            var textNum = (TextBox) sender;

            if (!decimal.TryParse(textNum.Text, out d))
            {
                textNum.Text = "5";
            }
        }

        //The KeyDown event handler for both text boxes
        private void txtNum_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter || e.KeyData == Keys.Return)
            {
                btnMyButton.Focus(); //fires the Leave event for the active text box
                btnMyButton.PerformClick();
            }
        }

        //OnClick event handler for the button
        private void btnMyButton_Click(object sender, EventArgs e)
        {
            decimal num1;
            decimal num2;

            MessageBox.Show(string.Format("You entered {0} and {1}", txtNum1.Text, txtNum2.Text));

            if (decimal.TryParse(txtNum1.Text, out num1) && decimal.TryParse(txtNum2.Text, out num2))
            {
                //If our new event has any subscribers, invoke the event.
                if(this.MyButtuttonClicked != null)
                    this.MyButtuttonClicked.Invoke(num1, num2);
            }
        }

        private void LoadPlugins()
        {
            var pluginDir = System.IO.Path.GetDirectoryName(Application.ExecutablePath) + @"\Plugins\";

            //Create plugin directory if it doesn't exist
            System.IO.Directory.CreateDirectory(pluginDir);

            //DirectoryCatalog gets a list of all .dll files in our plugin directory
            var catalog = new DirectoryCatalog(pluginDir, "*.dll");

            //CompositionContainer parses all of the dlls in the catalog and identifies their exports
            var container = new CompositionContainer(catalog);

            //Iterate through each export that matches our IPlugin interface and add it to our plugin collection
            foreach (var plugin in container.GetExports<IPlugin, IPluginMetaData>())
            {
                this._plugins.Add(plugin.Metadata.Name, plugin.Value);
                this.clbPlugins.Items.Add(plugin.Metadata.Name, false);
            }

            //Best practice per MS is to dispose the CompositionContainer as soon as we are finished with it.
            container.Dispose();
            catalog.Dispose();
        }

        //ItemCheck event for clbPlugins (checked list box)
        private void clbPlugins_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            var item = clbPlugins.Items[e.Index].ToString();


            if (e.NewValue == CheckState.Checked)
            {
                //If the user checked the box for the plugin, subscribe the plugin's handler to the event
                this.MyButtuttonClicked += this._plugins[item].HandleNumbers;
            }
            else
            {
                //If the user unchecked the box for the plugin, unsubscribe the plugin's handler to the event
                this.MyButtuttonClicked -= this._plugins[item].HandleNumbers;
            }
        }
    }
}

This concludes post #2.  In Post #3 I will create a few example plugins in their own solution without access to any of the code contained in Form1.cs, just as I would expect a 3rd party developer to be able to do.

Using the Managed Extensibility Framework (MEF) to implement a third-party plugin solution. Part 1–Introduction

I recently started working on a project that would benefit greatly from the ability to support plugins that were developed by someone other than myself or anyone else who necessarily had access to our source code. Having never really done anything like this myself I set off and tried to research it. I found a lot of information about loading compiled .dll files, using reflection and Activator.CreateInstance and so on. Then in the comments of those posts I’d see someone say “Why don’t you use MEF?” and that’s it; no other information.

In a quick nutshell, here’s what Wikipedia has to say about MEF as of August 2014:

Managed Extensibility Framework (MEF) is a component of .NET Framework 4.0 aiming to create lightweight, extensible applications. It allows application developers to discover and use extensions with no configuration required. It also lets extension developers easily encapsulate code and avoid fragile hard dependencies. MEF also allows extensions to be reused across applications. MEF was introduced as a part of .NET 4.0 and Silverlight 4.

Additionally, here is the MSDN page for your reference.

In my research, I found a lot of great examples of using MEF to quickly load external dll files and instantiate the types that they had defined within them. The problem was, all of the examples I found had all of the projects (application and plugins) contained within a single solution. I wanted to know how to enable someone else to take my plugin specification and implement it, without me having to distribute my entire solution to them. I never did find a true tutorial on that aspect of MEF, but I did figure out a way that works and that is the way I am going to present in this series. If anyone happens upon this series and has a better method, I’d be happy to learn from you!

In this series of posts I am going to go through the process of creating a .NET 4.0 Windows Forms application that implements a simple plugin system.  The plugins system will load any available plugins on startup and allow the user to enable/disable them.  I will then create an entirely separate solution that creates some example plugins that can be loaded an used by my original program.  This should simulate the workflow that I would expect a third-party developer to go through to develop plugins for my application.

The series will consist of 3 posts.  The first post, which you are now reading, will serve as the introduction and cover the initial application (without plugin support).  In the second post, I will add plugin handling capabilities to the application, and in the third post I will create a couple sample plugins within their own solution.

I’m going to use Visual Studio 2010 for this project.  I will include zipped archives for both solutions at the end of the series.

To start out, I have created a simple application that allows the user to enter two numbers, then reports back to them what they entered.

Application

The Application

Result of pressing the button

Result of Button Press

And the code for my application

using System;
using System.Windows.Forms;

namespace MyPluginApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //the Leave event handler for both text boxes
        private void textNum_Leave(object sender, EventArgs e)
        {
            decimal d;
            var textNum = (TextBox) sender;

            if (!decimal.TryParse(textNum.Text, out d))
            {
                textNum.Text = "5";
            }
        }

        //The KeyDown event handler for both text boxes
        private void txtNum_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter || e.KeyData == Keys.Return)
            {
                btnMyButton.Focus(); //fires the Leave event for the active text box
                btnMyButton.PerformClick();
            }
        }

        //OnClick event handler for the button
        private void btnMyButton_Click(object sender, EventArgs e)
        {
            MessageBox.Show(string.Format("You entered {0} and {1}", txtNum1.Text, txtNum2.Text));
        }
    }
}

This concludes the first post of the series.  In the next post I will add the capability for my application to load and utilize plugins.