MEF 101 - Part2

This is part 2 of a 2 part series exploring the MEF.We covered some of the basics in Part 1 In this part we'll cover the following:

  • Catalogs
  • Recomposition
  • Export Providers

Catalogs:
Catalogs provide one way for MEF to discover components that it can compose. In it's most basic form it contains a registration of types. A container by itself is just an empty repository. The catalog is the one that collects and returns ComposablePartDefinitions i.e (objects of types that you have registered with MEF) to the container which in turn uses this for satifying imports on a specified object. We've seen AssemblyCatalog previously as a means for finding and satisfying imports. The following catalogs are built into the framework: TypeCatalog, AssemblyCatalog, DirectoryCatalog and AggregateCatalog. Each of these catalogs are derived from ComposablePartCatalog which is the abstract base class for all catalogs.

TypeCatalog
A type catalog is an immutable catalog that lets you register one or more type names.When this catalog is provided to the container it will use the types specified in this catalog for resolving imports and exports. You can specify types directly using 'typeof' (less useful since you are coupled to concrete types) or read type information from a configuration file instead.

 
  1. [Export(typeof(ILogger))]   
  2. public class ConsoleLogger : ILogger   
  3. {   
  4.     [Import]   
  5.     public ITextFormatter Formatter   
  6.     {   
  7.         get;   
  8.         set;   
  9.     }   
  10.   
  11.     public void Log(string str)   
  12.     {   
  13.         string formattedString = Formatter.Format(str);   
  14.         Console.WriteLine(formattedString);   
  15.     }   
  16.   
  17.     public string Name   
  18.     {   
  19.         get  
  20.         {   
  21.             return "Console Logger";   
  22.         }   
  23.     }   
  24. }   
  25.   
  26. [Export(typeof(ITextFormatter))]   
  27. public class TextFormatter : ITextFormatter   
  28. {   
  29.     public string Name   
  30.     {   
  31.         get  
  32.         {   
  33.             return "Text Formatter";   
  34.         }   
  35.     }   
  36.   
  37.     public string Format(string str)   
  38.     {   
  39.         return string.Format("Formatted Message:{0}", str);   
  40.     }   
  41. }   
  42.   
  43. class Program   
  44. {   
  45.   
  46.     [Import]   
  47.     public ILogger Logger { getset; }   
  48.   
  49.   
  50.     static void Main(string[] args)   
  51.     {   
  52.         Program p = new Program();   
  53.         ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(ConsoleLogger), typeof(TextFormatter));    
  54.         CompositionContainer container =   
  55.             new CompositionContainer(typeCatalog);               
  56.         container.ComposeParts(p);   
  57.         p.Logger.Log("Hello world");   
  58.   
  59.     }   
  60. }  
To avoid coupling to specific type names (and by extension an assembly holding a concrete implementation) you could use 'Type.GetType(string typename)' instead where the typename could come from a config file. TypeCatalog gives you fine grained control over each type that you would like to register and make available to the container.

Assembly Catalog
If all the types that you would like to register are contained in an assembly you can use the AssemblyCatalog which scans the assembly and identifies the types that expose Import and Export attributes and registers them in one fell swoop into the catalog.This is useful when you have an application which is broken down into components by say feature areas or you have an on demand download for performance reasons or you have a pay as you go model for features available to your application as is typical in plugin based applications which is what MEF is all about anyways.
The AssemblyCatalog lets you register the types defined in such assemblies and make them available to the rest of the host application via the container.
 

 
  1. class Program   
  2. {   
  3.   
  4.     [Import]   
  5.     public ILogger Logger { getset; }   
  6.   
  7.   
  8.     static void Main(string[] args)   
  9.     {   
  10.         Program p = new Program();   
  11.         Assembly assembly = Assembly.LoadFile(Path.Combine(Directory.GetCurrentDirectory(), "MEF101.Implementations.dll"));   
  12.         //you could also load the assembly on the fly from over the wire by using Assemly.Load(byte[] bytes)   
  13.         ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(assembly);               
  14.         CompositionContainer container =  new CompositionContainer(assemblyCatalog);               
  15.         container.ComposeParts(p);   
  16.         p.Logger.Log("Hello World");   
  17.   
  18.     }   
  19. }  

