次の方法で共有


Simple Introduction to Extensible Applications with the Managed Extensions Framework

Recently my team has been working on the Managed Extensions Framework (MEF)... I have gotten a chance to explain the concept to folks and I think I have discovered a way to talk about MEF that folks can easily get.  So I thought I'd spend a little time walking through a *very* simple MEF example as a way to introduce folks to the power of extensible applications in general, and MEF in particular. 

BTW, you can download the current MEF CTP  and the final working sample.

Background

Let's start with the most simple example: Hello World! 

    1: using System;
    2:  
    3: class Program
    4: {
    5:     public void Run()
    6:     {
    7:         Console.WriteLine("Hello World!");
    8:         Console.ReadKey();
    9:     }
   10:     static void Main(string[] args)
   11:     {
   12:         Program p = new Program();
   13:         p.Run();
   14:     }
   15: }

Now, you might now always want to print the same string, so let's refactor slightly to pull the string out.. 

    1: public string Message { get; set; }
    2:  
    3: public void Run()
    4: {
    5:     Console.WriteLine(Message);
    6:     Console.ReadKey();
    7: }

This looks nice, now we need to add the message... well, the actual text is a separate concern, as such it should be in a different class.    Such as:

    1: public class SimpleHello 
    2: {
    3:     public string Message
    4:     {
    5:         get
    6:         {
    7:             return "hello world!!";
    8:         }
    9:     }
   10: }

Now we simply need wire these up:

    1: public void Run()
    2: {
    3:     SimpleHello hello = new SimpleHello();
    4:     Message = hello.Message;
    5:  
    6:     Console.WriteLine(Message);
    7:     Console.ReadKey();
    8: }

This works, but something looks odd about line 3 and 4... We have introduced tight coupling back.. What we really want to do is externalize lines 3 and 4, so they can be controlled without effecting the rest of the logic of the program. 

 

Enter MEF

Add a Reference to the System.ComponentModel.Composition.dll assembly found in the bin directory of the MEF zip. 

Add

    1: using System.ComponentModel.Composition;

