Wednesday, November 25, 2009

Looking Around at Circular References in MEF

In the last post of this series, we created a new example code base used to display help text for various "commands".  This was a simple code base that extended previous examples by using external assemblies and different catalogs to identify all of the parts that can be imported and mapped.  This example covers a lot of scenarios when applied beyond the means of console-based text output since each imported "command" could literally be a functional piece of code by itself.  However, what happens when the imports require something from our main application?  When one object has a dependency with another object of another type; only for the dependent have a dependency towards the initial type; this is called a circular dependency.  In this post, we're going to look at the condition of a circular dependency and see how MEF encounters such issues.

A Closer Look at Circular Dependencies in General

To reiterate what was said a moment ago, a circular dependency is where two objects depend on each other for different things in somewhat of a symbiotic relationship.  Let's look at an example.  Let's say we have an application that contains two special types.  One type is used to read configuration information and implements the IConfig interface.  The second type is used to log any errors that may occur in the system and implements the IErrorManager interface. 

Example5 - CircularReference

In this illustration, our configuration manager requires an instance of an error manager in case there was an error reading the configuration information.  In addition, our error manager requires an instance of a configuration manager to know how the system is configured for logging errors.  Since each type has a required dependency on the other, a common pattern is to place the required dependencies into a constructor so that the proper dependencies are provided and the type is instantiated in a valid state.  Below is an example what our constructors may look like.

   1:  class ConfigurationManager : IConfig
   2:  {
   3:      private IErrorManager _errMgr;
   4:   
   5:      public ConfigurationManager (IErrorManager errorManager)
   6:      {
   7:          _errMgr = errorManager;
   8:      }
   9:  }
  10:   
  11:  class ErrorManager: IErrorManager
  12:  {
  13:      private IConfig _configMgr;
  14:   
  15:      public ErrorManager(IConfig configManager)
  16:      {
  17:          _configMgr = configManager;
  18:      }
  19:  }

If we have to pass in a valid instance of IErrorManager to our configuration manager, we would first have to instantiate an instance of IConfig for our error manager.  Since there's not a way based on the above code to instantiate a type without the other, the code has to be changed to allow for each to be created in an invalid state and the dependency to be passed to it via a property like below.

   1:  class ConfigurationManager : IConfig
   2:  {
   3:      public IErrorManager ErrMgr { get; set; }
   4:   
   5:      public ConfigurationManager() { }
   6:  }
   7:   
   8:  class ErrorManager : IErrorManager
   9:  {
  10:      public IConfig ConfigMgr { get; set; }
  11:   
  12:      public ErrorManager() { }
  13:  }

The issue with this pattern is that it requires the developer to now remember to always inject the proper dependencies after the types have been instantiated through their constructors.  Ultimately, it's risky because we're all human and tend to forget from time to time.   IoC containers help here a little bit, but can still confuse people.  An alternative solution which many people prefer is to used a bootstrapped version of one of the two objects that doesn't depend on the other.  This pattern also has issues due to what may not be available in a bootstrapped version.  In our example, a bootstrapped IConfig object may not contain any error management code with in it.

Looking At a Simple MEF Circular Reference

Now that we, hopefully, understand a bit more about what a circular reference is and where it can occur, let's see about recreating our example with MEF.   Let's create a program that has a property for a IErrorManager and IConfigMgr instances.  To build the circular reference, let's inject the dependencies through the constructors of our ErrorManager and ConfigManager classes and mark the constructors as Imports.  Below is our Circular Reference implementation.

Program.cs

   1:  namespace MEFExample5
   2:  {
   3:      class Program
   4:      {
   5:          [Import(typeof(IConfigManager))]
   6:          public IConfigManager ConfigurationManager { get; set; }
   7:   
   8:          [Import(typeof(IErrorManager))]
   9:          public IErrorManager ErrorManager { get; set; }
  10:   
  11:          static void Main(string[] args)
  12:          {
  13:              var prog = new Program();
  14:              prog.Run();
  15:          }
  16:   
  17:          void Run()
  18:          {
  19:              Compose();
  20:              Console.WriteLine(ConfigurationManager.TestText);
  21:              Console.WriteLine(ErrorManager.TestText);
  22:              Console.ReadKey();
  23:          }
  24:   
  25:          void Compose()
  26:          {
  27:              var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  28:              var container = new CompositionContainer(catalog);
  29:              container.ComposeParts(this);
  30:          }
  31:      }
  32:  }