DirectoryCatalog
If all your types are contained in a specific directory (such as the current directory your exe is running from) you could use the DirectoryCatalog specifying a directory name and an optional search pattern to have MEF scan all assemblies in the specified directory and automatically register all types that it finds into the catalog like so:
 

 
  1. class Program   
  2.    {   
  3.   
  4.        [Import]   
  5.        public MEF101.Interfaces.ILogger Logger { getset; }   
  6.   
  7.   
  8.        static void Main(string[] args)   
  9.        {   
  10.            Program p = new Program();               
  11.            ComposablePartCatalog dirCatalog = new DirectoryCatalog(Directory.GetCurrentDirectory());   
  12.            CompositionContainer container =  new CompositionContainer(dirCatalog);               
  13.            container.ComposeParts(p);   
  14.            p.Logger.Log("Hello World");   
  15.   
  16.        }   
  17.    }  

AggregateCatalog
You can all combine all the above catalog types into an aggregate catalog type using the AggregateCatalog. As the name implies this takes in one or more ComposablePartCatalogs and exposes the the type registrations contained in all of them to the container as one whole. This is great when you have your types spread out over multiple locations. For example your applcation can start with a barebones set of features loaded from an assembly running in the current directory and you can asynchronously download updates/feature assemblies in the background. You can then compose your application using a combination of AssemblyCatalog and DirectoryCatalog
The following code shows a simplified example wherein ILogger implementations come from an AssemblyCatalog and a TypeCatalog .The TypeCatalog is added after the AggregateCatalog is constructed .

 
  1. [Export(typeof(MEF101.Interfaces.ILogger))]   
  2. class DebugLogger : MEF101.Interfaces.ILogger   
  3. {   
  4.        public string Name   
  5.        {   
  6.            get { return "Debug Logger"; }   
  7.        }   
  8.   
  9.        [Import]   
  10.        public MEF101.Interfaces.ITextFormatter Formatter   
  11.        {   
  12.            get;   
  13.            set;   
  14.        }   
  15.   
  16.        public void Log(string str)   
  17.        {   
  18.            Debug.Write(str);   
  19.        }   
  20.    }   
  21.   
  22. class Program   
  23. {   
  24.     [ImportMany]   
  25.     public IEnumerable<MEF101.Interfaces.ILogger> Loggers { getset; }   
  26.   
  27.   
  28.     static void Main(string[] args)   
  29.     {   
  30.         Program p = new Program();               
  31.         ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(DebugLogger));   
  32.         ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(Assembly.LoadFile(   
  33.                                 Path.Combine(Directory.GetCurrentDirectory(),   
  34.                                     "MEF101.Implementations.dll")));   
  35.         AggregateCatalog aggCatalog = new AggregateCatalog(assemblyCatalog);   
  36.         aggCatalog.Catalogs.Add(typeCatalog);   
  37.         CompositionContainer container =  new CompositionContainer(aggCatalog);               
  38.         container.ComposeParts(p);   
  39.         foreach (var logger in p.Loggers)   
  40.         {   
  41.             logger.Log("Hello");   
  42.         }              
  43.     }   
  44. }  

Whereas all the other catalog types are immutable, the AggregateCatalog lets you add and subract ComposablePartCatalogs from it's internal "Catalog" collection facilitating recomposition of the container on the fly.

