MEF Tip : Importing multiple instances of a Contract

In the last post, I have used following Contract

Code Snippet
  1. public interface ILogger
  2.     {
  3.         void Debug(string message);
  4.         void Error(string message);
  5.         void Warn(string message);
  6.     }

 

To import a collection of ILogger the ImportManyAttribute  can be used like:

 

Code Snippet
  1. [ImportMany(typeof(ILogger))]
  2. public IEnumerable<ILogger> Loggers { get; set; }

Composition using MEF

 

With .NET 4.0 Microsoft ships a new composition container named Managed Extensibility Framework (MEF). MEF already have two feathers in the cap: Visual Studio 2010 editor extensions and Intellipad, the DSL editor of OSLO SQL Server Modeling.

MEF can be used to achieve the object-oriented programming  principle “Open-Closed Principle (OCP)”  software entities (classes, modules, functions, etc.) should be open for extension, but closed for modifications. What that means to me is that it should be possible to extend the application without modifying its case. For example, if an application was programmed to log to an text file and a later point of time if the requirement is to write to a console, then it should be possible to do so without modifying the source code. To achieve OCP we need to provide extension points. We can found a lot of such examples in the addin model of the applications like Visual Studio text editor, Outlook, WinAmp etc.

 

The fundamental concepts in MEF are : Import, Export, Composable Part, Contracts, Composition. The following diagram shows how they are related :

image_4_129702C1

 

A Composable Part should have at least one or more Export or Import. Imports and Exports are defined using Contracts.  A catalog of parts is used by container to compose the parts. Lets see a simple example of this.

 

Let’s define a contract first for the Imports and Exports. I have a ILogger interface as contract.

Code Snippet
  1. public interface ILogger
  2.     {
  3.         void Debug(string message);
  4.         void Error(string message);
  5.         void Warn(string message);
  6.     }

 

A ConsoleLogger class which implements ILogger and writes to the Console.

Code Snippet
  1. using System;
  2. using System.ComponentModel.Composition;
  3.  
  4.  
  5. namespace CdcSoftware.Loggers
  6. {
  7.     [Export(typeof(ILogger))]
  8.     public class ConsoleLogger : ILogger
  9.     {
  10.         #region ILogger Members
  11.  
  12.         public void Debug(string message)
  13.         {
  14.             Console.WriteLine("[DEBUG] " +  message);
  15.         }
  16.  
  17.         public void Error(string message)
  18.         {
  19.             Console.WriteLine("[ERROR] " + message);
  20.         }
  21.  
  22.         public void Warn(string message)
  23.         {
  24.             Console.WriteLine("[WARN] " + message);
  25.         }
  26.  
  27.         #endregion
  28.     }
  29. }

 

At line no. 2 the we import System.ComponentModel.Composition namespace from the references assembly System.ComponentModel.Composition one of the MEF assembly. This is required for the ExportAttribute used at line no. 7. By using Export attribute we have exported the ConsoleLogger as a composable part .

Now we have a contract and an export. Lets see the code to import. To import we need to use the ImportAttribute like this. 

Code Snippet
  1. [Import(typeof(ILogger))]
  2. public ILogger Logger { get; set; }

Now lets compose it.

Code Snippet
  1. using System.ComponentModel.Composition;
  2. using System.ComponentModel.Composition.Hosting;
  3. using System.Reflection;
  4.  
  5. namespace CdcSoftware.Application
  6. {
  7.     public class ComposedApplication
  8.     {
  9.         [Import(typeof(ILogger))]
  10.         public ILogger Logger { get; set; }
  11.  
  12.         public static void Main(string[] args)
  13.         {
  14.             ComposedApplication app = new ComposedApplication();
  15.             app.Run();
  16.         }
  17.  
  18.         private void Run()
  19.         {
  20.             Compose();
  21.             //now the application dependency is composed
  22.             Logger.Debug("This is a debug message");
  23.         }
  24.  
  25.         private void Compose()
  26.         {
  27.             AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  28.             CompositionContainer mefContainer = new CompositionContainer(catalog);
  29.             mefContainer.ComposeParts(this);
  30.         }
  31.  
  32.     }
  33. }

 

On start of the ComposedApplication, the first thing we do is to compose it as in line 20. For this application since all the required parts viz. Imports and Exports, are in the same assembly I am using AssemblyCatalog at line no 27 and initializing with the executing assembly. Then the CompositionContainer is instantiated with this catalog as constructor parameter. To get the dependency fulfilled or the part composed, we need to call the ComposeParts method on the container as in line 29.  After the composition succeeds, all the imports the current class will be fulfilled like the Logger property.

 

References :

Where is Python absolutely necessary in Intellipad? http://social.msdn.microsoft.com/Forums/en-US/oslo/thread/7a440138-1417-4414-89c3-2e6ecd2ab430

Managed Extensibility Framework in the Editor http://msdn.microsoft.com/en-us/library/dd885013.aspx