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 Three, where I complete the application created in Part One and modified to use MEF in Part Two. This part will show MEF composing the application from multiple assemblies into one application at run time.
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.
Part Two introduced MEF into the application. We covered the basic elements of MEF: Exports, Imports, Catalogs, and Containers. We decorated our implementations with Export attributes, and built an Assembly catalog to find find them all. We modified our targets with the Import attributes, and finally used the CompositionContainer to compose our final instance.
Breaking Up Is Not So Hard To Do
To demonstrate how MEF composes across assemblies, we need some assemblies to work with. So lets partition our current application into a three pieces. The first will be the application itself. So, lets cover the three parts, one at a time.
RealMEF Project
This project should be a Windows WPF Application. It should contain two files, App.xaml, and MainWindow.xaml (and their corresponding code-behind files).
App.xaml
App.xaml.cs
MainWindow.xaml
MainWindow.xaml.cs
Code Changes
The changes are minor, and limited to line 13. I changed the AssemblyCatalog to be a DirectoryCatalog. The DirectoryCatalog is used to tell MEF to load all assemblies in the current path. There is a second constructor override that takes the path and a search pattern, and loads all assemblies that match the search pattern in the specified path. However, for this use case, I am telling MEF to search for all assemblies (*.dll) in the current directory. That’s the extent of the changes for the main application.
TransformationEngine Project
This class library project contains the IGenerator and ITransformer interfaces, and the TransformationEngine class. Here is the code again.
IGenerator
ITransformer
TransformationEngine
This project defines the two interfaces that will be used by other types, and eventually imported into the TransformationEngine. Notice that I use the InheritedExport attribute on the interfaces. This attribute allows for derived types to be automatically tagged as an exported type. So your classes do not even have to be aware of MEF. Also notice that the Transformers property is decorated by the ImportManyAttribute. This allows for a collection of those types to be imported rather than just one. I covered both in Part Two, but wanted to highlight them again since they are so important.
Those three types are all that makes up this project. This type of project is sometimes referred to a Contracts Project, since it just exports (and imports) the contracts (decorated interfaces). The purpose of it is to define the types common to both the assembly that uses the types (RealMEF project) and assembly providing the implementations (TransformationImplementation Project – see below).
TransformationImplementation Project
This project is another class library project that contains the classes moved from the main application. Here is the code again.
LoremIpsumGenerator
LowerCaseTransformer
UpperCaseTransformer
These classes are the same as before, just in a new assembly all their own.
Building the Projects
Dependencies
The RealMEF project should have a dependency on the TransformationEngine project, but not the TransformationImplementation project. The TransformationImplementation project should also have a dependency on the TransformationEngine project, but not RealMEF. This will allow us to see what MEF can truly do for us.
Build the projects, and copy the TransformationImplementation assembly into the bin\debug or bin\release directory of the RealMEF project. Then run RealMEF. Everything should work – even though there is no defined linkage between the RealMEF project and your implementations.
Taking it to the Next Step
Modify the TransformationEngine class by changing the Import(typeof(IGenerator)) to Import(“StarWars”) and recompile. If you run your program now, it will fail. So, lets fix it.
Create a new class library project, I called it TransformationEx. Lets add two new implementations, just for the fun of it.
ReverseTransformer
StarWarsTextGenerator
Line 1 contains the most interesting piece. I added the Export attribute, and specified a name for this export as well as the type. This will override the InheritedExport on the IGenerator type for this implementation only. You may notice that the text is the same I added to the Import attribute for the TransformationEngine above.
This project is similar to the TransformationImplementation project, so it depends only on the TransformationEngine project. Build it, and copy the assembly to the to RealMEF bin\debug or bin\release directory. Then run it. It all just works. That is the power of MEF. You just enhanced a pre-built project with new functionality very easily.
Summary
Take a look at how the ReverseTransformer implementation is now an available transformer, and it required no import or export changes. The StarWarsTextGenerator was different because I required only one, I did not implement any logic to pick the right one, and I wanted to demonstrate named import / exports. The way I dealt with the ReverseTransformer is more likely how you will deal with adding new functionality for your applications.
Over these three posts (Part One, Part Two, and this one), you have seen how to take a simple application, decompose it into MEF-able components, and further decompose into late-binding assemblies. While this does not cover the entirety of MEF, it gives you the knowledge needed to use the most commonly used functionality. So go decompose those monolithic applications and use MEF.
If you have done something cool with MEF, post it as a comment and share.