The Managed Extensibility Framework (MEF) is a new library being developed by Microsoft to offer an easy way to add extensibility to applications. MEF will be a new feature of .NET 4 but will also work on 3.5. MEF is currently available in CTP on Codeplex. To show some of the core functionality MEF offers I've created a sample application. The sample application demonstrates how MEF can be used to create a plug-in architecture for custom text formatting. The sample application contains a simple form that has two text areas, one for the input of the source article text and the other to display the text after formatting. In this example we will use MEF to bind plug-in parts to add custom formatting functionality. The application will show how easy it is to create extension points in your application that can be used later with no recompilation required.
The three main steps to add MEF to your application are:
Exports: Create the components you would like to export. These are the parts that will plug-in to your application.
Imports: Define where you would like to import the exported parts into your application.
Configuration: Configure where the imports and exports will be found and add them to the Composition Container.
Exports
I've added two example formatters as exports, one for removing offensive words and one for adding advertisements. The user can select the required formatters form the list and click "Format Article" to run the specified formatters over the source text. The formatted text then appears in the processed text area. Each formatter that appears in the list box is dynamically loaded by MEF at runtime.
To create a new formatter for export take the following simple steps:
- Create a new class library project in Visual Studio and add a reference to the contracts assembly that contains the IArticleFormatter interface and add a reference to the MEF assembly System.ComponentModel.Composition.
- Create a new class that implements the IArticleFormatter interface. Add functionality to format the received text in its Format method and implement the Name property to give the formatter a display name.
- Now for the MEF part. Add the Export attribute to the class definition to tell the composition container that this should be exported.
- When you've created the custom formatter, just drop it in to the directory that MEF is configured to reference.
Here is the code from the sample offensive word formatter. The format method replaces all instances of offensive words with a message stating the word has been removed.
[Export(typeof(IArticleFormatter))]
public class OffensiveWordFormatter : IArticleFormatter
{
private List<string> offensiveWords;
public OffensiveWordFormatter()
{
offensiveWords = new List<string>();
offensiveWords.Add("mankini");
offensiveWords.Add("arrrrgh");
}
public string Name
{
get { return "Offensive Word Formatter"; }
}
public string Format(string article)
{
StringBuilder builder = new StringBuilder(article);
foreach (string word in offensiveWords)
{
builder.Replace(word, "[offensive word removed]");
}
return builder.ToString();
}
}
The main point to notice in the above code is that we specify the name of the interface as the type we wish to export. If we just added the Export tag with no type it would be exported as an OffensiveWordFormatter and not the base interface we will require for our imports.
Imports
To allow for Exported objects to be bound they require a matching Import. Below is the code that specifies we wish to import any IArticleFormatters. Although we could have used a standard Import attribute on a public property we instead use the ImportingConsrtuctor attribute to allow for the dependencies to be injected into the constructor. You should also notice that we use an IEnumerable of IArticleFormatters which means that the composition container will inject all instances of IArticleFormatter as an IEnumerable. If we had only one article formatter the import could just specify the interface to be imported and not request an IEnumerable, however the composition container will throw an exception if it has more than one object that can satisfy a single dependency.
[Export("MainWindow")]
public partial class Window1 : Window
{
[ImportingConstructor]
public Window1([Import(typeof(IArticleFormatter))] IEnumerable<IArticleFormatter> articleFormatters)
{
InitializeComponent();
lstArticleFormatters.ItemsSource = articleFormatters;
lstArticleFormatters.DisplayMemberPath = "Name";
}
private void btnFormatArticle_Click(object sender, RoutedEventArgs e)
{
txtDestination.Text = txtSource.Text;
foreach (var formatter in lstArticleFormatters.SelectedItems)
{
txtDestination.Text = ((IArticleFormatter)formatter).Format(txtDestination.Text);
}
}
}
Configuration
Now that we've specified our imports and exports, we just need to configure the composition container to include them for composition. This has been implemented in the Compose method on the application. To add our parts we need to create a catalog to reference them. We've created an AggregatingComposablePartCatalog which acts as a catalog of catalogs, this allows us to use two different catalogs. The first catalog is an AttributedAssemblyCatalog which we can use to add all imports and exports from a specified assembly. The second is a DirectoryPartCatlog which points to a specific directory that will be used for all future IArticleFormatter parts to be added to. You'll also notice that after the creation of the catalogs we explicitly add the application object as it must have its dependency on the main window satisfied. When the catalogs are created we add them to the composition container and call its Compose method.
private bool Compose()
{
var catalog = new AggregatingComposablePartCatalog();
catalog.Catalogs.Add(new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\PluginArticleFormatters\", true));
container = new CompositionContainer(catalog.CreateResolver());
container.AddPart(this);
try
{
container.Compose();
}
catch (CompositionException compositionException)
{
MessageBox.Show(compositionException.ToString());
return false;
}
return true;
}
This short example has shown the simple steps of adding MEF to an application. You can download the sample solution from here. If you want to investigate MEF further I'd highly recommend Glenn Blocks' excellent PDC session that you can download here.
del.icio.us Tags: MEF