Now in the Program class, we need to import a value for Message -- that is, we want to specify that someone outside of this program needs to supply a message.  Then we need to remove our tight coupling.    Note in line 4-5 and we saying we want to import the value for Message.  Here I am showing doing it by type (string).. because basic types such as strings might be pretty common, consider using a named import such as [Import("Message")]

    1: class Program
    2: {
    3:  
    4:     [Import]
    5:     public string Message { get; set; }
    6:  
    7:     public void Run()
    8:     {
    9:        // SimpleHello hello = new SimpleHello();
   10:         //Message = hello.Message;
   11:  
   12:         Console.WriteLine(Message);
   13:         Console.ReadKey();
   14:     }

Now in the SimpleHello class we need to export the Message property.  This tells the system it can be used to satisfy requirements.    Notice line 3 and 4 and am marking it with an Export attribute..  Again, this exports it by type (string in this case).  As with above, you might want to do it with an explicit name for a more real world example [Export("Message")]

    1: public class SimpleHello 
    2: {
    3:     [Export]
    4:     public string Message
    5:     {
    6:         get
    7:         {
    8:             return "hello world!!";
    9:         }
   10:     }
   11: }

Now we need to tell MEF to wire these up for us.

    1: public void Run()
    2: {
    3:     //SimpleHello hello = new SimpleHello();
    4:     //Message = hello.Message;
    5:     var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
    6:     var container = new CompositionContainer(catalog.CreateResolver());
    7:     container.AddPart(this);
    8:     container.Compose();
    9:  
   10:  
   11:     Console.WriteLine(Message);
   12:     Console.ReadKey();
   13: }

In line 5, we create a catalog -- that tells MEF where to look for imports and exports.  In this case, we are saying the currently running assembly.  There are tons of different parts catalogs, we will look at some later and you can of course build your own.

in line 6, we create a Composition container-- this is effectively the soup that all the different parts will be wired up together.

In line 7, we add this instance of Program to the container so that its dependencies get wired up.

In line 8, we do the compose magic.  this is where the Message property of Program gets set. 

Notice, in this case the wire up is done by matching types (String to String)... clearly that isn't always the right way, we will look at other ways to wire up later.

Run it and you get the expected output of "hello world!".

Now let's add another message, just to be a little more fun... 

    1: public class MoreMessages
    2: {
    3:     [Export]
    4:     public string FunMessage
    5:     {
    6:         get
    7:         {
    8:             return "This is getting fun!";
    9:         }
   10:     }
   11: }

Now run.. It blows up!  Why?  Well, let's look at the exception:

System.ComponentModel.Composition.CompositionException Error : Multiple exports were found that match the constraint '(composableItem.ContractName = \"System.String\")'. The import for this contract requires a single export only."

From the error it looks like we provided too many ways to satisfy the Import... MEF didn't know which one to pick.  Of course you can programmatically get in there and help, you can also just remove the export from one of the messages... but more fun, you can actually tell MEF you are able to deal with zero or more results.  Change the Message property of Program as follows:

    1: [Import]
    2: public IEnumerable<string> Messages { get; set; }

Notice we changed the return type to be a collection of strings rather than just one string.

Now change the usage code slightly and we get:

    1: class Program
    2: {
    3:  
    4:     [Import]
    5:     public IEnumerable<string> Messages { get; set; }
    6:  
    7:     public void Run()
    8:     {
    9:         //SimpleHello hello = new SimpleHello();
   10:         //Message = hello.Message;
   11:         var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
   12:         var container = new CompositionContainer(catalog.CreateResolver());
   13:         container.AddPart(this);
   14:         container.Compose();
   15:  
   16:         foreach (var s in Messages)
   17:         {
   18:             Console.WriteLine(s);
   19:         }
   20:  
   21:     
   22:         Console.ReadKey();
   23:     }

image

Wow -- we get both messages!  Pretty cool...

More Value of MEF

Ok, I think we can all agree that we added a little complexity if all we were going to do is factor what was in the same assembly. MEF really shines when you have separate independent groups working on different components.  By definition these are often in different assemblies with cross no dependencies.  To show how MEF supports this, let's add a new Class Library project to our solution.    Call it ExternalMessages and add a reference to the System.ComponentModel.Composition.dll assembly.

Add the following class.

    1: using System;
    2: using System.ComponentModel.Composition;
    3:  
    4: public class Class1
    5: {
    6:     [Export]
    7:     public string Message
    8:     {
    9:         get
   10:         {
   11:             return "I am starting to get it...";
   12:         }
   13:     }
   14: }

Now we need to wire up this class into the catalog...    Notice in line 6, we change the catalog to look in a directory for the parts... 

    1: public void Run()
    2:  {
    3:      //SimpleHello hello = new SimpleHello();
    4:      //Message = hello.Message;
    5:      var catalog = new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug");
    6:          // new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());
    7:      var container = new CompositionContainer(catalog.CreateResolver());
    8:      container.AddPart(this);
    9:      container.Compose();
   10:  
   11:      foreach (var s in Messages)
   12:      {
   13:          Console.WriteLine(s);
   14:      }
   15:  
   16:  
   17:      Console.ReadKey();
   18:  }

Note: DirectoryPartCatalog also supports relative paths that will look for a path under the current AppDomain.CurrentDomain.BaseDirectory.  For example:
new DirectoryPartCatalog(@”.\extensions\”);

Run it and we get our new message! 

Cool, but we lost our old messages, and I kind of liked them too...  Well, luckily, we have an aggregate part catalog that can take parts from several sources.

    1: public void Run()
    2:  {
    3:      //SimpleHello hello = new SimpleHello();
    4:      //Message = hello.Message;
    5:      var catalog = new AggregatingComposablePartCatalog();
    6:         catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
    7:         catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
    8:      var container = new CompositionContainer(catalog.CreateResolver());
    9:      container.AddPart(this);
   10:      container.Compose();

Pretty cool, we now get all the messages!

image

Finally, just to bring up the point here... I created a bunch of different assemblies that exported Messages... All I need to do is point the catalog at them and go. 

image

    1: public void Run()
    2: {
    3:     //SimpleHello hello = new SimpleHello();
    4:     //Message = hello.Message;
    5:     var catalog = new AggregatingComposablePartCatalog();
    6:        catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
    7:        catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
    8:        catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
    9:     var container = new CompositionContainer(catalog.CreateResolver());
   10:     container.AddPart(this);
   11:     container.Compose();
   12:  

See where I added them in line 7..    Now just copy the assemblies into this directory and they become available for this program to use! Notice how I don't need to change any of the logic of the core program as I add more and more extensions. 

image

Taking MEF to the Next Level

Above I showed the simplest scenario... let's get a bit more powerful.  If you pick apart the main program looking for tight coupling, that Console.WriteLine() will really stand out.. What if you want to log to a file, call a web service or print to HTML or WPF?   Tightly coupling to the Console does not make that easy.  How can we use the seperation of concerns principle and MEF to get rid of this tight coupling? 

First, we need to define an interface that describes the contract for outputting strings.  To ensure correct dependency management go ahead and create a new Library project called SharedLibrary, add this interface and reference this project from each of the other team projects. 

    1: namespace SharedLibrary
    2: {
    3:     public interface IOutputString
    4:     {
    5:         void OutputStringToConsole(string value);
    6:     }
    7: }

Now, back in the main program we can factor out the Console.WriteLine ()...

    1: class Program
    2: {
    3:     [Import]
    4:     public IEnumerable<string> Messages { get; set; }
    5:  
    6:     [Import]
    7:     public IOutputString Out { get; set; }
    8:  
    9:     public void Run()
   10:     {
   11:         //SimpleHello hello = new SimpleHello();
   12:         //Message = hello.Message;
   13:         var catalog = new AggregatingComposablePartCatalog();
   14:            catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
   15:            catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
   16:            catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
   17:         var container = new CompositionContainer(catalog.CreateResolver());
   18:         container.AddPart(this);
   19:         container.Compose();
   20:  
   21:         foreach (var s in Messages)
   22:         {
   23:             Out.OutputString(s);
   24:         }
   25:  
   26:     
   27:         Console.ReadKey();
   28:     }

In line 6-7 we define Out and in line 23 we change from Console.WriteLine() to Out.OutputString().

Now in External Messages project add the following class

    1: [Export(typeof(IOutputString))]
    2: public class Class1 : IOutputString
    3: {
    4:     public void OutputString(string value)
    5:     {
    6:         Console.WriteLine("Output=" + value);
    7:     }
    8:  
    9:     

Notice here we are explicitly saying the expert type to be that shared interface.  Now when we run it we get:

image

To make things more interesting, lets add another implementation of IOutputString that is a bit more creative.

    1: [Export(typeof(IOutputString))]
    2: public class ReverseOutputter : IOutputString
    3: {
    4:  
    5:     public void OutputString(string value)
    6:     {
    7:         foreach (var s in value.Split().Reverse())
    8:         {
    9:             Console.ForegroundColor = (ConsoleColor)(s.Length % 10);
   10:             Console.Write(s + " ");
   11:         }
   12:         Console.WriteLine();
   13:     }
   14: }

Just running this now would give us an error right, because we told MEF we wanted exactly one IOutputString... if we change our code to work with multiple we get more fun!  In line 7 we changed to request a set of IOutputStrings and in line 19 we changed to loop through all the output devices. 

    1: class Program
    2: {
    3:     [Import]
    4:     public IEnumerable<string> Messages { get; set; }
    5:  
    6:     [Import]
    7:     public IEnumerable<IOutputString> OutputSet { get; set; }
    8:  
    9:     public void Run()
   10:     {
   11:         var catalog = new AggregatingComposablePartCatalog();
   12:            catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
   13:            catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
   14:            catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
   15:         var container = new CompositionContainer(catalog.CreateResolver());
   16:         container.AddPart(this);
   17:         container.Compose();
   18:  
   19:         foreach (var Out in OutputSet)
   20:         {
   21:             foreach (var s in Messages)
   22:             {
   23:                 Out.OutputString(s);
   24:             }
   25:         }
   26:  
   27:     
   28:         Console.ReadKey();
   29:     }

Now when we run it we get all our messages in all our outputs..

image

Now the only real logic in our main is the nested foreach loops.  We may very well like to change that in the future as well... so let's see if we can abstract that out using exactly the same techniques we have talked about already. 

    1: class Program
    2: {
    3:     [Import]
    4:     public IEnumerable<string> Messages { get; set; }
    5:  
    6:     [Import]
    7:     public IEnumerable<IOutputString> OutputSet { get; set; }
    8:  
    9:     [Import("OutputMessages")]
   10:     public Action<IEnumerable<IOutputString>, IEnumerable<string>> OutputMessages { get; set; }
   11:  
   12:     public void Run()
   13:     {
   14:         var catalog = new AggregatingComposablePartCatalog();
   15:            catalog.Catalogs.Add (new DirectoryPartCatalog(@"..\..\..\ExternalMessages\bin\Debug"));
   16:            catalog.Catalogs.Add(new DirectoryPartCatalog(@"..\..\..\ExtraMessages"));
   17:            catalog.Catalogs.Add (new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
   18:         var container = new CompositionContainer(catalog.CreateResolver());
   19:         container.AddPart(this);
   20:         container.Compose();
   21:  
   22:  
   23:         OutputMessages(OutputSet, Messages); 
   24:         
   25:     }

First, in line 9-10 we define an Action that can do the output... and in line 23 we remove our nested foreach loops and replace it with a call to this method.  Now all we have in our Main is effectively wiring up components.  Very loosely coupled!

Now let's add an option for how the messages are outputted.   It would be very easy to define a config file option for selecting these and returning the correct one.  Notice that MEF knows that this method follows the contract of the Action delegate above, so it does all the wireup for us. 

    1: [Export("OutputMessages")]
    2: public void OutputByMessage(IEnumerable<IOutputString> outputSet, IEnumerable<string> messages)
    3: {
    4:     foreach (var s in messages)
    5:     {
    6:         foreach (var Out in outputSet)   
    7:         {
    8:             Out.OutputString(s);
    9:         }
   10:     }
   11:     Console.ReadKey();
   12: }

I would love to hear what you think?  Can you think of usage for MEF in your applications?    I'd also love for you to grab the final sample and see how many different ways you can extend it..  Here is the final working sample.  Enjoy!

Comments

  • Anonymous
    September 29, 2008
    PingBack from http://blog.a-foton.ru/index.php/2008/09/29/simple-introduction-to-extensible-applications-with-the-managed-extensions-framework/

  • Anonymous
    September 29, 2008
    Very nice tutorial. How about an option to dynamically bind.  So if you drop a new DLL in the directory it would be added into the container and bound at runtime - without having to restart. Also like to see some options for synchronous/asynchronous calling patterns in outgoing interfaces where there's more than one target.  Maybe an opportunity to incorporate WF.

  • Anonymous
    September 29, 2008
    Hey Joe, regarding dynamically binding... while Brad didn't show it here, it is supported in the current methods via "rebinding." The reason the app needs to be restarted here in Brad's demo is he outputs the messages and then effectively ends the app.

  • Anonymous
    September 29, 2008
    I'd love to see one of these frameworks move the linking logic into a much lower level in the type resolution call stack.   This is great and all, but what happens when a type is resolved from within code you can't control?  For instance, during xaml or xml deserialization.  You can't rewrite the serializers to use the MEF to identify, load and compose types that aren't known at compile time or within any loaded assemblies....

  • Anonymous
    September 29, 2008
    The comment has been removed

  • Anonymous
    September 29, 2008
    I have a few questions about MEF: What about System.Addin? Couldn´t that be done with it? What is the difference between MEF and System.Addin?

  • Anonymous
    September 29, 2008
    Thanks Jason. Also I would ask how this works with start-up configuration - like IoC containers.

  • Anonymous
    September 29, 2008
    I have searched the NET and I should say I have not come across an article like this which is so easy to understand and learn the concepts. cheers

  • Anonymous
    September 29, 2008
    @Robert, Krys has a good article on this here. http://blogs.msdn.com/kcwalina/archive/2008/06/13/MAFMEF.aspx The bits that he wrote the article based on are old, however the principle still stands. MAF is primarily focused on add-in activation and isolation. MEF is concerned with discovery and compostion of clr-types, which includes types activated through MAF. There is some overlap though, however in the places where they overlap, the functionality supports the primary goals of each framework.

  • Anonymous
    September 29, 2008
    The past few weeks, the momentum has picked up around samples using MEF. In my last post I mentioned

  • Anonymous
    September 29, 2008
    Brad Abrams has a post on his blog Simple Introduction to Extensible Applications with the Managed Extensions

  • Anonymous
    September 29, 2008
    Brad Abrams can always be relied upon for a great post - and the latest whizzy thing to come from Microsoft

  • Anonymous
    September 29, 2008
    Brad Abrams can always be relied upon for a great post - and the latest whizzy thing to come from Microsoft

  • Anonymous
    September 29, 2008
    This is great introduction. There might be a small typo - IOutputString interface should contain OutputString() instead of OutputStringToConsole().

  • Anonymous
    September 29, 2008
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    September 30, 2008
    Excellent KISS approach. I have only one doubt, The second example looks like the export happens by type rather than the property. This would mean any dll loaded by a catalog if it has a exported string it will show that as a message. The other thing is what someone had already said, this whole idea of loading dlls by reflection should happen at a very low level. There are other issues with the MEF approach and calls for enhancements. For example

  1. securing a class to be loaded based on authentication,
  2. getting the other dlls that are used by the one loaded is another I can think of.
  3. Loading dlls from different sources.
  • Anonymous
    September 30, 2008
    The feed item for this is borked and loses formatting.

  • Anonymous
    September 30, 2008
    The comment has been removed

  • Anonymous
    September 30, 2008
    In the example under Taking MEF to next level, the Interface is well known to the application as well as the MEF'd dll. This would mean that a dynamic loading will only be possible for those dlls that support that interface. So from that example it looks like, an application implementing MEF should be well aware of what interface(s) it is going to implement. hmmmm.,,,,,,

  • Anonymous
    September 30, 2008
    How do AppDomains fit into this? Or do they at all?

  • Anonymous
    September 30, 2008
    The comment has been removed

  • Anonymous
    September 30, 2008
    The comment has been removed

  • Anonymous
    September 30, 2008
    The comment has been removed

  • Anonymous
    September 30, 2008
    Excellent Brad, Simple & Effective. The last example was more interesting. Thanks Jomit

  • Anonymous
    October 01, 2008
    Simple Introduction to Extensible Applications with the Managed Extensions Framework

  • Anonymous
    October 01, 2008
    Hi Glenn, Many thanks for the response. The 'syntactic sugar' you mention is exactly the sort of thing that sounds very useful, especially if it shields us mere mortals from having to do a lot of the plumbing for simple scenarios. It would be fantastic if you could share some of the API mock-ups with us - I'm passionate about this after reading Brad's FDG book ;)

  • Anonymous
    October 03, 2008
    【原文地址】 Simple Introduction to Extensible Applications with the Managed Extensions Framework 【原文发表日期】

  • Anonymous
    October 06, 2008
    Excellent blog Brad - can you tell me where you think MEF fits with Unity, replace or complement? Thanks

  • Anonymous
    October 07, 2008
    I would love to see another article under the same writing approach that compares MEF to System.AddIn (as one other person commented) and/or any generic IoC container like StructureMap, Unity, Castle Windsor... What are the overlaps, dependencies, differences?

  • Anonymous
    October 07, 2008
    hi there, also I've talked a little bit in this blog on practical writing of Add-in-based applications,

  • Anonymous
    October 08, 2008
    The comment has been removed

  • Anonymous
    October 11, 2008
    MEF stands for &quot; M anaged E xtensions F ramework&quot;. The idea behinds it is to allow reuse and

  • Anonymous
    November 06, 2008
    The Managed Extensibility Framework (MEF) is a new feature of .NET 4 (and will work on 3.5 as well) that

  • Anonymous
    November 07, 2008
    The Managed Extensibility Framework (MEF) is a new feature of .NET 4 (and will work on 3.5 as well) that

  • Anonymous
    November 27, 2008
    Good programmers have been implementing similar approaches to this since like... forever! Not that I don't like it, I think it would be great if this becomes mainstream in the .NET world having a common way of implementing plug-in/add-in/extentending architecture. But, I have a question, what if the class with the color text output that came later could apply the same behaviour to the earlier monochrome ones. How hard would that be to implement?