Friday, September 6, 2013

Simple MEF Tutorial

MEF is a great tool to create plugins. And a great way to extend your application.
With MEF (or Managed Extensibility Framework) you can create a separate dll which implements an interface and if you build the application correctly the new functionality will work right away.
And the greatest thing about it, it's a part of the framework ever since version 4.

So here we start:
First we create a console application:




And a new Class Library Named Contracts.
In that project we will store the solution wide interfaces.

In the newly created project add an interface, named INotification

    public interface INotification
    {
        string Notification { get; }
    }

After we finished with the interface we will create a new class library named "Notifications".
There, we will store the implementing classes of Add a reference to the Contracts class library and implement a HelloNotification Class,
and for System.ComponentModel.Composition

    [Export(typeof(INotification))]
    public class HelloNotification : INotification
    {
        public string Notification
        {
            get
            {
                return "Hello World, Mef Style!";
            }
        }
    }

The Export attribute tells the MEF mechanism to export the class as INotification and if someone imports the INotification from somewhere else he will get an instance of the exported class.
Now, in order to tell MEF mechanism where to search for the Exported tags, we need to build a little
BootStrapper which loads the exported types upon startup.

    public class BootStrapper
    {
        public CompositionContainer Run()
        {
            var mefCatalog = new AggregateCatalog();
         
            var executablePath = Assembly.GetExecutingAssembly().Location;
            var executableDirectory = Path.GetDirectoryName(executablePath);

            mefCatalog.Catalogs.Add(new DirectoryCatalog(executableDirectory));

            var container = new CompositionContainer(mefCatalog);
            container.ComposeParts(this);

            return container;
        }
    }

The two last lines before the return statement are responsible of composing the classes from the catalog into the MEF Container
In order for that code to work, we need to set the OutputPath of each project to the directory of the executable. (With exception of the MefSample Project).
Go to Project Properties, Build tab, and look for Output group.


Now we have one project which implement the INotification Interface, and we want to use it.
So first we run the BootStrapper to get the exported classes.
then we use it!
But how?
We have 2 ways of doing so.
The first way is to use the [Import] Attribute.
The Import Attribute marks the field or property as good for exported class assignment.
but it won't going to work unless someone will satisfy the import,
so after we marked the property as [Import] we tell the container to satisfy the imports by SatisfyImportsOnce on the object which needs it. In our case it's "this" (Program class)

After that we can use the field or property as normal object which implements the interface.

    public class Program
    {
        [Import]
        INotification _notification;

        public void Run()
        {
            var bootstrapper = new BootStrapper();
            var container = bootstrapper.Run();

            container.SatisfyImportsOnce(this);
            Console.WriteLine(_notification.Notification);
        }

        static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }
    }

But, this will only work if we know we have one exported class only.
If we have two of that kind, it will crash, because the MEF doesn't know which one to take.

Now add another class to the notification project.
Let's call it SecondNotification  which implements INotification as well.
And exported by the Type of the interface.

    [Export(typeof(INotification))]
    public class SecondNotification : INotification
    {
        public string Notification
        {
            get { return "2nd Hello World, Mef Style!"; }
        }
    }

Now if we try to run the program we will get an exception:
1) More than one export was found that matches the constraint:
                ContractName  Contracts.INotification
                RequiredTypeIdentity   Contracts.INotification
Resulting in: Cannot set import 'MefSample.Program._notification (ContractName="Contracts.INotification")' on part 'MefSample.Program'.
Element: MefSample.Program._notification (ContractName="Contracts.INotification") -->  MefSample.Program

The Mef tells you it cannot resolve the correct class for the contract.
We can easily solve it by changing the Import attribute to a new attribute.
[ImportMany], and change the field type to   IEnumerable
so our Program class will look as follows:

    public class Program
    {
        [ImportMany]
        IEnumerable _notifications;

        public void Run()
        {
            var bootstrapper = new BootStrapper();
            var container = bootstrapper.Run();

            container.SatisfyImportsOnce(this);

            foreach (var notification in _notifications)
                Console.WriteLine(notification.Notification);
        }

        static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }
    }

Please take note that the printing is now running in a foreach loop on all of the imported values.


 You can use the MEF in another way as well.
We can ditch the Import attributes and use the container directly.
The way we do it is by using the method GetExportedValue() of the Container.
So for now comment out the Export Attribute from the SecondNotification Class
and make the following changes in the Program Class.

    public class Program
    {
        public void Run()
        {
            var bootstrapper = new BootStrapper();
            var container = bootstrapper.Run();

            var notification = container.GetExportedValue();

            Console.WriteLine(notification.Notification);
        }

        static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }
    }


Now we are getting the export not by an attribute but by direct reference to the container.
If we want a several Classes, we would do as we did before.
Uncomment the export we did several like above
and notice we get the same exception as above.
Now all we need to do is the GetExportedValues() method and That’s it!.

    public class Program
    {
        public void Run()
        {
            var bootstrapper = new BootStrapper();
            var container = bootstrapper.Run();

            var notifications = container.GetExportedValues();

            foreach (var notification in notifications)
                Console.WriteLine(notification.Notification);
        }

        static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }
    }                              
                          

Now you’ve created your very first MEF Project with ease!

Code samples can be downloaded from here

No comments:

Post a Comment