Gentle Introduction to MEF–Part Two

At the Tampa C# Meetup on August 3rd, I presented this Gentle Introduction to MEF using the same project modified over three steps. This is Part Two, where I show the application created in MEF.

Recap

In Part One I created an application the generates some text, and transforms it based on a selected transformation method. We introduced the following interfaces:

  • IGenerator – implemented by our text generators
  • ITransformer – implemented by our transformers

We also introduced a few implementation of those interfaces:

  • LoremIpsumGenerator – returns a Lorem Ipsum text string
  • LowerCaseTransformer – returns the supplied text converted to lower case
  • UpperCaseTransformer– returns the supplied text converted to upper case

Finally, we introduced a composite class, TranformationEngine, to contain a reference to the single IGenerator, and collection of ITransformers.

All of this was tied together in a simple WPF based UI shown here. While the user interface might now win any design awards, it does allow us to demonstrate the functionality clearly.

The Managed Extensibility Framework (MEF)

So how can MEF make our life easier? We shall see. First, a little primer on what makes up MEF. There are four concepts of MEF that you need to understand, Exports, Imports, Catalogs, and Containers. So lets cover each one.

Exports

Exports are types that have been decorated with a MEF attribute to let MEF know that the decorated type is exposed for composition.

Imports

Imports are methods, properties, and constructors that are decorated with a MEF attribute to let MEF know that the decorated method, property, or constructor can be populated during composition.

Catalogs

Catalogs are collections of exported types.

Containers

Containers maintain the list of all catalogs, and contain the functionality to perform a MEF composition.

Examples of Decorating Your Code

Simple enough? NO! Really? Ok. Here are some examples for our trusty program. We will start by looking at the changes to the TransformationEngine.

public class TranformationEngine
{
  [Import]
  public IGenerator Generator { get; set; }
  [ImportMany(typeof(ITransformer))]
  public IEnumerable<ITransformer> Transformers { get; set; }
}

Can you spot the difference between this MEF-ed version of the class and the non-MEF-ed version? Here is the old version for comparison.

public class TranformationEngine
{
  public IGenerator Generator { get; set; }
  public IEnumerable<ITransformer> Transformers { get; set; }
}

See the difference now? It is the two Import attributes. The first one (line 3) has the ImportAttribute. This tells MEF that the property Generator needs to be populated during composition. The second one, ImportMany (line 5), tells MEF that the Transformer property requires a collection of ITransformer types to be populated during composition.

Let’s look at the other side of the composition (I will explain this soon – I promise).

[Export(typeof(ITransformer))]
public class UpperCaseTransformer : ITransformer
{
  #region Implementation of ITransformer
  public string Name
  {
    get { return "Upper Case Transformer"; }
  }
  public string Transform(string text)
  {
    return text.ToUpper();
  }
  #endregion
}

Here is the UpperCaseTransformer. Do you see the difference? I won’t bother with the before and after versions, and instead, just highlight line 1. The ExportAttribute is telling MEF that this type will be exported as ITransformer. Can you guess the changes to the other two?

[Export(typeof(ITransformer))]
public class LowerCaseTransformer : ITransformer

[Export(typeof(IGenerator))]
public class LoremIpsumGenerator : IGenerator

I didn’t copy over the rest of the classes, because there were no other differences. All we needed to do was add the ExportAttribute.

Type Specificity in the Export and Import Attributes

You might have noticed that I specified types as a parameter to both attributes. If you do not specify the type, MEF assumes you mean the type being decorated. While this may work in simple programs, once you start separating the user of functionality from the implementation of functionality through an interface, you will find you need to specify the types. For example, LowerCaseTransformer. This class is exported as an ITransformer. The TransformationEngine is looking for a collection of ITransformer types. If I did not specify the type, MEF would have exported a LowerCaseTransformer, and not an ITransformer implemented by LowerCaseTransformer. See the difference?

Building the Catalogs

MEF has a number of built in catalogs, such as the AssemblyCatalog, TypeCatalog, and AggregateCatalog. These expose exported types from different sources. The TypeCataclog is the most basic in that it takes a collection of types, and exports those that are decorated. The AssemblyCatalog scans a provided assembly, and exports any types that are appropriately decorated. Finally, for this discussion, is the AggregateCatalog. This catalog takes a collection of other catalogs and combines the exported types from all into a single collection.

