Introduction to the Managed Extensibility Framework

The Managed Extensibility Framework (MEF) allows developers to create applications that have a well-defined and standardized plug-in architecture. The functionality of an MEF application can be extended without recompiling the application. MEF was released as part of the .NET Framework 4.0 but MEF doesn’t appear to be widely used in the developer community.

I din’t like the MEF examples I found on the MSDN Web site but I found a very good example by David Buksbaum at http://buksbaum.us/2011/08/20/gentle-introduction-to-mefpart-one/ and decided to experiment with and modify his example. My demo is a WinForm application that fetches some dummy data, and then changes the data using one of two MEF plug-ins. The essence of the structure is two VS Solutions. The first solution has three projects. The first project is a C# WinForm app named DataDoctor which contains the UI. The second project in the first Solution is a C# Class Library named CoreFunctionality that has basic non-pluggable functionality. The third Solution is a C# Class Library named MEFCoordinator that houses MEF plumbing.

(Note: After I created this demo and gained a lot of insights into MEF, I realized some of my design choices were pretty poor. This demo works but could have been organized much better. I did a version 2 that’s much, much better and I’ll publish an update at some point.)

First I created the DataDoctor application and placed a ComboBox control, two multi-line TextBox controls, and three Button controls. I left the app alone for now.

Next I added the MEFCoordinator project. Here’s the code:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;

namespace MEFcoordinator
{
  // export the data changer plug-ins interface to the app
  [InheritedExport(typeof(IDataChanger))]
  public interface IDataChanger
  {
    string[][] Change(string[][] data);
  }

  public class MEFcoordinator
  {
    // import one or more data changers into the controller
    // these are essentially plug-ins
    [ImportMany(typeof(IDataChanger))]
    public IEnumerable Changers { get; set; }
  }

} // ns

The MEF Coordinator has a reference to System.ComponentModel.Composition where MEF code lives. The Coordinator defines what pluggable functionality is exposed through MEF. In this case there can be a collection of DataChanger objects.

Next I added the CoreFunctionality class library Project to the VS Solution. Here’s the code:

using System;
using MEFcoordinator;
namespace CoreFunctionality
{
  // 'normal' functionality with local interface definition
  public interface IDataFetcher
  {
    string[][] Fetch(string filePath);
  }

  public class DataFetcher : IDataFetcher
  {
    public string[][] Fetch(string dummyFilePath)
    {
      string[][] result = new string[4][];
      result[0] = new string[] { "1.0", "1.1", "1.2" };
      result[1] = new string[] { "2.0", "2.1", "2.2" };
      result[2] = new string[] { "3.0", "3.1", "3.2" };
      result[3] = new string[] { "4.0", "4.1", "4.2" };
      return result;
    }
  }

  // functionality that is pluggable
  // the interface definition is in the MEF controller
  public class AddTen : IDataChanger
  {
    public string[][] Change(string[][] data)
    {
      string[][] result = new string[data.Length][];
      for (int i = 0; i < data.Length; ++i)
      {
        result[i] = new string[data[i].Length];
        Array.Copy(data[i], result[i], data[i].Length);
      }
      for (int i = 0; i < data.Length; ++i)
        for (int j = 0; j < data[i].Length; ++j)
          result[i][j] = "1" + data[i][j];
      return result;
    }
  }

} // ns

Notice there’s nothing really MEF-specific here except the reference to the MEFCoordinator so that class AddTen knows about IDataChanger.

Now I went back to the DataDoctor project and wired up Form1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
using MEFcoordinator;

namespace DataDoctor
{
  public partial class Form1 : Form
  {
    private MEFcoordinator.MEFcoordinator mefCoordnator;
    private static string[][] data;

    public Form1()
    {
      using (var catalog = new DirectoryCatalog("."))
      {
        var container = new CompositionContainer(catalog);
        mefCoordnator = new MEFcoordinator.MEFcoordinator();
        container.SatisfyImportsOnce(mefCoordnator);
      }

      InitializeComponent();

      comboBox1.DataSource = mefCoordnator.Changers;
      comboBox1.SelectedIndex = 0;

    }

    private void button1_Click(object sender,
      EventArgs e) // fetch data
    {
      CoreFunctionality.DataFetcher df =
        new CoreFunctionality.DataFetcher();
      data = df.Fetch("dummyFilePath");

      string txt = "";
      for (int i = 0; i < data.Length; ++i)
      {
        for (int j = 0; j < data[i].Length; ++j)
        {
          txt += data[i][j] + "    ";
        }
        txt += Environment.NewLine;
      }
      textBox1.Text = txt;
    }

    private void button2_Click(object sender,
      EventArgs e) // change
    {
      var c = comboBox1.SelectedItem as IDataChanger;
      string[][] changedData = null;
      if (c != null)
        changedData = c.Change(data);
      string txt = "";
      for (int i = 0; i < changedData.Length; ++i)
      {
        for (int j = 0; j < changedData[i].Length; ++j)
        {
          txt += changedData[i][j] + "    ";
        }
        txt += Environment.NewLine;
      }
      textBox2.Text = txt;
    }

    private void button3_Click(object sender,
      EventArgs e)
    {
      textBox1.Text = string.Empty;
      textBox2.Text = string.Empty;
    }


  } // Form
} // ns

Most of the MEF action is in the Form constructor. A catalog is created where all assemblies in the current directory are scanned to see if they are valid plug-ins and the MEF Coordinator is instantiated. Although the code isn’t long it’s a bit subtle in several places.

After building the project, (and dealing with any Project Dependency issues), the app runs and can do the “Add Ten” change.

The power of MEF is the ability to extend functionality without recompiling the base application. To demo this, I created a new VS C# Class Library Project named AddOneThousandFunctionalty. Here’s its code:

using System;
using MEFcoordinator; // add ref to coordinator
// build then copy dll to main app bin

namespace AddOneThousandFunctionality
{
  public class AddOneThousand : IDataChanger
    {
    public string[][] Change(string[][] data)
    {
      string[][] result = new string[data.Length][];
      for (int i = 0; i < data.Length; ++i)
      {
        result[i] = new string[data[i].Length];
        Array.Copy(data[i], result[i], data[i].Length);
      }
      for (int i = 0; i < data.Length; ++i)
        for (int j = 0; j < data[i].Length; ++j)
          result[i][j] = "1,00" + data[i][j];
      return result;
    }
    }
}

To add this functionality to the DataDoctor app, I just built the code, copied the resulting DLL into the bin directory of DataDoctor, and presto, DataDoctor had new functionality.

MEF can be much more complex than this simple demo, but it’s a pretty cool technology for building modular applications.

ManagedExtensibilityFramework

Advertisements
This entry was posted in Software Test Automation. Bookmark the permalink.