Recomposition
MEF provides a facility to allow a composable application to change it's behavior when composable parts can change on the fly via a mechanism called recomposition.
Let's re-write the example such that the call to "Catalogs.Add" is made after we compose the instance of Program
 

 
  1. [Export(typeof(MEF101.Interfaces.ILogger))]   
  2. class DebugLogger : MEF101.Interfaces.ILogger   
  3. {   
  4.        public string Name   
  5.        {   
  6.            get { return "Debug Logger"; }   
  7.        }   
  8.   
  9.        [Import]   
  10.        public MEF101.Interfaces.ITextFormatter Formatter   
  11.        {   
  12.            get;   
  13.            set;   
  14.        }   
  15.   
  16.        public void Log(string str)   
  17.        {   
  18.            Debug.Write(str);   
  19.        }   
  20.    }   
  21.   
  22. class Program   
  23. {   
  24.     [ImportMany]   
  25.     public IEnumerable<MEF101.Interfaces.ILogger> Loggers { getset; }   
  26.   
  27.   
  28.     static void Main(string[] args)   
  29.     {   
  30.         Program p = new Program();               
  31.         ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(DebugLogger));   
  32.         ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(Assembly.LoadFile(   
  33.                                 Path.Combine(Directory.GetCurrentDirectory(),   
  34.                                     "MEF101.Implementations.dll")));   
  35.         AggregateCatalog aggCatalog = new AggregateCatalog(assemblyCatalog);   
  36.            CompositionContainer container =  new CompositionContainer(aggCatalog);               
  37.            container.ComposeParts(p);   
  38.         aggCatalog.Catalogs.Add(typeCatalog);   
  39.             foreach (var logger in p.Loggers)   
  40.             {   
  41.                logger.Log("Hello");   
  42.             }              
  43.     }   
  44. }  

This causes the following exception to be thrown:
Change in exports prevented by non-recomposable import 'MEF.LazyImports.Program.Loggers (ContractName="MEF101.Interfaces.ILogger")' on part 'MEF.LazyImports.Program'
This happens because the container changed after the instance of Program was already composed. The error indicates that the part being imported on the instance of Program i.e Loggers does not support recomposition and hence the change was prevented. We can easily enable this by changing the [Import] declaration on the Loggers property to
[ImportMany(AllowRecomposition=true)]. This allows for the program to dynamically obtain another instance of logger in it's Loggers collection and continue working normally
i.e. recompose itself
Similar to adding catalogs on the fly, we can also remove catalogs from the container and this will cause recomposition and update the Import on the instance of the Program automatically like so:
 
  1. [Export(typeof(MEF101.Interfaces.ILogger))]   
  2. class DebugLogger : MEF101.Interfaces.ILogger   
  3. {   
  4.        public string Name   
  5.        {   
  6.            get { return "Debug Logger"; }   
  7.        }   
  8.   
  9.        [Import]   
  10.        public MEF101.Interfaces.ITextFormatter Formatter   
  11.        {   
  12.            get;   
  13.            set;   
  14.        }   
  15.   
  16.        public void Log(string str)   
  17.        {   
  18.            Debug.Write(str);   
  19.        }   
  20.    }   
  21.   
  22. class Program   
  23. {   
  24.     [ImportMany]   
  25.     public IEnumerable<MEF101.Interfaces.ILogger> Loggers { getset; }   
  26.   
  27.   
  28.     static void Main(string[] args)   
  29.     {   
  30.         Program p = new Program();               
  31.         ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(DebugLogger));   
  32.         ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(Assembly.LoadFile(   
  33.                                 Path.Combine(Directory.GetCurrentDirectory(),   
  34.                                     "MEF101.Implementations.dll")));   
  35.         AggregateCatalog aggCatalog = new AggregateCatalog(assemblyCatalog);   
  36.            CompositionContainer container =  new CompositionContainer(aggCatalog);               
  37.            container.ComposeParts(p);   
  38.         aggCatalog.Catalogs.Add(typeCatalog);   
  39.             foreach (var logger in p.Loggers)   
  40.             {   
  41.                logger.Log("Hello");   
  42.             }   
  43.         aggCatalog.Catalogs.Remove(typeCatalog);   
  44.             foreach (var logger in p.Loggers)   
  45.             {   
  46.                 logger.Log("Hello");   
  47.             }              
  48.     }   
  49. }  