ConfigMgr.cs

   1:  namespace MEFExample5
   2:  {
   3:      public interface IConfigManager
   4:      {
   5:          string TestText { get; set; }
   6:      }
   7:   
   8:      [Export(typeof(IConfigManager))]
   9:      public class ConfigMgr : IConfigManager
  10:      {
  11:   
  12:          public string TestText { get; set; }
  13:   
  14:          public ConfigMgr()
  15:          {
  16:              TestText = "Config";
  17:          }
  18:   
  19:          [ImportingConstructor]
  20:          public ConfigMgr(IErrorManager errorManager) : this()
  21:          {
  22:              _errorManager = errorManager;
  23:          }
  24:      }
  25:  }

ErrorManager.cs

   1:  namespace MEFExample5
   2:  {
   3:      public interface IErrorManager
   4:      {
   5:          string TestText { get; set; }
   6:      }
   7:   
   8:      [Export(typeof(IErrorManager))]
   9:      public class ErrorManager : IErrorManager
  10:      {
  11:          public string TestText { get; set; }
  12:   
  13:          public ErrorManager ()
  14:          {
  15:              TestText = "Error";
  16:          }
  17:   
  18:          [ImportingConstructor]
  19:          public ErrorManager(IConfigManager configManager) : this()
  20:          {
  21:              _configManager = configManager;
  22:          }
  23:      }
  24:  }

When we try to run this, we get the following error message:

Example5-Error1

As we can see, MEF doesn't completely remove issues inherit from constructor injection-based circular references.  Like we discussed above though, we can move our dependencies into properties instead of a constructor.  What's nice about doing such with MEF though is that we can don't have to truly remember to wire up the dependencies manually.  With the ability to declaratively set our dependent properties as Imports, we won't need any additional code.  Below is the updated code to that addresses the circular reference.

ConfigMgr.cs

   1:  namespace MEFExample5
   2:  {
   3:      public interface IConfigManager
   4:      {
   5:          string TestText { get; set; }
   6:      }
   7:   
   8:      [Export(typeof(IConfigManager))]
   9:      public class ConfigMgr : IConfigManager
  10:      {
  11:          private IErrorManager _errorManager;
  12:   
  13:          public string TestText { get; set; }
  14:   
  15:          [Import(typeof(IErrorManager))]
  16:          public IErrorManager ErrorMngr
  17:          {
  18:              get { return _errorManager; }
  19:              set { _errorManager = value; }
  20:          }
  21:   
  22:          public ConfigMgr()
  23:          {
  24:              TestText = "Config";
  25:          }
  26:   
  27:          public ConfigMgr(IErrorManager errorManager) : this()
  28:          {
  29:              _errorManager = errorManager;
  30:          }
  31:      }
  32:  }

ErrorManager.cs

   1:  namespace MEFExample5
   2:  {
   3:      public interface IErrorManager
   4:      {
   5:          string TestText { get; set; }
   6:      }
   7:   
   8:      [Export(typeof(IErrorManager))]
   9:      public class ErrorManager : IErrorManager
  10:      {
  11:          private IConfigManager _configManager;
  12:   
  13:          public string TestText { get; set; }
  14:   
  15:          [Import(typeof(IConfigManager))]
  16:          public IConfigManager ConfigManager
  17:          {
  18:              get { return _configManager; }
  19:              set { _configManager = value; }
  20:          }
  21:   
  22:          public ErrorManager ()
  23:          {
  24:              TestText = "Error";
  25:          }
  26:   
  27:          public ErrorManager(IConfigManager configManager) : this()
  28:          {
  29:              _configManager = configManager;
  30:          }
  31:      }
  32:  }

Summary:

