Tuesday, August 6, 2013

MEF Part 2 

On the last installment I demonstrated how to export, import, and compose types. This still doesn't solve the problem of decoupling the components. The composing code still needs a reference to a particular type which defeats the purpose of decoupling and late binding.

The key to solving that problem is to use interfaces. Interfaces separate implementation details from an objects contract, which is exactly what we need. We want the objects to behave same but we don't care about the details of how they were created or where they live. We can create an interface in a common library and reference that interface when we compose the objects. When creating extensions the common library will also be referenced and that gives us the glue we need to abstract the implementation details.

Let's look at an example interface.  This is a simple example, but you can obviously create something more complex for your needs.
public interface IPlugin
{
   string GetData();
}

And here is an example of an exporter:
[Export(typeof(IPlugin))]
 public class MyPlugin : IPlugin
 {
  public string GetData()
  {
   return "Hello World from MyPlugin";
  }
 }
Here is some sample code demonstrating how we can use the above features:
public class Program
{
   [ImportMany]
   protected IEnumerable<IPlugin> _plugins;
   static void Main(string [] args)
   {
       Program p = new Program();
       p.Run(args);
   }
 
   public void Run(string [] args)
   {
       string pluginDirectory = Path.GetFullPath(@"..\..\Plugins");
 
      // a catalog to aggregate other catalogs
      var aggregateCatalog = new AggregateCatalog();
 
      // a directory catalog, to load parts from dlls in the Plugins folder
      var directoryCatalog = new DirectoryCatalog(pluginDirectory, "*.dll");
 
      // an assembly catalog to load information about parts from this assembly
      var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
 
      aggregateCatalog.Catalogs.Add(directoryCatalog);
      aggregateCatalog.Catalogs.Add(assemblyCatalog);
 
      // create a container for our catalogs
      var container = new CompositionContainer(aggregateCatalog);
 
      // finally, compose the parts
      container.ComposeParts(this);
      foreach(IPlugin plugin in _plugins)
      {
         Console.WriteLine(plugin.GetData());
      }
   }   
}

And again, there you have it. Now the client (or composing container) only needs to reference the interface which is in a shared library. Many plugin dlls can be scanned and many plugin types can be included in a single dll. All of them will be included in the list imported by the client.

No comments:

Post a Comment