Notifications of Recomposition
MEF can notify you whenever a recomposition happens in case you need to perform some action such as cleanup/housekeeping. It does this via an interface called IPartImportSatisfiedNotification
You implement this interface in any of your parts in which you need that a recomposition(or more generally a composition) has occured.The interface has a single method called OnImportSatisfied
that you need to provide an implementation for.

 

 
  1. [Export(typeof(MEF101.Interfaces.ILogger))]   
  2. class DebugLogger : MEF101.Interfaces.ILogger   
  3. {   
  4.        public string Name   
  5.        {   
  6.            get { return "Debug Logger"; }   
  7.        }   
  8.   
  9.        [Import]   
  10.        public MEF101.Interfaces.ITextFormatter Formatter   
  11.        {   
  12.            get;   
  13.            set;   
  14.        }   
  15.   
  16.        public void Log(string str)   
  17.        {   
  18.            Debug.Write(str);   
  19.        }   
  20.    }   
  21.   
  22. class Program : IPartImportsSatisfiedNotification   
  23. {   
  24.     [ImportMany]   
  25.     public IEnumerable<MEF101.Interfaces.ILogger> Loggers { getset; }   
  26.     
  27.     public void OnImportsSatisfied()   
  28.     {   
  29.         Console.WriteLine("Import satisfied");   
  30.     }   
  31.     static void Main(string[] args)   
  32.     {   
  33.         Program p = new Program();               
  34.         ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(DebugLogger));   
  35.         ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(Assembly.LoadFile(   
  36.                                 Path.Combine(Directory.GetCurrentDirectory(),   
  37.                                     "MEF101.Implementations.dll")));   
  38.         AggregateCatalog aggCatalog = new AggregateCatalog(assemblyCatalog);   
  39.            CompositionContainer container =  new CompositionContainer(aggCatalog);               
  40.            container.ComposeParts(p);   
  41.         aggCatalog.Catalogs.Add(typeCatalog);   
  42.             foreach (var logger in p.Loggers)   
  43.             {   
  44.                logger.Log("Hello");   
  45.             }   
  46.         aggCatalog.Catalogs.Remove(typeCatalog);   
  47.             foreach (var logger in p.Loggers)   
  48.             {   
  49.                 logger.Log("Hello");   
  50.             }              
  51.     }   
  52. }  

In the above example, recomposition occurs thrice. Once for the initial composition: container.ComposeParts(p); and then once each for the calls to Catalogs.Add and Catalogs.Remove

Export Providers
Up until now we have used catalogs to initialize the CompositionContainer.We can also use one or more Export Providers to initialize the container. The difference is that by providing one or more export providers, MEF will search for exports matching an import in the order in which the exports providers are set on the CompositionContainer. This helps in situations where more than one catalog has part definitions that could satisfy an Import. Let's change the code above to illustrate this
 

 
  1. [Export(typeof(MEF101.Interfaces.ILogger))]   
  2.     class DebugLogger : MEF101.Interfaces.ILogger   
  3.     {   
  4.   
  5.         public string Name   
  6.         {   
  7.             get { return "Debug Logger"; }   
  8.         }   
  9.   
  10.         [Import]   
  11.         public MEF101.Interfaces.ITextFormatter Formatter   
  12.         {   
  13.             get;   
  14.             set;   
  15.         }   
  16.   
  17.         public void Log(string str)   
  18.         {   
  19.             Debug.Write(str);   
  20.         }   
  21.     }   
  22.        
  23.     class Program    
  24.     {   
  25.          [Import(AllowRecomposition=true)]   
  26.         public MEF101.Interfaces.ILogger Logger { getset; }   
  27.   
  28.         static void Main(string[] args)   
  29.         {   
  30.             Program p = new Program();               
  31.             ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(DebugLogger));   
  32.             ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(Assembly.LoadFile(   
  33.                                               Path.Combine(Directory.GetCurrentDirectory(),   
  34.                                               "MEF101.Implementations.dll")));   
  35.             AggregateCatalog aggCatalog = new AggregateCatalog(typeCatalog,assemblyCatalog);   
  36.             CompositionContainer container =  new CompositionContainer(aggCatalog);               
  37.             container.ComposeParts(p);             
  38.         }         
  39.     }  
