Code Focused
How To Refactor for Dependency Injection, Part 7: Using the Managed Extensibility Framework
The Microsoft MEF can be used for dependency injection, but it does it much differently than most other containers. See how to get the most from the unique features of MEF.
- By Ondrej Balas
- 11/21/2014
In this seventh and final article in my series on dependency injection (DI), I will be exploring the benefits of using the Microsoft Managed Extensibility Framework (MEF). It's unlike the other DI containers that I've covered so far, and some would even go as far as to say it's not even a DI container at all. While DI containers were created to improve the composition of whole object graphs, MEF was built with the goal of supporting the plugging in of functionality that may not be known at the time of development. In this way, MEF can be used to support plug-in architectures like the ones I describe in my previous article.
Like the other DI containers I've mentioned, MEF supports practices such as loose coupling of code and constructor injection, but it does it much differently. Rather than having a composition root where you'll find explicit bindings or convention definitions, MEF uses an attributes-based system to define those bindings on the actual objects themselves. One downside to this is that when using MEF, it's sprinkled everywhere in your code instead of being in one centralized location.
Starting with MEF
The easiest way to start with MEF is to use the Import and Export attributes it provides. To get these, you'll first need a reference to the MEF assembly. To add the reference, find your project within Solution Explorer and right-click on References. On the Assemblies tab, find System.ComponentModel.Composition and check the box, as shown in Figure 1.
Once MEF is added, you can begin decorating objects with Export or Import attributes. The Import attribute is used to specify dependencies, and can be used like this:
class ShoppingCart
{
[Import]
public ITaxCalculator TaxCalculator { get; set; }
public decimal CalculatePrice(params LineItem[] lineItems)
{
return TaxCalculator.AddTax(lineItems.Sum(li => li.Price * li.Quantity));
}
}
When MEF attempts to satisfy the dependencies for the ShoppingCart class, it will see the Import attribute decorating the TaxCalculator property, and it will look for a class exporting the ITaxCalculator interface. The export will look something like this:
[Export(typeof(ITaxCalculator))]
class FakeTenPercentTaxCalculator : ITaxCalculator
{
public decimal AddTax(decimal amount)
{
return amount * 1.10m;
}
}
Note that when using the Export attribute, the type being exported must be specified. This works a lot like the Bind statement in Ninject, just applied to an object rather than being applied to the container itself.
To inject the FakeTenPercentTaxCalculator into the ShoppingCart, you'll need to create an MEF container and then use it to fill the ShoppingCart with data. One way this can be done is like this:
AssemblyCatalog catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
CompositionContainer container = new CompositionContainer(catalog);
var cart = new ShoppingCart();
container.SatisfyImportsOnce(cart);
First, MEF will need an AssemblyCatalog, which you can populate with any assemblies that it should inspect for the Import and Export attributes. In this case I'm using the currently executing assembly, but you can give it any valid .NET assembly. Then, I create a CompositionContainer by passing it the AssemblyCatalog. Finally, I create a new instance of ShoppingCart and have the container inject the FakeTenPercentTaxCalculator by calling the SatisfyImportsOnce method and passing in the instance.
One important thing to note here is that SatisfyImportsOnce takes a ComposablePart by default and you might get the error, "Argument type ‘ShoppingCart' is not assignable to parameter type ‘System.ComponentModel.Composition.Primitives.ComposablePart.'" This occurs because the method that takes in any arbitrary object is actually an extension method defined within System.ComponentModel.Composition, and you must add the following using statement for it to become available:
using System.ComponentModel.Composition;
Injecting Fake Data
If you've been following this series you may have noticed that when building UIs, I like to inject them with fake data as part of the development process. This is something that's easily done in a framework like Ninject, where you can have one module for live data and one for fake data. This is more difficult in MEF as the exports don't exist in a single place, so the logic to switch them out must be spread apart.
One way I've done this is to create a custom Export attribute that checks a flag when deciding if it should export itself or not. This has a few parts to it, the first being an enum defining the modes in which the application can run:
public enum ExportModes
{
Live = 0,
Fake = 1
}
Then I use that to define the current application mode somewhere around where I'd normally want the application root to be. Often this is just a constant defined near the entry-point of my application, like this:
public const ExportModes ExportMode = ExportModes.Fake;
Next, I use my custom attribute to selectively export classes only when a specific mode is set like this:
[CustomExport(typeof(ITaxCalculator), ExportModes.Fake)]
class FakeTenPercentTaxCalculator : ITaxCalculator
{
...
}
And I could have another class now with the following export:
[CustomExport(typeof(ITaxCalculator), ExportModes.Live)]
class RealTaxCalculator : ITaxCalculator
{
...
}
By doing it this way, the behavior of the application can be changed just by changing the constant. I don't find this to be as flexible as using modules with a DI container like Ninject, but it is a minor inconvenience compared to some of the benefits that MEF may provide. The code to the CustomExport attribute can be found in Listing 1.
Listing 1: The CustomExport attribute can be used to conditionally redefine exports.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CustomExportAttribute : ExportAttribute
{
public CustomExportAttribute(Type type, ExportModes exportMode)
: base(GetType(type, exportMode))
{
}
private static Type GetType(Type type, ExportModes mode)
{
if (mode == Program.ExportMode)
{
return type;
}
return typeof (NoType);
}
private class NoType
{
}
}
Constructor Injection with MEF
Throughout this series I've always leaned toward constructor injection over anything else, which MEF also supports (just with a bit more difficulty). When you want to get a component from MEF, you must first give it an Export attribute and have it export itself. In my example, I put the Export onto the ShoppingCart class, like this:
[Export(typeof(ShoppingCart))]
class ShoppingCart
{
...
}
And then rather than instantiating it manually and asking MEF to satisfy its dependencies, I can just request the ShoppingCart directly, like this:
ShoppingCart cart = container.GetExportedValue<ShoppingCart>();
At this point, the ShoppingCart class is still being created first and then having its properties populated by MEF. To use proper constructor injection, you must also decorate the constructor with the ImportingConstructor attribute, like this:
[ImportingConstructor]
public ShoppingCart(ITaxCalculator taxCalculator)
{
_taxCalculator = taxCalculator;
}
Wrapping Up
MEF is a powerful framework for creating extensible applications, and should definitely be taken into consideration when architecting an application with such a need. My goal in showing MEF was to show that while traditional DI containers have a use, it's important to see problems solved in different ways so that you can make well-informed decisions about your applications' architecture. The beauty of MEF is that it can easily be used in conjunction with a more traditional DI framework.
About the Author
Ondrej Balas owns UseTech Design, a Michigan development company focused on .NET and Microsoft technologies. Ondrej is a Microsoft MVP in Visual Studio and Development Technologies and an active contributor to the Michigan software development community. He works across many industries -- finance, healthcare, manufacturing, and logistics -- and has expertise with large data sets, algorithm design, distributed architecture, and software development practices.