2.5. MEF

Managed Extensibility Framework (MEF) is a new framework for creating customizable applications that can be used by any .NET-compatible language. Glenn Block (PM for the new MEF in .NET 4.0) says the following:

Quite simply, MEF makes building extensible apps, libraries, and frameworks easy. It shares some common characteristics of other frameworks out there, but it also addresses a whole new set of problems that arise in building extremely large scalable extensible systems.

http://blogs.msdn.com/gblock/archive/2008/09/26/what-is-the-managed-extensibility-framework.aspx

Let's say you have created a Tetris application and want to allow users to extend it by creating their own shapes of bricks. MEF enables you to do this by defining a brick interface and then dynamically loading and resolving the created extensions.

When creating a MEF application, take the following steps:

  1. Define areas of the application that can be extended and decorate them with the [Import] attribute.

  2. Determine a contract/interface that defines what your extensions must do/be (this could be as simple as stating they must be of type String).

  3. Create an extension that meets these requirements and decorate it with the [Export] attribute.

  4. Modify your application to load these extensions.

2.5.1. Why Use MEF?

Using MEF has the following advantages:

  • Microsoft hopes that MEF will become the preferred standard method of creating extensions. By utilizing a standard plug-in model, your extensions could be used in many applications.

  • MEF provides a number of flexible ways to load your extensions.

  • Extensions can contain metadata to provide further information about their capabilities. For example, you may only want to load extensions that can communicate securely.

  • MEF is open source and works on VS2008 (www.codeplex.com/MEF).

BUT COULDN'T I ACCOMPLISH THIS WITH REFLECTION/DEPENDENCY INJECTION/IOC CONTAINERS/VOODOO?

There is overlap in the functionality provided by the technologies mentioned in this section and MEF. MEF and Inversion of Control (IOC) containers do have some overlap, and many people would classify MEF as an IOC container. MEF's primary purpose is, however, creating extensible applications through discovery and composition, whereas IOC containers are generally more focused on providing an abstraction for testing purposes. It's not a discussion I want to get into, but Oren Eini does, so please refer to http://ayende.com/Blog/archive/2008/09/25/the-managed-extensibility-framework.aspx.


2.5.2. Hello MEF

In this sample application, you will create two extensions that print out a message. You will then load them both into an IEnumerable<string> variable called Message before iterating through them and printing out the messages.

  1. Create a new console project and call it Chapter2.HelloMEF.

  2. Add a reference to System.ComponentModel.Composition.

  3. Add a new class called MEFTest.

  4. Add the following using statements to the class:

    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;

  5. Modify the MEFTest class code to the following (note how we decorate the Message property with the [Import] attribute):

    public class MEFTest
    {
        [Import]
        public string Message { get; set; }
    
        public void HelloMEF()
        {
            CompositionContainer container = new CompositionContainer();
            CompositionBatch batch = new CompositionBatch();
            batch.AddPart(new Extension1());
            batch.AddPart(this);
            container.Compose(batch);
    
            Console.WriteLine(Message);
    
            Console.ReadKey();
        }
    }

  6. We now need to create the extensions to load, so create a new class called Extension1, and add the following using statement:

    using System.ComponentModel.Composition;

  7. Amend Extension1.cs to the following:

    public class Extension1
    {
        [Export]
        public string Message
        {
            get
            {
                return "I am extension 1";
            }
        }
    }

  8. Finally, open Program.cs and add the following code:

    static void Main(string[] args)
    {
        MEFTest MEFTest = new MEFTest();
        MEFTest.HelloMEF();
    }

  9. Press F5 to run the application, and you should see that both extensions are loaded and the Message is property printed out, as Figure 2-14 shows.

    Figure 2.14. Output from HelloMEF application

Congratulations, you have created your first MEF application.

2.5.3. How Did This Example Work?

You started off by telling MEF that your Message property can be extended by marking it with the [Import] attribute. The [Import] attribute means "I can be extended" to MEF:

[Import]
public string Message { get; set; }

You then created an extension class and added the [Export] attribute. [Export] tells MEF "I am an extension":

class extension1
{
    [Export]
    public string Message
    {
        get
        {
            return "I am extension 1";
        }
    }
}

You then created a container (containers resolve MEF extensions when they are requested) to hold the extensions and added your extension classes to it using a CompositionBatch:

CompositionContainer container = new CompositionContainer();
CompositionBatch batch = new CompositionBatch();
batch.AddPart(new extension1());
batch.AddPart(this);

The Compose() method was then called, which caused MEF to load our extensions into the Message property:

container.Compose(batch);

