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;
   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:          }
  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;
   8:  namespace MEFExample3
   9:  {
  10:      public class Program
  11:      {
  12:          [ImportMany(typeof(IMessage))]
  13:          IEnumerable<IMessage> Messages { get; set; }
  15:          static void Main(string[] args)
  16:          {
  17:              Program p = new Program();
  18:              p.Run();
  19:          }
  21:          void Run()
  22:          {
  23:              Compose();
  25:              IMessage message = (Messages.Where(m => m.Name == "Simple")).FirstOrDefault();
  26:              Console.WriteLine(message.MyMessage);
  27:              Console.ReadKey();
  28:          }
  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.


kick it on DotNetKicks.comShout it

1 comment:

  1. I really love this series. Thanks for sharing.

    Just suggestion for the LINQ, FirstOrDefault can accept also func.

    var message = Messages.FirstOrDefault(m => m.Name == "Simple");