Freigeben über


MEF

The Managed Extensibility Framework (MEF) is a new library, still under development, that provides support for composing applications dynamically. Many applications have a composite model, where the total functionality is provided by a number of component parts. Often, these components are all known at design-time, so the composition is static. However, it is sometimes useful to be able to build the composition dynamically – where the set of components to be loaded is only discovered at runtime. Office client apps are composite apps in the sense that they discover and load add-ins dynamically – although of course Office apps do not use MEF. A preview of the MEF code, documentation and samples are available on codeplex here.

Let’s consider a simple example. A console app wants to invoke functionality that can be implemented in components discovered at runtime. There must be no static reference to the components within the app. To provide this indirection, the app needs to communicate with these components via an interface that they both recognize. The component implements this interface, and is then deployed in such a way that the app can discover it at runtime. Here’s the interface:

public interface ICalculate

{

    double Circumference(double radius);

}

The idea of using interfaces to communicate between components that can be built independently is not new, but the MEF framework simplifies the discovery and composition mechanics. I can create a simple class library project (which I shall call “Interface”) that contains just this interface. MEF components (or “composable parts”) do not directly depend on one another, instead they depend on a contract, which is a string identifier. It is reasonable to specify a contract via an interface, to ensure separation. So, I can implement this interface in another class library (which I’ll call “Implementation”):

[Export(typeof(Interface.ICalculate))]

public class Calculate : Interface.ICalculate

{

    public double Circumference(double radius)

    {

        return 3.14159 * radius * 2;

    }

}

Note the use of the Export attribute. This is defined in the assembly System.ComponentModel.Composition.dll, which is released as part of the preview of the MEF library on codeplex. Effectively, this specifies that I’m exporting the type identified by the string “Interface.ICalculate”.

Finally, I can create a console application project which specifies the contract it wants to use (via the Import attribute), uses MEF to discover and compose the available components, and then invokes the implemented contract:

class Program

{

    [Import]

    public Interface.ICalculate Calculate { get; set; }

    static void Main(string[] args)

    {

        Program p = new Program();

        p.Run();

        Console.ReadKey();

    }

    private void Run()

    {

        //AssemblyCatalog catalog =

        // new AssemblyCatalog(Assembly.Load(

        // "Implementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"));

        DirectoryCatalog catalog =

            new DirectoryCatalog(

                AppDomain.CurrentDomain.BaseDirectory);

        CompositionContainer container =

    new CompositionContainer(catalog);

        CompositionBatch batch = new CompositionBatch();

        batch.AddPart(this);

        container.Compose(batch);

        Console.WriteLine(

            String.Format("{0}", Calculate.Circumference(4)));

    }

}

The app uses a MEF catalog to load the assembly that implements the interface (‘exports the contract type’) that it wants to use. There are several different kinds of catalog. I’ve deliberately chosen not to use a simple AssemblyCatalog – because (as you can see from the commented code) that would require me to specify the assembly name for the assembly to be loaded – and I explicitly want to have zero knowledge of the implementing assembly (ie, no static reference) within the consuming application.

For this reason, I’m using a DirectoryCatalog, and pointing it at the folder where the executing assembly is. This means that the catalog will be able to use any assemblies it finds in this folder when it comes time to build the composition.

Next, I create a CompositionContainer, and initialize it with the DirectoryCatalog (and therefore, with all the assemblies that the DirectoryCatalog finds). I also add the current application object to the container, so that its dependencies (Import requirements) are included in the composition operation. Then, I can invoke the Compose method – this matches up all the Imports and Exports that have been collected in the container. Finally, I can invoke the contract method (“Circumference”) on the imported type, and I’m done.

So far, my composite app has been made up of 3 assemblies: the app itself, the interface assembly, and the implementation assembly. To further illustrate how MEF dynamic composition works, let’s suppose I have an alternative version of the implementation assembly (let’s call it “Alternate.dll”). You can imagine that this is produced by a different team, or perhaps bought in from a 3rd party vendor. Instead of the simple constant value for Pi that I used in my first implementation, this alternative implementation uses Math.PI:

[Export(typeof(Interface.ICalculate))]

public class Calculate : Interface.ICalculate

{

    public double Circumference(double radius)

    {

        return Math.PI * radius * 2;

    }

}

Note that this is not simply an updated version of the same assembly – it is a completely independent assembly, with a different assembly name. I can simply deploy this alternate version to the same folder as the consuming application. I do NOT need to rebuild the app, because it did not have any static reference to the implementation assembly – it only referred to the interface assembly, which has not changed.

At runtime, the catalog will pick up the alternate assembly, and the CompositionContainer will compose the composite app such that the consuming app code can transparently use the new version. Note that I cannot deploy both versions of the implementation assemblies to the same location – because the DirectoryCatalog will find them both, and then the CompositionContainer will barf if it finds 2 implementations of the same Export. So, when I deploy the alternate implementation, I must be careful to remove the original one.

You can see that this mechanism would in fact also work in the case of updated versions of the same assembly – although in that scenario the ‘dynamic discovery and composition’ feature is less obviously useful, because you’re more likely to know the assembly name at design-time.

As you can see, MEF is pretty simple, yet it enables a quite powerful dynamic composition model.

MefConsoleAppAlternate.zip

Comments

  • Anonymous
    April 02, 2009
    Nice to see you exploring MEF, that is the project I'm currently working on so please let me know if you have any questions or concerns.
  • Anonymous
    April 07, 2009
    In my last post , I looked briefly at MEF, and I’m wondering how this model can be applied to Office
  • Anonymous
    May 22, 2009
    Hi, thanks for your useful article.I am trying to learn MEF. I followed your article. I have three projects in my solution. all three have reference to System.ComponentModel.Composition.two classlibrary (MyInterface and MyExporter) and one Console App.but att batch.AddPart(this) it generates following error.The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.1) No exports were found that match the constraint '((exportDefinition.ContractName = "MyInterfaces.ICalculate") && (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") && "MyInterfaces.ICalculate".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))'.Resulting in: Cannot set import 'MyImporterConsole.Program.Calculate (ContractName="MyInterfaces.ICalculate")' on part 'MyImporterConsole.Program'.Element: MyImporterConsole.Program.Calculate (ContractName="MyInterfaces.ICalculate") --> MyImporterConsole.Program
  • Anonymous
    May 22, 2009
    Kourosh - it's impossible to tell what the problem might be. I suggest you compare your solution with the one I posted, to see if there are any differences.Also, you might be better off posting to the MEF forum on codeplex: http://www.codeplex.com/MEF. You will see there is a very active discussion forum there.