The call to ComposeParts causes the following error.
More than one export was found that matches the constraint . The reason for this is because there are 2 parts (ConsoleLogger and DebugLogger) that are exported as ILogger and the Program has a single import for a logger instead of a collection of loggers. In this case MEF does not know which export to use for satisfying the import and hence the exception.
By using ExportProviders you can control the resolution order for satisying imports.

 
  1. [Export(typeof(MEF101.Interfaces.ILogger))]   
  2. class DebugLogger : MEF101.Interfaces.ILogger   
  3. {   
  4.   
  5.        public string Name   
  6.        {   
  7.            get { return "Debug Logger"; }   
  8.        }   
  9.   
  10.        [Import]   
  11.        public MEF101.Interfaces.ITextFormatter Formatter   
  12.        {   
  13.            get;   
  14.            set;   
  15.        }   
  16.   
  17.        public void Log(string str)   
  18.        {   
  19.            Debug.Write(str);   
  20.        }   
  21.    }   
  22.   
  23. class Program   
  24. {   
  25.      [Import(AllowRecomposition=true)]   
  26.     public MEF101.Interfaces.ILogger Logger { getset; }   
  27.           
  28.     static void Main(string[] args)   
  29.     {   
  30.         Program p = new Program();               
  31.         ComposablePartCatalog typeCatalog = new TypeCatalog(typeof(DebugLogger));   
  32.         ComposablePartCatalog assemblyCatalog = new AssemblyCatalog(Assembly.LoadFile(   
  33.                                           Path.Combine(Directory.GetCurrentDirectory(),   
  34.                                           "MEF101.Implementations.dll")));   
  35.            CatalogExportProvider assemblyProvider = new CatalogExportProvider(assemblyCatalog);   
  36.            CatalogExportProvider typeProvider = new CatalogExportProvider(typeCatalog);   
  37.            AggregateExportProvider aggProvider = new AggregateExportProvider(   
  38.                assemblyProvider, typeProvider);               
  39.            CompositionContainer container = new CompositionContainer(aggProvider);   
  40.            typeProvider.SourceProvider = container;   
  41.            assemblyProvider.SourceProvider = container;   
  42.         container.ComposeParts(p);   
  43.         p.Logger.Log("Hello");   
  44.        }          
  45.    }  

Since the AggregateExportProvider contains the assemblyProvider first followed by typeProvider, the call to ComposeParts checks the assemblyProvider(and by extension the assemblyCatalog) first and notices that it can satisfy the import defined on Program, stops there and ends up using the ConsoleLogger. If no export was defined in the assemblyProvider then MEF would continue looking into the typeProvider for satisfying the import. Vice-versa, if the order was reversed and typeProvider was set before assemblyProvider then DebugLogger would have been used instead. You can also buid your own export providers by deriving from ExportProvider. This wraps up the coverage of MEF.

Print | posted @ Sunday, October 30, 2011 3:27 PM

Comments on this entry:

Gravatar # re: MEF 101 - Part2
by Glenn at 1/24/2013 11:19 AM

Thanks for both parts 1 and 2. These articles really helped me.
Gravatar # re: MEF 101 - Part2
by Dinesh at 10/9/2013 11:42 PM

Very Useful. Thanks
Post A Comment
Title:
Name:
Email:
Comment:
Verification: