Application Extensibility: MEF vs. IoC

Dino Esposito

image: Dino Esposito There’s an interesting new component in the Microsoft .NET Framework 4 specifically designed to provide an effective answer to an evergreen question: How would you write extensible applications that can discover at run time all the parts they’re made of?

As Glenn Block explained in his February 2010 article, “Building Composable Apps in .NET 4 with the Managed Extensibility Framework” (msdn.microsoft.com/magazine/ee291628), the Managed Extensibility Framework (MEF) can be used to streamline building composable and plug-in-based applications. As one who started approaching the problem back in 1994 (yes, it was one of my first real challenges as a developer), I definitely welcome any proposed solutions in this problem space.

The MEF doesn’t require you to buy, download and reference any additional libraries, and it offers a simple programming interface because it’s focused on solving the problem of general, third-party extensibility of existing applications. Glenn’s article is an excellent introduction to the MEF and should be considered required reading if you’re thinking about plug-in-based applications.

In this article, I’ll walk through the steps required to build an extensible application using the MEF as the underlying glue to keep the main body and external parts of the application together.

From IoC to the MEF and Back

Before I get to the sample application, however, I’d like to share some thoughts about the MEF and another popular family of frameworks: Inversion of Control (IoC).

In a nutshell, it’s correct to say that the functionality of the MEF and of a typical IoC framework overlap, but don’t coincide. With most IoC frameworks you can perform tasks that the MEF just doesn’t support. You could probably employ a functionally rich IoC container and, with some effort on your own, emulate some MEF-specific capabilities. In light of this, the question that I’m frequently asked when I mention the MEF in classes and everyday work is: What’s the difference between the MEF and an IoC tool? And when do I really need the MEF?

My thought is that, at its core, the MEF is an IoC framework built right into the .NET Framework. It’s not as powerful as many of the popular IoC frameworks today, but it can perform the basic tasks of a typical IoC container quite well.

Today, IoC frameworks have three typical capabilities. First, they can act as the factory of a graph of objects and walk through the chain of object relationships and dependencies to create an instance of any required and registered type. Second, an IoC framework can manage the lifetime of created instances and offer caching and pooling capabilities. Third, most IoC frameworks support interception and offer to create dynamic proxies around instances of specific types so that developers can pre- and post-process the execution of methods. I covered interception in Unity 2.0 in January (msdn.microsoft.com/magazine/gg535676).

The MEF, in a way, can serve as the factory of a graph of objects, meaning that it can recognize and handle members on a class that need be resolved at run time. The MEF also provides minimal support for caching instances, meaning that some caching capabilities exist, but they’re not as functionally rich as in some other IoC frameworks. Finally, in the version shipped with the .NET Framework 4, the MEF lacks interception capabilities entirely.

Having said that, when should you use the MEF? If you’ve never used an IoC framework and just need to clean up the design of your system by adding a bit of dependency injection, then the MEF can be an easy start. As long as you can quickly achieve your goals with it, the MEF is preferable to an IoC framework.

On the other hand, if you’ve spent years working with one or more IoC frameworks and can squeeze any bit of functionality out of them, then there’s probably nothing that the MEF can give you except, perhaps, its ability to scan various types of catalogs to find matching types. It should be noted, however, that some IoC frameworks such as StructureMap (structuremap.net/structuremap/ScanningAssemblies.htm) already offer to scan directories and assemblies to look for specific types or implementations of given interfaces. With the MEF, this is probably easier and more direct to do than with StructureMap (and a few others).

In summary, the first question to answer is whether you’re looking for general extensibility. If the answer is yes, then the MEF must be considered—perhaps in addition to an IoC tool if you also need to handle dependencies, singletons and interception. If the answer is no, then the best approach is using an IoC framework unless you have basic needs that the MEF can address as well. All things being equal, the MEF is preferable to an IoC framework because it’s built right into the .NET Framework and you don’t need to take any additional dependencies.

The MEF and Extensible Applications

While the MEF helps in the building of an extensible application, the most delicate part of the job is designing the application for extensibility. This is design and has little to do with the MEF, IoC or other technologies. In particular, you must figure out which parts of your application you intend to make available to plug-ins.

A plug-in is often a visual element and needs to interact with the UI of the main application, add or extend menus, create panes, display dialog boxes, or even add or resize the main windows. Depending on how you envision the plug-ins of your specific application, the amount of information to share with plug-ins may consist of just business data (essentially a segment of the application’s current state) or reference to visual elements such as containers, menus, toolbars and even specific controls. You group this information in a data structure and pass it down to the plug-in at initialization time. Based on that information, the plug-in should be able to adjust its own UI and implement its own additional custom logic.

Next comes the interface for the plug-ins. The interface depends on the injection points you’ve identified in your main application. By “injection point” I mean the places in the application’s code from which you’d invoke plug-ins to give them a chance to kick in and operate.

As an example of an injection point, consider Windows Explorer. As you may know, Windows Explorer allows you to extend its UI via shell extensions. These plug-ins are invoked at very specific moments—for example, when the user right-clicks to display the properties of a selected file. As the application’s architect, it’s your responsibility to identify these injection points and what data you intend to pass to registered plug-ins at that point.

Once every design aspect has been cleared up, you can look around for frameworks that can simplify the task of building a plug-in-based application.

A Sample Plug-In-Based Application

Even a simple application such as “Find the number” can be made richer and functionally appealing using plug-ins. Figure 1 shows the basic UI of the application. You might want to create a separate project to define the SDK of your application. It will be a class library where you define all classes and interfaces required to implement plug-ins. Figure 2 shows an example.

image: The Simple Sample Application

Figure 1 The Simple Sample Application

Figure 2 Definitions for the Application SDK

  1. public interface IFindTheNumberPlugin {
  2.   void ShowUserInterface(GuessTheNumberSite site);
  3.   void NumberEntered(Int32 number);
  4.   void GameStarted();
  5.   void GameStopped();
  6. }
  7. public interface IFindTheNumberApi {
  8.   Int32 MostRecentNumber { get; }
  9.   Int32 NumberOfAttempts { get; }
  10.   Boolean IsUserPlaying { get; }
  11.   Int32 CurrentLowerBound { get; }
  12.   Int32 CurrentUpperBound { get; }
  13.   Int32 LowerBound { get; }
  14.   Int32 UpperBound { get; }
  15.   void SetNumber(Int32 number);
  16. }
  17. public class FindTheNumberFormBase : Form, IFindTheNumberApi {
  18.   …
  19. }

All plug-ins are required to implement the IFindTheNumberPlugin interface. The main application form will inherit from the specified form class, which defines a list of public helper members useful to pass information down to plug-ins.

As you may guess from IFindTheNumberPlugin, registered plug-ins are invoked when the application displays its UI, when the user makes a new attempt to guess the number, and when the game is started and stopped. GameStarted and GameStopped are just notification methods and don’t need any input. NumberEntered is a notification that brings in the number the user just typed and submitted for a new try. Finally, ShowUserInterface is invoked when the plug-in must show up in the window. In this case, a site object is passed, as defined in Figure 3.

Figure 3 The Site Object for Plug-Ins

  1. public class FindTheNumberSite {
  2.   private readonly FindTheNumberFormBase _mainForm;
  3.   public FindTheNumberSite(FindTheNumberFormBase form) {
  4.     _mainForm = form;
  5.   }
  6.   public T FindElement<T>(String name) where T:class { … }
  7.   public void AddElement(Control element) { … }
  8.   public Int32 Height {
  9.     get { return _mainForm.Height; }
  10.     set { _mainForm.Height = value; }
  11.   }
  12.   public Int32 Width { … }
  13.   public Int32 NumberOfAttempts { … }
  14.   public Boolean IsUserPlaying { … }
  15.   public Int32 LowerBound { … }
  16.   public Int32 UpperBound { … }
  17.   public void SetNumber(Int32 number) { … }
  18. }

The site object represents the point of contact between the plug-in and the host application. The plug-in must gain some visibility of the host state and must even be able to modify the host UI, but it never gains knowledge of the host’s internal details. That’s why you might want to create an intermediate site object (part of your SDK assembly) that plug-in projects must reference.

I omitted for brevity the implementation of most methods in Figure 3, but the constructor of the site object receives a reference to the application’s main window, and using helper methods in Figure 2 (exposed by the main window object) it can read and write the application’s state and visual elements. For example, the Height member shows how the plug-in may read and write the height of the host window.

In particular, the FindElement method allows the plug-in (in the sample application) to retrieve a particular visual element in the form. It’s assumed that you unveil as part of your SDK some technical details of how to access certain containers such as toolbars, menus and the like. In such a simple application, it’s assumed that you document the ID of the physical controls. Here’s the implementation of FindElement:

  1. public T FindElement<T>(String name) where T:class {
  2.   var controls = _mainForm.Controls.Find(name, true);
  3.   if (controls.Length == 0)
  4.     return null;
  5.   var elementRef = controls[0] as T;
  6.   return elementRef ?? null;
  7. }
With the design of the application’s extensibility model completed, we’re now ready to introduce the MEF.

Defining Imports for Plug-ins