MEF then loaded extensions into the Messages property decorated with the [Export] attribute that matched the contract. Finally, you printed out the message to the screen. In this example, you only loaded extensions contained within the project itself, which isn't too useful. Luckily MEF allows you to load extensions declared outside the project.

2.5.4. MEF Catalogs

MEF uses a concept called catalogs to contain extensions. Catalogs come in three different flavors:

  • Assembly: Extensions are contained in a .NET assembly.

  • Directory: Extensions are in a physical directory.

  • Aggregate: This is a catalog type that contains both assembly and directory extensions.

In this example you will use a directory catalog to load an extension defined outside the main project. Directory catalogs scan the target directory for compatible extensions when first created. You can rescan the directory by calling the Refresh() method.

  1. It is a good idea to declare MEF interfaces in a separate project to avoid circular reference issues and facilitate reuse, so open the existing Chapter2.HelloMEF project and add a new class library project called Chapter2.MEFInterfaces.

  2. Inside this project, create an interface called ILogger.

  3. Replace the existing code in ILogger.cs with the following code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Chapter2.MEFInterfaces
    {
        public interface ILogger
        {
            string WriteToLog(string Message);
        }
    
    }

  4. In the Chapter2.HelloMEF project, add a reference to the Chapter2.MEFInterfaces project.

  5. In the Chapter2.HelloMEF project, create a class called MoreUsefulMEF and enter the following code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;
    using System.IO;
    namespace Chapter2.HelloMEF
    {

    class MoreUsefulMEF
        {
            [Import]
            private Chapter2.MEFInterfaces.ILogger Logger;
    
            public void TestLoggers()
            {
                CompositionContainer container;
                DirectoryCatalog directoryCatalog =
                  new DirectoryCatalog(
                    (Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
                  );
    
                container = new CompositionContainer(directoryCatalog);
                CompositionBatch batch = new CompositionBatch();
                batch.AddPart(this);
                container.Compose(batch);
                Console.Write(Logger.WriteToLog("test"));
                Console.ReadKey();
            }
        }
    }

  6. Open Program.cs and amend the Main() method to the following:

    MoreUsefulMEF MoreUsefulMEF = new MoreUsefulMEF();
    MoreUsefulMEF.TestLoggers();

  7. You will now create a logging extension, so add a new class library project to the solution called Chapter2.EmailLogger.

  8. Add a reference to the Chapter2.MEFInterfaces project.

  9. Add a reference to System.ComponentModel.Composition.

  10. Add a new class called EmailLogger.

  11. Amend the code to the following:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.Composition;

    namespace Chapter2.EmailLogger
    {
        [Export(typeof(Chapter2.MEFInterfaces.ILogger))]
        public class EmailLogger : MEFInterfaces.ILogger
        {
            public string WriteToLog(string Message)
            {
                //Simulate email logging
                return "Email Logger Called";
            }
        }
    
    }

  12. When you use a directory catalog to load MEF components, you can either compile the Chapter2.EmailLogger project and copy the built assembly to Chapter2.HelloMEF's bin folder, or add a project reference in Chapter2.HelloMEF to the Chapter2.EmailLogger project.

  13. Once you have done this, press F5 to run the HelloMEF project. The Email Logger extension should then be loaded and "Email Logger Called" outputted to the screen.

2.5.5. Metadata

An important feature of MEF is that you can provide additional information about an extension's capabilities with metadata. MEF can then utilize this information to determine the most appropriate extension to load and query its capabilities. For example, in the previous logging example, you might specify whether the logging method is secure, and then in high-security environments only load extensions that communicated securely. Metadata can be defined at a class or method level.

To add metadata to a class, use the [PartMetaData] attribute:

[PartMetadata("secure", "false")]
[Export(typeof(Chapter2.MEFInterfaces.ILogger))]
public class EmailLogger : MEFInterfaces.ILogger
{..}

You can add metadata to an individual method with the [ExportMetadata] attribute:

[ExportMetadata("timeout", "5000")]
public string WriteToLog(string Message)
{..}

Metadata can then be retrieved using a part's Metadata property. The following code demonstrates retrieving metadata from a directory catalog:

CompositionContainer container;
DirectoryCatalog directoryCatalog =
  New DirectoryCatalog((Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)));

foreach (var Part in directoryCatalog.Parts)
{
    Console.WriteLine(Part.Metadata["secure"]);
}

Note that querying a method's metadata is slightly different and that you must instead use the Part.ExportDefinitions property.

2.5.6. What's This All Got to Do with Visual Studio Extensibility?

Visual Studio utilizes MEF in an almost identical way to the previous examples when it loads Visual Studio extensions. When Visual Studio first loads, it examines the extensions directory and loads available extensions. Let's now look into how these extensions are created.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.107.193