Here is how I build the catalog in our new sample.

var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());    

I create an AssemblyCatalog, and pass in the currently executing assembly. MEF scans all the types exported (LoremIpsumGenerator, LoweCaseTransformer, and UpperCaseTransformer in case you forgot) and stores some information about them in the catalog. That’s it.

If I had decided to use a TypeCatalog, it would have looked like this.

var catalog = new TypeCatalog(typeof(LoremIpsumGenerator), typeof(LowerCaseTransformer), typeof(UpperCaseTransformer))

I chose to use the AssemblyCatalog because I do not want to hard code my types – ever.

Creating the Container

Once we have our catalog, we need to popular our container. Here is that code.

var container = new CompositionContainer(catalog);

Again we see that this fairly easy. We create a CompositionContainer, and pass it the catalog of our types.

Composition

I have been promising to explain composition for the whole post now, and here it comes.

Composition is the process of matching Imports on a specified type to Exports supplied in a catalog.

Understand? Either way, let me try again. MEF will scan a type you provide it for any property, method, or constructor that you have decorated with an Input attribute. For each one of those found Input attributes, MEF will scan the catalog for the most specific type that matches the Input requested type. When it finds it, it will insert an instance of the exported type into the location requested by the import. I am ignoring lifetime issues such as singletons for now, as lifetime management is for another post in the future.

Let’s see how I trigger the composition.

container.SatisfyImportsOnce(_tranformationEngine);

That’s the magic. I am telling the MEF container to satisfy my imports on the instance _transformationEngine. When this method returns, I will have my properties filled.

The New Constructor for MainWindow

Outside of the new attributes decorating some of our types, the only change is to the beginning of our constructor in MainWindow.

Here is the new constructor.

public MainWindow()
{
  //  bootstrap MEF
  //  search our executing assembly
  using (var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()))
  {
    var container = new CompositionContainer(catalog);
    _tranformationEngine = new TranformationEngine();
    container.SatisfyImportsOnce(_tranformationEngine);
  }

  InitializeComponent();

  cbTransformationOptions.ItemsSource = _tranformationEngine.Transformers;
  cbTransformationOptions.DisplayMemberPath = "Name";
  cbTransformationOptions.SelectedIndex = 0;
}

The changes are only lines 5-10, ignoring comments. We added 3 lines of MEF specific code. Let’s compare to the original.

public MainWindow()
{
  _tranformationEngine = new TranformationEngine
  {
    Generator = new LoremIpsumGenerator(),
    Transformers = new ITransformer[] 
      {
        new LowerCaseTransformer(), 
        new UpperCaseTransformer()
      }
    };
  InitializeComponent();

  cbTransformationOptions.ItemsSource = _tranformationEngine.Transformers;
  cbTransformationOptions.DisplayMemberPath = "Name";
  cbTransformationOptions.SelectedIndex = 0;
}

In the original version line 3 creates our TransformationEngine, and lines 5-7 populate the properties explicitly. In the new version, line 8 creates our TransformationEngine, and line 9 populates the properties. To be fair, lines 5 & 7 are needed to be able to call SatisfyImportsOnce. The rest of the code program remains the exact same.

Why Bother with MEF?

So all-in, not much of a lines of code savings here. What would have happened if TransformationEngine had another 40 properties, and each of those had properties to be filled (yes MEF nests)? Big code savings then. However, lines of code (LOC) is not really a code metric to use for choosing an implementation paradigm.

You may be asking yourself, why bother? The real win with MEF, is the ability to give up control over how your objects dependencies are populated. This concepts being used here are called Inversion of Control (IoC) and Dependency Injection (DI). I am not going to go into too much depth in this post, but what this means, is that I could swap out my LoremIpsumGenerator for another generator in the future, and my code will not change. Further, if done properly, will not even need a recompile. This means I can change specialized functionality in my program after it is compiled. For example, I could use a catalog to find all of the plugins my program supports, display that in a list, and allow my users to pick which ones to have active or not. Sounds familiar? That is because Visual Studio 2010 does exactly that – and uses MEF to provide that functionality.

Summary

In Part One of this series we explored a program constructed with some simple loose connected types. In this part we explored how to make the construction of those loose connections completely disconnected from each other. In Part Three we will take this to its conclusion and show how to use this to find types outside your executable to satisfy your imports.

Related

comments powered by Disqus