The main application will certainly expose a property that lists all currently registered plug-ins. Here’s an example:

  1. public partial class FindTheNumberForm :
  2.   FindTheNumberFormBase {
  3.   public FindTheNumberForm() {
  4.     InitializeMef();
  5.     …
  6.  }
  7.  [ImportMany(typeof(IFindTheNumberPlugin)]
  8.  public List<IFindTheNumberPlugin> Plugins {
  9.     get; set;
  10.   }
  11.   …
  12. }

Initializing the MEF means preparing the composition container specifying the catalogs you intend to use and optional export providers. A common solution for plug-in-based applications is loading plug-ins from a fixed folder. Figure 4 shows the startup code of the MEF in my example.

Figure 4 Initializing the MEF

private void InitializeMef() { try { _pluginCatalog = new DirectoryCatalog(@"\My App\Plugins"); var filteredCatalog = new FilteredCatalog(_pluginCatalog, cpd => cpd.Metadata.ContainsKey("Level") && !cpd.Metadata["Level"].Equals("Basic")); // Create the CompositionContainer with the parts in the catalog _container = new CompositionContainer(filteredCatalog); _container.ComposeParts(this); } catch (CompositionException compositionException) { ... } catch (DirectoryNotFoundException directoryException) { ... } }

You use a DirectoryCatalog to group available plug-ins and use the FilteredCatalog class (which is not in the MEF, but an example is shown in the MEF documentation at bit.ly/gf9xDK) to filter out some of the selected plug-ins. In particular, you can request that all loadable plug-ins have a metadata attribute that indicates the level. Missing the attribute, the plug-in is ignored.

The call to ComposeParts has the effect of populating the Plugins property of the application. The next step is just invoking plug-ins from the various injection points. The first time you invoke plug-ins is right after the application loads to give them a chance to modify the UI:

  1. void FindTheNumberForm_Load(Object sender, EventArgs e) {
  2.   // Set up UI
  3.   UserIsPlaying(false);
  4.   // Stage to invoke plugins
  5.   NotifyPluginsShowInterface();
  6. }
  7. void NotifyPluginsShowInterface() {
  8.   var site = new FindTheNumberSite(this);
  9.   if (Plugins == null)
  10.     return;
  11.   foreach (var p in Plugins) {
  12.     p.ShowUserInterface(site);
  13.   }
  14. }

Similar calls will appear in the event handlers that signal when the user just started a new game, quit the current game or just made a new attempt to guess the mysterious number.

Writing a Sample Plug-In

A plug-in is just a class that implements your app’s extensibility interface. An interesting plug-in for the application in Figure 1 is one that shows the number of attempts the user made so far. The number of attempts is being tracked by the business logic of the application, and it’s exposed to plug-ins via the site object. All a plug-in must do is prepare its own UI, bind it to the number of attempts and attach it to the main window.

Plug-ins of the sample application will create new controls in the UI of the main window. Figure 5 shows a sample plug-in.

Figure 5 The Counter Plug-In

[Export(typeof(IFindTheNumberPlugin))] [PartMetadata("Level", "Advanced")] public class AttemptCounterPlugin : IFindTheNumberPlugin { private FindTheNumberSite _site; private Label _attemptCounterLabel; public void ShowUserInterface(FindTheNumberSite site) { _site = site; var numberToGuessLabelRef = _host.FindElement<Label>("NumberToGuess"); if (numberToGuessLabelRef == null) return; // Position of the counter label in the form _attemptCounterLabel = new Label { Name = "plugins_AttemptCounter", Left = numberToGuessLabelRef.Left, Top = numberToGuessLabelRef.Top + 50, Font = numberToGuessLabelRef.Font, Size = new Size(150, 30), BackColor = Color.Yellow, Text = String.Format("{0} attempt(s)", _host.NumberOfAttempts) }; _site.AddElement(_attemptCounterLabel); } public void NumberEntered(Int32 number = -1) { var attempts = _host.NumberOfAttempts; _attemptCounterLabel.Text = String.Format("{0} attempt(s)", attempts); return; } public void GameStarted() { NumberEntered(); } public void GameStopped() { } }

The plug-in creates a new Label control and places it just below an existing UI element. Next, whenever the plug-in receives the notification that a new number has been entered, the counter is updated to show the current number of attempts according to the state of the business logic. Figure 6 shows the plug-in in action.

image: The Sample App and a Few Plug-Ins

Figure 6 The Sample App and a Few Plug-Ins

Plugging In

At the end of the day, the most delicate task of designing extensible apps is the design of the host and the interface of plug-ins. This is a pure design task and has to do with the feature list and user’s requirements.

When it comes to implementation, though, you have quite a few practical tasks to accomplish regardless of the plug-in interface, such as selecting, loading and verifying plug-ins. In this regard, the MEF gives you significant help in simplifying creation of the catalog of plug-ins to load, and automatically loading them up in much the same way an IoC framework would do.

Note that the MEF is under continual development, and you can find the latest bits, documentation and example code at mef.codeplex.com.


Dino Esposito is the author of “Programming Microsoft ASP.NET MVC” (Microsoft Press, 2010) and coauthored “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.

Thanks to the following technical expert for reviewing this article: Glenn Block

This entry was posted in Best Practices. Bookmark the permalink.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s