So in this post we looked at Circular References and and how they are handled through MEF.  In the next post of this series, we'll dive into how we can use lazy loading towards imported parts and where they could be applied at.

Resources:


kick it on DotNetKicks.comShout it

Monday, November 23, 2009

Playing Nice with Other Assemblies using MEF Catalogs

Looking over the previous three posts, we have been working within a single assembly for managing our extensibility parts.  While this works well if we want to use MEF more as an IoC container, it really limits the extensibility of our application.  In the single assembly model that we've been using, every update would require a new build of the application.  To overcome this limitation, we'll create a new example project that builds on what we've covered and branch it out into multiple assemblies.

Building a Console's Help Information

In the new example, we'll be creating a new console application whose sole purpose is to output help documentation on various fake commands.  We aren't going to be implementing any of the actual command functionality; however, each extension will represent a new command and is responsible for its own help documentation.  Given our scenario, it isn't very practical for an application to do nothing without any extensions.  Because of this, our example will be importing in the various parts from both inside of its own assembly (like we've done previously) as well as assemblies located in a Plug-Ins directory.

Defining Our Project Structure

Given the scenario that we are going to be working on, there are a couple of different ways we can structure our solution.  In an attempt to make this a bit more practical, we'll split out our extensibility contract into a separate assembly.  This assembly we'll reference into our hosting application as well as any external plug-in assemblies so that they aren't directly referencing our executable. While this extra assembly that contains our extensibility contract will be very simple, it also provides a point to branch out with other core functionality in a real world scenario.

For the sake of this example, let's start a new Windows Console application project named MEFExample4. Once the solution was created, we need to add a Class Library Project named MEFExample4.Core.  Below is an image of the possible solution explorer window inside of Visual Studio.

Example4-SolutionExplorerStart

Creating Our Extensibility Contract

Now that we have the project structure established, let's go ahead and add a new Interface to the MEFExample4.Core project named IHelp.  This interface will be used by our various "commands" and define a CommandName and HelpText property.  Below is the code for IHelp.

   1:  namespace MEFExample4.Core
   2:  {
   3:      public interface IHelp
   4:      {
   5:          string CommandName { get; }
   6:          string HelpText { get; }
   7:      }
   8:  }

Building the Hosting Application

Now that we have our contract by means of the MEFExample4.Core.IHelp type, let's create our extensible application that imports multiple instances of IHelp types.  In this first iteration, we'll setup the application in the same way as our initial, simple example from earlier posts. We'll use the AssemblyCatalog which is used to instantiate types of a given assembly, and like our previous examples, will use Reflection to get the currently executed assembly. 

In addition to the catalog we're currently choosing, we need to make sure that we have our collection property to hold our imported parts and decorate it with the ImportMany(typeof(IHelp)) attribute. Once that's in place, the only thing left is to setup our console output for displaying the CommandName and HelpText properties of our imports.  Below is first iteration of our Program.cs file.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.Composition;
   4:  using System.ComponentModel.Composition.Hosting;
   5:  using System.Reflection;
   6:  using System.Text;
   7:  using MEFExample4.Core;
   8:   
   9:  namespace MEFExample4
  10:  {
  11:      class Program
  12:      {
  13:          static void Main(string[] args)
  14:          {
  15:              Program p = new Program();
  16:              p.Run();
  17:          }
  18:   
  19:          [ImportMany("command", typeof(IHelp))]
  20:          public IEnumerable<IHelp> Commands { get; set; }
  21:   
  22:          void Run()
  23:          {
  24:              Compose();
  25:   
  26:              foreach(IHelp cmd in Commands)
  27:              {
  28:                  Console.WriteLine(FormatCommandOutput(cmd));
  29:              }
  30:   
  31:              Console.ReadKey();
  32:          }
  33:   
  34:          void Compose()
  35:          {
  36:              var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  37:              var container = new CompositionContainer(catalog);
  38:              container.ComposeParts(this);
  39:          }
  40:   
  41:          string FormatCommandOutput(IHelp command)
  42:          {
  43:              StringBuilder sb = new StringBuilder();
  44:              sb.AppendLine(command.CommandName);
  45:              sb.AppendLine(string.Empty); //empty line
  46:   
  47:              List<string> splitText = SplitText(command.HelpText);
  48:   
  49:              splitText.ForEach(t => sb.AppendLine(t));
  50:   
  51:              return sb.ToString();
  52:          }
  53:   
  54:          List<string> SplitText(string text)
  55:          {
  56:              const int LINE_LENGTH = 55;
  57:              int length = text.Length;
  58:              List<string> output = new List<string>();
  59:   
  60:              for (int i = 0; i * LINE_LENGTH < length; i++)
  61:              {
  62:                  if (i*LINE_LENGTH + LINE_LENGTH <= length)
  63:                  {
  64:                      output.Add(text.Substring(i * LINE_LENGTH, LINE_LENGTH).PadLeft(60, ' '));
  65:                  }
  66:                  else
  67:                  {
  68:                      output.Add(text.Substring(i * LINE_LENGTH).PadLeft(55, ' '));
  69:                  }
  70:              }
  71:   
  72:              return output;
  73:          }
  74:      }
  75:  }

