Wow, long time no blog :). Busy at work, hehehe.
MEF is a new (to be released as part of .NET Framework 4.0) open source extensibility framework. You can find the latest drop at this CodePlex site.
The easiest way to explain MEF is to think about it as a combination of a plug-in and dependency injection container subsystem for your application. Hopefully you already know what a plug-in subsystem and dependency injection container are. If not, well, you can get up-to-date on these topics quickly here and here. Also, good resources for these are dnrTV! episodes #34 (on plug-in architecture) , #126 (on Dependency Injection Container), and of course #130 (on MEF itself). They are a bit bulky, but if you can spare the bandwidth and time, they are worth it.
One demo from PDC 2008 by Scott Guthrie that I found fascinating is when he dropped a new extension assembly (dll) into a directory of Visual Studio 2010 (that will be using MEF for its extensibility) and then he switched back to Visual Studio, the extension was recognized immediately, loaded and operating right away without having to reload Visual Studio.
I tried scrounging the web to find a sample source code on how to do just this with MEF, but I just couldn't find a ready made one. After doing some readings, I know I need to use the DirectoryPartCatalog and have it watch the directory where the new extension DLL will be dropped into and I also know that I need to add AllowRecomposition = true to my Import attribute. And so, armed with these, I started experimenting. Attached to this blog post is the sample code that do just that (dynamically add extension / plug-in to a running application and have it recognized right away and available for usage).
Just so you understand what I was thinking, I'll walk you through the solution.
I have a solution containing 4 projects.
Greeter.Core is just a shared contract library that will be used by the other 3 projects. EnglishGreeter and Indonesian Greeter are extension libraries that implement the contract (namedly the ITimeGreeter interface) from Greeter.Core project.
namespace Greeter.Core
{ public interface ITimeGreeter
{ IPerson ContextPerson { get; set; } string MorningGreeting();
string AfternoonGreeting();
string EveningGreeting();
}
}
1: using System.ComponentModel.Composition;
2: using Greeter.Core;
3:
4: namespace EnglishGreeter
5: { 6: [Export(typeof(ITimeGreeter))]
7: public class EnglishGreeter : ITimeGreeter
8: { 9: [Import]
10: public IPerson ContextPerson { get; set; } 11:
12: public string MorningGreeting()
13: { 14: return string.Format("Good morning, {0}.", ContextPerson.FullName); 15: }
16:
17: public string AfternoonGreeting()
18: { 19: return string.Format("Good afternoon, {0}.", ContextPerson.FullName); 20: }
21:
22: public string EveningGreeting()
23: { 24: return string.Format("Good evening, {0}.", ContextPerson.FullName); 25: }
26: }
27: }
You'll need to reference MEF's System.ComponentModel.Composition.dll and the Greeter.Core project in your extension projects. Note that I'm decorating line 6 and 9 with Export and Import attributes from MEF. These are necessary (for now) for MEF to recognize what components / parts are needed and what components are provided by this particular component. For example, line 6 basically said, "Hey, whoever needs an ITimeGreeter contract implementation, I can provide you with one" and line 9 basically said, "For me to be able to work properly, I will need an IPerson (see line 10) contract implementation). Also notice that nowhere in this class I am providing a concrete implementation of IPerson (either in the class constructor, which is non-existent) or anywhere else. You might think that I'm going to assign it from somewhere else later, but you'll see that I'm actually not doing this anywhere explicitly. MEF will take care of this for me, which is cool :).
I won't go through the IndonesianGreeter class since it's basically a replica of the EnglishGreeter one with Indonesian wordings.
So, finally, we have the ConsoleGreeter project, which is a Console application that will use MEF to discover any ITimeGreeter contract implementations and use them. You'll need to reference MEF's System.ComponentModel.Composition.dll and Greeter.Core project as well in this project.
I also implemented an IPerson contract from Greeter.Core in this project. I know it doesn't really make sense to do this in this way, but I want to show MEF import / export and automatic dependency injection capabilities. So, please bear with me on this one. You'll see how John Doe (the instantiated Person object will be available to the extensions library without having to wire this manually in the ConsoleGreeter application.
namespace Greeter.Core
{ public interface IPerson
{ string FirstName { get; set; } string LastName { get; set; } string FullName { get; } }
}
using System.ComponentModel.Composition;
using Greeter.Core;
namespace ConsoleGreeter
{ [Export(typeof(IPerson))]
public class Person : IPerson
{ public string FirstName { get; set; } public string LastName { get; set; }
public Person()
{ FirstName = "John";
LastName = "Doe";
}
public string FullName
{ get { return FirstName + " " + LastName; } }
}
}
Next, let's take a look at the real "extensible shell" which is the Program class in ConsoleGreeter project...
1: using System;
2: using System.IO;
3: using System.Reflection;
4: using System.Collections.Generic;
5: using System.ComponentModel.Composition;
6: using Greeter.Core;
7:
8: namespace ConsoleGreeter
9: { 10: [Export]
11: public class Program
12: { 13: private static CompositionContainer container;
14:
15: [Import]
16: public IPerson Person { get; set; } 17:
18: [Import(AllowRecomposition = true)]
19: public IEnumerable<ITimeGreeter> Greeters { get; set; } 20:
21: static void Main()
22: { 23: Compose();
24:
25: var program = container.GetExportedObject<Program>();
26: program.Run();
27: }
28:
29: private static void Compose()
30: { 31: var catalog = new AggregatingComposablePartCatalog();
32:
33: catalog.Catalogs.Add(new DirectoryPartCatalog(
34: Path.Combine(Environment.CurrentDirectory, "Extensions"), true));
35: catalog.Catalogs.Add(new AttributedAssemblyPartCatalog(
36: Assembly.GetExecutingAssembly()));
37:
38: container = new CompositionContainer(catalog);
39: container.Compose();
40: }
41:
42: private void Run()
43: { 44: Console.WriteLine("Person to greet: {0}", Person.FullName); 45:
46: while (true)
47: { 48: foreach (var greeter in Greeters)
49: { 50: Console.WriteLine(greeter.MorningGreeting());
51: Console.WriteLine(greeter.AfternoonGreeting());
52: Console.WriteLine(greeter.EveningGreeting());
53: }
54:
55: Console.WriteLine("Press any key to continue or Esc to quit..."); 56:
57: if (Console.ReadKey().Key == ConsoleKey.Escape)
58: break;
59: }
60: }
61: }
62: }
Note that I'm Exporting the Program class itself in line 10 and then I'm asking MEF container to provide me with an instance of the Program class in line 25. The heavy lifting in MEF is done in the Compose method in line 29 - 40. In the Compose method, I am creating a composite catalog (line 31) which allow me to add more catalogs into it, which I did in line 33 and 35.
In line 33 I'm adding the DirectoryPartCatalog that interest me for the component file drag and drop functionality like the one ScottGu did. Basically, the DirectoryPartCatalog will monitor the Extensions subdirectory of the current application (well, not really, right now it's from wherever you are running this program from, might want to change it to the actual path of the program installation or better yet, a subfolder in the ApplicationData special folder, but for the sake of demo, ...). Note the true at the end of line 34. It is necessary to have this for the DirectoryPartCatalog to watch for any new component being dropped into the Extensions folder. Also, note the Import attribute on line 18. I added AllowRecomposition = true to it so the dynamic recomposition behavior will take place when adding new ITimeGreeter implementation components to Extensions folder.
OK, now, to see the dynamic detection and addition of the new component, you can do the following:
1. Compile ConsoleGreeter project (debug configuration will do just fine).
2. Browse to wherever the debug bin folder of ConsoleGreeter and create an Extensions folder in there if it's not there already.
3. Make sure Extensions folder is empty. If there is any file in there, just delete them (make sure the application is not running while you are doing this).
4. Compile EnglishGreeter project and copy the bin (debug / release) folder content to the Extensions folder in step 3.
5. Compile IndonesionGreeter project but don't copy it just yet.
6. Run ConsoleGreeter from the bin folder.
7. Note that without specifying any IPerson implementation and ITimeGreeter implementation, the program ran just fine (MEF detects that Program needs a Person object in line 16) and One or more ITimeGreeter implementation in line 19 and create, load and inject the necessary objects into Program automatically.) At the moment, you should see an output like so:
Person to greet: John Doe
Good morning, John Doe.
Good afternoon, John Doe.
Good evening, John Doe.
Press any key to continue or Esc to quit...
8. Now, copy the IndonesianGreeter bin folder content to Extensions folder and once everything is copied, go back to the already running ConsoleGreeter application and press any key (not Escape). And you should see something like:
Good morning, John Doe.
Good afternoon, John Doe.
Good evening, John Doe.
Selamat Pagi, John Doe
Selamat siang, John Doe
Selamat malam, John Doe
Press any key to continue or Esc to quit...
As you can see, MEF dynamically recognize that a new extension component was added to the Extensions folder and have it loaded and ready to go. This is very neat!
The only caveat at the moment is, you can't recompiled / update an already existing component in the Extensions folder unless you have shadow copy turned on.
You'll need to grab MEF Preview 3 Release from the site to compile this example. I'm also using some .NET 3.5 syntaxes in the solution, so you might want to adjust them if you are going to compile this in VS2005. Mostly, I'm using var, so not a big deal to change those back to the actual types.
Happy MEF-ing.