Managed Extensibility Framework (MEF) quick start

Introducing MEF with simple code example application

  • Share

Managed Extensibility Framework or MEF is a library introduced in .NET Framework 4 which allows developers to easily create plugin based applications. It lives in System.ComponentModel.Composition namespace and before start working with MEF reference to this assembly needs to be added in a project.

It is basically an implementation of IoC (Inversion of Control) pattern with few core functionalities such as assembly loading. It allows you to extend the application by simply writing new plugin as an assembly and it will be picked up automatically by container when composition happens.

One of the things that MEF relies on is attributed programming model which allows you to define which elements of your extension assembly will be exposed for composition of a container in a host application.

To make things more clear, I'll start with and example application. As any IoC pattern we'll have to declare shared interfaces which will extension assemblies will have to implement in order to provide communication for the hosting application.

So first thing to be created will be an assembly named MefContract with a simple interface called IMessageSender

namespace MefContract
{
    public interface IMessageSender
    {
        void ShowMessage();
    }
}
    

To keep things simple we'll just add one void method named ShowMessage which will be implemented in derived classes.
Next thing, since we already have our contract interface is to create simple plugin assemblies which will be loaded and consumed by our host application.
We'll create two projects of Class library type and name them MefExtension1 and MefExtension2.
Inside these two projects we'll add a reference to MefContract project and then create classes that implement IMessageSender interface.Code Snippet

namespace MefExtension1
{
    public class Messanger:IMessageSender
    {
        public void ShowMessage()
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(string.Join(string.Empty, new string[] { "Message from ", this.GetType().Namespace }));
        }
    }
}
    

namespace MefExtension2
{
    public class Messanger:IMessageSender
    {
        public void ShowMessage()
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine(string.Join(string.Empty,new string[]{ "Message from ", this.GetType().Namespace}));
        }
    }
}
    

Method will only printout the current namespace and change the color of text in a console.

Now we need to declare Export attribute in the class level enabling the host application to perform composition in the runtime. Before adding attributes we need to ad reference to System.ComponentModel.Composition in both extension projects.

Since our shared interface for extension will me IMessageSender we'll export this interface using export attribute in bot extension projects.

namespace MefExtension1
{
    [Export(typeof(IMessageSender))]
    public class Messanger:IMessageSender
    {
        public void ShowMessage()
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(string.Join(string.Empty, new string[] { "Message from ", this.GetType().Namespace }));
        }
    }
}
    

namespace MefExtension2
{
    [Export(typeof(IMessageSender))]
    public class Messanger:IMessageSender
    {
        public void ShowMessage()
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine(string.Join(string.Empty,new string[]{ "Message from ", this.GetType().Namespace}));
        }
    }
}
    

With this we are done with simple plugins and there are now ready to be consumed by a host application.
For the sake of simplicity, for host application a console application will be used, so add another project of Console Application type to a solution and add references to System.ComponentModel.Composition and MefContract in order to perform composition.

However references to extension libraries MefExtension1 and MefExtension2 are not required. In this lies the idea of writing new extensions without host application to know about then until the runtime and even then, host is only aware of shared interface from the contract library.
First thing we need to do is to declare a container on the Program class level which will import classes that export IMessageSender interface. To make it more optimized I prefer using Lazy so that an object instance is not created until the variable is not required for the first time.

namespace MefHost
{
    class Program
    {
        [Import]
        Lazy<IMessageSender> MessageSender;
        
        static void Main(string[] args)
        {
        
        }
    }
}
    

Since we already created more than one plugin, this piece of code will throw an exception, so to make it more extensible we'll use an IEnumerable to be prepared to load more than one.

namespace MefHost
{
    class Program
    {
        [ImportMany]
        IEnumerable<Lazy<IMessageSender>> MessageSenders;
        
        static void Main(string[] args)
        {
        
        }
    }
}
    

Like in IoC MEF creates a container which will be responsible for importing all plugin assemblies available. As I mentioned before, the thing that makes MEF more advanced that IoC pattern is a container itself which will do the heavy-lifting of assembly search and loading.

We'll invoke container's heavy-lifting in a constructor of our console application class. since DirectoryCatalog will monitor folder Environment.CurrentDirectory it means we have to place both extension libraries in the bin folder of host application.
To make this automated extension projects MefExtension1 and MefExtension2 need to be modified to copy it's output to host application bin folder.

Add the following post build events to both extension projects to automatically copy assemblies to host application bin folder
copy /Y "$(TargetPath)" "$(SolutionDir)\MefHost\bin\Debug"
copy /Y "$(TargetPath)" "$(SolutionDir)\MefHost\bin\Release"

namespace MefHost
{
    class Program
    {
        [ImportMany]
        IEnumerable<Lazy<IMessageSender>> MessageSenders;

        private readonly CompositionContainer _container;

        private Program()
        {
            AggregateCatalog catalog = new AggregateCatalog();
            DirectoryCatalog dirCatalog = new DirectoryCatalog(Environment.CurrentDirectory);
            catalog.Catalogs.Add(dirCatalog);
            _container = new CompositionContainer(catalog);

            try
            {
                _container.ComposeParts(this);
            }
            catch (CompositionException compositionException)
            {
                Console.WriteLine(compositionException.ToString());
                Console.ReadLine();
            }
        }

        static void Main(string[] args)
        {
            Program program = new Program();
            foreach (Lazy<IMessageSender> messanger in program.MessageSenders)
            {
                messanger.Value.ShowMessage();
            }
            Console.ReadLine();

        }
    }
}
    

After extension assemblies are loaded, we can simply iterate through extensions enumeration and invoke exposed methods or access properties. In our case since we have only one public method we'll invoke it.

Now when we start the project we'll see that methods in both extensions are invoked.

Mefhost
As much as MEF is good and extensible there are few things that lack for some specific cases of plugin approach which are:

  • Plugin assemblies cannot be replaced in the runtime since MEF holds handle on them
  • Host application needs to be restarted in order to pick-up updated assemblies

Some of these issues I tried to resolve in a project I started before MEF was introduced in .NET Framework 4. You can find it on GitHub. It might help you by providing some guidelines for your problems which are not resolved by MEF.

References

  • Share

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.

Comments for this article

comments powered by Disqus