Writing the First Command

Now that we have our contract and hosting application defined, we need to create our first "command".  We started our application to look inside of it's own assembly for our various types that will be imported.  Because of this, we need to add a new class called TestCommand and have it implement our IHelp interface.  Let's set the CommandName property to "Test" and the HelpText property to some random text.  Once we decorate the class with our Export(typeof(IHelp)) attribute, we can compile and run the application and see our text.

Example4-OutputTest1

Preparing the Application For External Plug-ins

We've tested our application in the same fashion as the previous posts in this series and we're almost ready to work with external assemblies.  Before we do that, we need to add a new folder to our console application project named Plug-Ins.  This folder will be our holding area for the external assemblies that house additional "commands". 

The second thing we need to do is change our AssemblyCatalog to a DirectoryCatalog object.  Unlike an AssemblyCatalog which accepts an assembly as a constructor parameter, the DirectoryCatalog accepts a file path (relative or absolute) to the location where any number of the assemblies are found.  In addition to that difference, the DirectoryCatalog will review all assemblies located in the supplied directory.  This means that once we point our DirectoryCatalog to our Plug-Ins folder, we can just start dropping assemblies into there without having to recompile the console app. Note: Since the project uses the default build location of /bin/Debug, the path we supply will traverse up the directory tree once again. In practice, the Plug-Ins directory should probably be in the same directory as the executable application or the path configurable via an App.config file.  Below is the updated Program.Compose() method.

   1:  void Compose()
   2:  {
   3:      var catalog = new DirectoryCatalog(@"..\..\Plug-ins\");
   4:      var container = new CompositionContainer(catalog);
   5:      container.ComposeParts(this);
   6:  }

Adding Another Assembly

With our folder/repository ready to be used by our DirectoryCatalog, it's time for us to create a new "command" that implements IHelp from a new/separate assembly. Let's add a new class library project called MEFExample4.Commands.  Before we write any code for our new command, let's update the project properties a little bit.  Since we are working on our Commands assembly in the same solution as the application, let's add a Post-Build Event script that will copy our built Commands assembly to the Plug-Ins directory we made in the previous section.  To do this, we need to open the Project's Property window and click on the Build Events tab.  In the Post-Build Event Command Line textbox, we need to add the following:

copy $(TargetPath) $(SolutionDir)\MEFExample4\Plug-ins\$(TargetFileName)

If you are not familiar with Pre/Post-Build events and the macros, feel free to check out this link since they can be used to do some pretty interesting things.  As for the above script, it tells the build manager to copy the TargetPath (the built .dll file) and copy it to the application's Plug-ins directory using the same name.

Now that we have the project setup, we can write the code for our next "command".  We are going to rename Class1.cs to ExampleCommand.cs and add the below code into it.

   1:  using System.ComponentModel.Composition;
   2:  using MEFExample4.Core;
   3:   
   4:  namespace MEFExample4.Commands
   5:  {
   6:      [Export("command", typeof(IHelp))]
   7:      public class ExampleCommand : IHelp
   8:      {
   9:          private string _helpText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
  10:                                          "Nulla molestie erat rhoncus sapien volutpat rhoncus sed sit " +
  11:                                          "amet dolor. Aliquam rhoncus sem et neque elementum lacinia ut " +
  12:                                          "vel est. Vestibulum et urna sit amet nibh feugiat imperdiet ac " +
  13:                                          "id tellus. Fusce lectus risus, congue et.";
  14:   
  15:          public string CommandName
  16:          {
  17:              get { return "Example1"; }
  18:          }
  19:   
  20:          public string HelpText
  21:          {
  22:              get { return _helpText; }
  23:          }
  24:      }
  25:  }

After that, let's build the solution and test everything out.  When we run the application we should now see Example1 and the Ipsum text that we designated for the HelpText property like the image below.

Example4-OutputTest2

In addition, if we take a look in the Plug-Ins directory, we can also see our MEFExample4.Commands.dll is listed.

Working with Internal and External Assemblies

Now that we've seen how to work with the currently executing assembly as well a directory containing multiple different assemblies, there's one last thing for this post.  What if we wanted to look both internally and externally for our Commands?  An application like this without any native commands is just a shell and provides little initial value.  Sure we can create all of our commands in a separate assembly so we never have to touch the application; however, this allows for our initial commands to be deleted.  In order to cultivate both internal commands and external commands, we need to use an AggregateCatalog object.

An AggregateCatalog differs from an AssemblyCatalog or a DirectoryCatalog in that it's, in essence, a catalog of catalogs.  Looking at the intellisense, we're shown that the AssemblyCatalog constructor accepts either an IEnumerable<ComposablePartCatalog> object or a parameter array of ComposablePartCatalogs.  For the example, we'll use the parameter array version of the constructor to add new instances of an AssemblyCatalog and DirectoryCatalog into our new AggregateCatalog as shown by the updated code below.

   1:  void Compose()
   2:  {
   3:      var catalog = new AggregateCatalog(new DirectoryCatalog(@"..\..\Plug-ins\"),
   4:                                         new AssemblyCatalog(Assembly.GetExecutingAssembly()));
   5:      var container = new CompositionContainer(catalog);
   6:      container.ComposeParts(this);
   7:  }

Once we do this, we can test the application and see that the output has changed to the following:

Example4-OutputTest3

Summary

In this post we really looked at starting a more applicable console application using MEF.  We looked at how you can use a couple different catalogs in order to obtain the assemblies that contain parts for our application.  There are additional catalogs available that allow other ways to import assemblies and types and can also be used with an AggregateCatalog.  In the next post for this series, we'll dive into how MEF handles Circular References real quick and then further expand our example.

Resources:



kick it on DotNetKicks.comShout it

Thursday, November 19, 2009

LINQing to MEF Imports

At the end of the last post, we looked at how we can explicitly manage our imports and exports using a combination of text-based labels and type declarations.  In addition, we began to look at the ImportMany() attribute for importing more than one value in our extensible application.  Along with the ImportMany(), we looked at looping through the simple example to display the property of each imported type.  This approach isn't bad if the number of imported types are small; however, looping really wouldn't work well if your application loaded a very large amount of applications.  In a way, it's a great problem to have if your application has a large community based plug-in repository (i.e. Wordpress or Firefox).  There may come a time when we may need to find a specific plug-in without looping through everything.  In order to determine this, there's a couple different ways to handling this.  In this post, we're going to explore a non-MEF way of handling it using LINQ.  In a future post, we'll look at how we can expand upon this using some of the constructs that MEF provides.

Giving Our Messages a New Property

One of the nice thing about using ImportMany(), is that it tells MEF to add the various parts into a collection of type IEnumerable.  What's great about that is that any collection that is derived from IEnumerable is query-able using LINQ.  Because of this, we can use LINQ to select individual or subsets of imported parts.

In this example, we need to modify our IMessage interface to supply it a Name property.  We'll use this property as a label of our decorated part class and then query against it.  Because we're updating the definition of IMessage, we also need to implement the Name property in our SimpleMessage and SecondMessage classes.  We'll set the new properties of our classes to "Simple" and "Second" respectively as well.  Below is the code for SecondMessage at this point for illustration.

   1:  using System.ComponentModel.Composition;
   2:   
   3:  namespace MEFExample3
   4:  {
   5:      [Export(typeof(IMessage))]
   6:      public class SecondMessage : IMessage
   7:      {
   8:          public string MyMessage
   9:          {
  10:              get { return "Hello, From a Second Class"; }
  11:          }
  12:   
  13:          public string Name
  14:          {
  15:              get { return "Second"; }
  16:          }
  17:      }
  18:  }

LINQ to ImportMany

Now that we have a property that can be easily identified, let's update our Program class to just output SimpleMessage.MyMessage instead of both.  To do this using LINQ we first need to add the System.Linq namespace.  Next next add a Where() clause to our IEnumerable<IMessage> collection to query against the IMessage.Name property.  Finally, let's output the appropriate message.  Below is what our Program class looks like after all of these changes:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.Composition;
   4:  using System.ComponentModel.Composition.Hosting;
   5:  using System.Linq;
   6:  using System.Reflection;
   7:   
   8:  namespace MEFExample3
   9:  {
  10:      public class Program
  11:      {
  12:          [ImportMany(typeof(IMessage))]
  13:          IEnumerable<IMessage> Messages { get; set; }
  14:   
  15:          static void Main(string[] args)
  16:          {
  17:              Program p = new Program();
  18:              p.Run();
  19:          }
  20:   
  21:          void Run()
  22:          {
  23:              Compose();
  24:   
  25:              IMessage message = (Messages.Where(m => m.Name == "Simple")).FirstOrDefault();
  26:              Console.WriteLine(message.MyMessage);
  27:              Console.ReadKey();
  28:          }
  29:   
  30:          private void Compose()
  31:          {
  32:              var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  33:              var container = new CompositionContainer(catalog);
  34:              container.ComposeParts(this);
  35:          }
  36:      }
  37:  }

In this post, we took our collection of imported types and filtered them using LINQ.  MEF provides additional ways to manage this using declarative Metadata attributes which we'll explore in a future post; however, for now, this method should provide a solution during this learning experience.  In the next post, we'll look at expanding our horizons by breakout our imported types into a separate assembly and using different types of part catalogs to achieve such.

Resources


kick it on DotNetKicks.comShout it

Friday, November 13, 2009

A Deeper Look at MEF's Imports and Exports

In the last article, I provided a very brief introduction to MEF and showed a very simple console application.  In this section, we'll be looking at the issues associated with that sample and diving into the various aspects of declaring your Imports and Exports to overcome those issues.  The code and samples in this post will still be within a single assembly.  Because of this, I'll may use the word Dependencies and Parts interchangeably.

The Issues with the Last Example's Simplicity

At the end of the last post, we looked at a simple console application that used MEF to wire our dependencies within the same assembly together.  Below is the code for our Program.cs and SimpleMessage.cs classes again.

Program.cs

   1:  using System;
   2:  using System.ComponentModel.Composition;
   3:  using System.ComponentModel.Composition.Hosting;
   4:  using System.Reflection;
   5:   
   6:  namespace MEFExample2
   7:  {
   8:      class Program
   9:      {
  10:          [Import()]
  11:          string Message { get; set; }
  12:   
  13:          static void Main(string[] args)
  14:          {
  15:              Program p = new Program();
  16:              p.Run();
  17:          }
  18:   
  19:          void Run()
  20:          {
  21:              Compose();
  22:              Console.WriteLine(Message);
  23:              Console.ReadKey();
  24:          }
  25:   
  26:          private void Compose()
  27:          {
  28:              var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  29:              var container = new CompositionContainer(catalog);
  30:              container.ComposeParts(this);
  31:          }
  32:      }
  33:  }

SimpleMessage.cs

   1:  using System.ComponentModel.Composition;
   2:   
   3:  namespace MEFExample2
   4:  {
   5:      class SimpleMessage        
   6:      {
   7:          [Export()]
   8:          string MyMessage
   9:          {
  10:              get { return "Hello, Extensible World"; }
  11:          }
  12:      }
  13:  }

Now, what happens when we change our SimpleMessage class to include a new string property, we'll call it NewMessage, that's also decorated with the [Exprot()] attribute and run the application again?

   1:  [Export()]
   2:  string NewMessage
   3:  {
   4:      get { return "This is another Message."; }
   5:  }

Example2-ChangeRejectedException

Doh! Runtime Error of Type ChangeRejectedException. At this point MEF doesn't know how to wire up our dependencies properly so we'll need to help it out.  First, let's look at what the purpose of Imports and Exports are and how we can use them better.

Defining the Extensibility Contract Using Text Descriptions.

When MEF is composing the various parts of our application together, it attempts to match the members we declare as Exports to matching Imports.  In our initial example, there was only a single Export and Import that were of a matching type (string) and the mapping was easy.  Once we added the second property of the same type, MEF didn't know what to do and attempted to apply both string Exports to our only string Import.  What if we only wanted the SimpleMessage.MyMessage property to be mapped to Program.Message property?  In order to to this, MEF provides options for the Import and Export attributes that allow us to declare a specific label and/or type that can be used in the mapping process.

In order to use a label to declare the mapping, all we would have to do is change the Program.Message  and SimpleMessage.MyMessage declarations to the following:

   1:  public class Program
   2:  {
   3:      [Import("ExampleMessage")]
   4:      string Message { get; set; }
   5:   
   6:      ...
   7:  }
 
   1:  public class SimpleMessage        
   2:  {
   3:      [Export("ExampleMessage")]
   4:      public string MyMessage
   5:      {
   6:          get { return "Hello, Extensible World"; }
   7:      }
   8:   
   9:      ...
  10:  }

As you can see, we provided a simple text description/label to the Import and Export attributes that allow us better control of the mapping.  By making just this update, we can now compile and run the code successfully.

Defining the Extensibility Contract Using Types

For simple cases like this, just passing a text description or label is more than enough to handle the mappings.  But I also said that we could use Types in addition (or in place of) the descriptions to handle the mappings.  In the original example from the last post, we inferred this aspect since the properties were the same type (both were string); however, we can modify our code so that Program.Message took an object instead of just a string.

To implement this change, let's make some changes to our application.  First, let's define a new Interface called IMessage that defines the property MyMessage and have our SimpleMessage class implement it.  Next, we need to make some changes to our Export() declaration.  Since we are now passing types we will want to move the Export() decoration to be class level instead of on the SimpleMessage.MyMessage property and also explicitly declare the Export to be of our IMessage type as shown below.

   1:  [Export(typeof(IMessage))]
   2:  public class SimpleMessage : IMessage      
   3:  {
   4:      public string MyMessage
   5:      {
   6:          get { return "Hello, Extensible World"; }
   7:      }
   8:   
   9:      ...
  10:  }

Lastly, we need to update the code in our Program class by changing three things.  We need to change the Message property to be of type IMessage instead of string.  We should also change the Import() decoration on our property to explicitly expect the IMessage type. And finally, we need to change our output to Message.MyMessage due to the type change.  The updated Program.cs code is shown below.

   1:  using System;
   2:  using System.ComponentModel.Composition;
   3:  using System.ComponentModel.Composition.Hosting;
   4:  using System.Reflection;
   5:   
   6:  namespace MEFExample2
   7:  {
   8:      public class Program
   9:      {
  10:          [Import(typeof(IMessage))]
  11:          IMessage Message { get; set; }
  12:   
  13:          static void Main(string[] args)
  14:          {
  15:              Program p = new Program();
  16:              p.Run();
  17:          }
  18:   
  19:          void Run()
  20:          {
  21:              Compose();
  22:              Console.WriteLine(Message.MyMessage);
  23:              Console.ReadKey();
  24:          }
  25:   
  26:          private void Compose()
  27:          {
  28:              var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  29:              var container = new CompositionContainer(catalog);
  30:              container.ComposeParts(this);
  31:          }
  32:      }
  33:  }

Now when we run the application, the mappings are still correct and we now have a type specific mapping based on an interface.  In addition to explicitly declaring the type of the mapping, we can also add a label to removing any additional ambiguity if we have multiple classes that implement our IMessage interface but only want to import a specific one.  Though, what if we DID want to import more than one instance of classes that implement our IMessage interface?  Thankfully, MEF has a way for that as well.

Adding a Second Message with ImportMany()

If you can remember the beginning of this specific post, we took our SimpleMessage class and added a second string property which caused our mappings to break since MEF didn't know which string property to map to our then, Program.Message string property. With this situation we addressed it through the use of explicitly declaring the mapping through labels and type abstractions; however, there is another way.  Building on the updates from the last section, what if we were to create a new class that implements our IMessage interface and decorates itself with the Export() attribute?  Let's add a new class to our project called SecondMessage.cs and implement that scenario.  Below is the SecondMessage.cs code:

   1:  using System.ComponentModel.Composition;
   2:   
   3:  namespace MEFExample2
   4:  {
   5:      [Export(typeof(IMessage))]
   6:      public class SecondMessage : IMessage
   7:      {
   8:          public string MyMessage
   9:          {
  10:              get { return "Hello, From a Second Class"; }
  11:          }
  12:      }
  13:  }

When we attempt to run the application, we get the same ChangeRejectedExcemption that we did when we had 2 string exports before.  Instead of doing all of the explicit stuff we did previously, let's change our expectations of the number of objects we're importing into our Program.Message property.  MEF addresses this issue by providing an ImportMany() attribute. ImportMany() allows MEF to map many different objects that conform to the contract (label and/or type) to a single property, where as Import() is only a singular mapping.  Since we are now working with multiple instances of IMessage though, we need to update our property to be a collection, as well as update our output to loop through the results.  After applying these updates, Program.cs should resemble the below code.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.Composition;
   4:  using System.ComponentModel.Composition.Hosting;
   5:  using System.Reflection;
   6:   
   7:  namespace MEFExample2
   8:  {
   9:      public class Program
  10:      {
  11:          [ImportMany(typeof(IMessage))]
  12:          IEnumerable<IMessage> Messages { get; set; }
  13:   
  14:          static void Main(string[] args)
  15:          {
  16:              Program p = new Program();
  17:              p.Run();
  18:          }
  19:   
  20:          void Run()
  21:          {
  22:              Compose();
  23:   
  24:              foreach(IMessage message in Messages)
  25:              {
  26:                  Console.WriteLine(message.MyMessage);
  27:              }
  28:   
  29:              Console.ReadKey();
  30:          }
  31:   
  32:          private void Compose()
  33:          {
  34:              var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  35:              var container = new CompositionContainer(catalog);
  36:              container.ComposeParts(this);
  37:          }
  38:      }
  39:  }

Running our application after these latest updates, we see that we get both messages as we expected.  In the next post, we'll take a quick look at how we can manage this need for looping by using LINQ; however, for now,we can see how to we're able to better manage our mappings using Imports and Exports of types and/or labels as well as working with multiple imports.

Resources


kick it on DotNetKicks.comShout it

Thursday, November 12, 2009

Working with the Managed Extensibility Framework (TOC)

This post is simply a table of contents for posts that I'm creating to help others learn about the Managed Extensibility Framework (MEF).  As new posts get published, I'll be updating the links to this page.  In addition, as I work more with MEF, I'll be adding more entries to this series to hopefully cover a wide range of scenarios while using such.

  1. An Introduction to MEF
  2. A Deeper Look at MEF's Imports and Exports
  3. LINQing to MEF Imports
  4. Playing Nice with Other Assemblies using MEF Catalogs
  5. Looking Around at Circular References in MEF
  6. Managing Composition Through Lazy Loading Parts
  7. Using MEF and Custom Configuration Sections
  8. A Configurable Type Catalog for MEF
  9. Making Part Declarations Easier with InheritedExports
  10. Hiding Parts From InheritedExport
  11. Using MEF with Signed Part Assemblies