Managed Extensibility Framework (MEF)

MEF is a framework which allows you to create plugins for application.
Imagine that you have an program and you want to allow other developers to add functionality to existing software without need to edit source of your app.
Using MEF you application can search specified folder for additional DLLs with new functionalities.

Example below shows how to create simple MEF translator. App translates from english to other languages. Basic version (without any plugins) translates to polish only. When plugin is provided words can be translated to german and italian as well.

To create an MEF application you need to add reference to System.ComponentModel.Composition.
Our solution contains three projects:
a) MEFTranslator – console application, main application with basic functionality, user interface, knows how to read plugins;
b) MEFTranslatorBasic – dll, interfaces neccessary to write plugins.
c) EuropeanLanguages – dll, a plugin with DE and IT translations. (plugin is optional, main application can run without any plugins).

MEFTranslator references MEFTranslatorBasic to provide basic (PL) translation.
EuropeanLanguages references MEFTranslatorBasic to provide other translations.

MEFTranslatorBasic contains two, self-explanatory interfaces.

    ///<summary>
    /// Informs what plugin can do
    /// </summary>
    public interface ITranslation
    {
        string Translate(string word);
    }

 

    ///<summary>
    /// Additional information about plugin opportunities
    /// </summary>
    public interface ITranslationData
    {
        string Lang { get; }
    }

In main project we have interface and class which performs main business logic (notice attributes).

    ///<summary>
    /// Contains main bussiness logic. Finds all available translations for given word and returns translated string.
    /// </summary>
    [Export(typeof(ITranslator))]
    class ExtendableTranslator : ITranslator
    {
        [ImportMany]
        private IEnumerable<Lazy<ITranslation, ITranslationData>> translations;

        public string DoTranslation(string word)
        {
            if (String.IsNullOrEmpty(word))
                return "invalid word";

            word = word.Trim();
            string result = "";
            //below is the magic part; we have here all translations from main app and plugins (if any)
            //in Metadata you can find language thanks to ExportMetadata attribute and ITranslationData interface
            foreach (var t in translations)
            {
                result += t.Metadata.Lang + "=>" + t.Value.Translate(word) + "; ";
            }

            if (String.IsNullOrEmpty(result))
                result = "Languages not found.";

            return result;
        }
    }

We have here basic translation as well (notice attributes).

    [Export(typeof (ITranslation))]
    [ExportMetadata("Lang", "PL")]
    class PLTranslation : ITranslation
    {
        public string Translate(string word)
        {
            switch(word)
            {
                case "dog":
                    return "pies";
                case "cat":
                    return "kot";
                default:
                    return "nieznany";
            }
        }
    }

The last thing is app initialization, searching for plugins and the user interface.

    class Program
    {
        [Import(typeof(ITranslator))]
        ITranslator translator;

        CompositionContainer container;

        private Program()
        {
			//Find and compose available translations in current app directory and in additional folder
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
            catalog.Catalogs.Add(new DirectoryCatalog(@"c:\myplugins")); //this line can be commented out - then we won't search for plugins

            container = new CompositionContainer(catalog);
            try
            {
                this.container.ComposeParts(this);
            }
            catch (CompositionException compositionException)
            {
                Console.WriteLine(compositionException.ToString());
            }
        }

        static void Main(string[] args)
        {
            var p = new Program();
//in a loop ask for word and perform main bussiness logic to find translated words
            while (true)
            {
                Console.WriteLine("Type a word: ");
                var word = Console.ReadLine();
                var translations = p.translator.DoTranslation(word);
                Console.WriteLine(translations);
            }
        }

    }

The last project (EuropeanLanguages) is a plugin which (dll) can be copied to c:\myplugins.
We have in it translation to italian (analogy to PLTranslation).

    [Export(typeof(ITranslation))]
    [ExportMetadata("Lang", "IT")]
    public class ITTranslation : ITranslation
    {
        public string Translate(string word)
        {
            switch (word)
            {
                case "dog":
                    return "cane";
                case "cat":
                    return "gatto";
                default:
                    return "sconosciuto";
            }
        }
    }

This way anyone can create next dll like EuropeanLanguages copy to specified catalog and extend translator.

Application running without EuropeanLanguages.

mef_without_plugin

Application running with EuropeanLanguages plugin.

mef_with_plugin

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s