Creating extendible applications with MEF

Ever wanted to create an application that is easy to maintain and even more easy to extend?
Then the following piece by Michael Hensen about Microsoft Extension Framework (MEF) could be a solution for your needs!

With MEF, which is part of VS2010 own extensions platform, you can write parts of an application is an enclosed dll. This way you can build up your application the normal way and based on the requirements of a client you can add or remove functions as easy as removing a dll from the base directory.
This can be done in VS2008 and you won’t need VS2010 to be able to do this right now.
You can find more info on MEF at http://Mef.codeplex.com
.

Creating a simple Hello world with MEF and Visual WebGui.
In this section I’ll show you how to write the base for a simple framework that can take multiple functions from multiple dll’s and have them interact with the framework.


As a picture tells more then a thousand words

In the image above you see the complete idea of how to work with MEF and the first main thing you notice is the part named ‘Interface object’. This holds the interface of the function objects on which they need to comply.  As an interface is a contract you’ll always now which minimal functions such an object has implemented.

But first we need something that can present all the functions and give the user the change to use functions defined in the function blocks.  This is called the main application, which has a reference to the interface block so that we can call the functions.

For this sample a very simple framework is used. Just a splitter panel with on the left side a navigation tabs control and on the right a headered panel. The Navigation tabs will show the names of the function objects found in the main bin directory and after selecting a tab the right side will show you the function. The main application and function objects also have all a reference to the MEF component named System.ComponentModel.Composition.dll which can be found in the MEF package from Mef.Codeplex.com. Note that there is NO reference between the main application and the functions directly!!!

The Interface object

For the purpose of this sample a very simple interface is declared.
You can make the interface as complex as you like and extend it with events and other functions as you like. As long as you remember that every function object that you will write will need to implement the complete interface. Thus splitting up an interface in multiple interfaces is preferred when your interface would be to complex or to much to implement simple functions. I myself use multiple interfaces to split up common objects. This is code for the interface object.

namespace IMEFInterface
{
    public interface IMEFApplication
    {
        string Name
{ get;
}
        void Init();
            }
}

Just a simple Init function and a Name property are implemented in this case. This means that every component would need to have this 2 items declared!

Loading functions with MEF

Now that we have the basic layout of the framework application we need some code to let the framework now where and what to load as functions

Let us start with the main code of the application.
The comments in the code should explain all the functions as used!


#region Using using System;
using System.Collections.Generic;
using Gizmox.WebGUI.Forms;
using System.Reflection;
using System.IO;
//The MEF object
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition; 
#endregion namespace MEFSample
{
    public partial class frmMEFContainer : Form
    {
        //this tag causes MEF to start searching for objects/functions tagged with export(typeof(IMEFApplication))
        [ImportMany(typeof(IMEFInterface.IMEFApplication))]
        List<IMEFInterface.IMEFApplication> ImportedItems;
         public frmMEFContainer()
        {
            InitializeComponent();
         }
        private void frmMEFContainer_Load(object sender, EventArgs e)
        {
            //Load the lists with the importmany tags
            LoadMef();
            //iterate through the list to get the functions and a a tab to the navigationtabs.
            foreach (IMEFInterface.IMEFApplication FunctionObject in ImportedItems)
            {
                IMEFInterface.IMEFApplication mAppExtension =
(IMEFInterface.
IMEFApplication)Activator.CreateInstance(FunctionObject.GetType());
                NavigationTab mNavTab = new NavigationTab();
                 mNavTab.Text = mAppExtension.Name;
                //add the object as a reference to the tag.. Mind that no instantion is done.
                mNavTab.Tag = mAppExtension;
                navTabs.TabPages.Add(mNavTab);
                            }
            //init the first tab
            navTabs_SelectedIndexChanged(navTabs, null);
        }
         static public string AssemblyDirectory
        {
            get
            {
                //where to look !!
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                //create a path withouth the file// extension in front
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }

         private void LoadMef()
        {
            //What directory to look for!
            String strPath = AssemblyDirectory; 
          
using (var Catalog = new AggregateCatalog())
            {
                DirectoryCatalog directorywatcher = new DirectoryCatalog(strPath, "*.dll");
                Catalog.Catalogs.Add(directorywatcher);
                CompositionBatch batch = new CompositionBatch();
                batch.AddPart(this);
                CompositionContainer container = new CompositionContainer(Catalog);
                //get all the exports and load them into the appropriate list tagged with the importmany
                container.Compose(batch);
            }
        }
         private void navTabs_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Clear the panel controls.
            headeredPanel1.Controls.Clear();
            if (navTabs.SelectedItem == null)
            {
                //stop if we have no tabs!!! (no functions)
                return;
            }
            //get the functionobject from the tag as added by loading tabs function in frmMEFContainer_Load
            IMEFInterface.IMEFApplication mControl = (sender as NavigationTabs).SelectedItem.Tag as IMEFInterface.IMEFApplication;
            //use the interface to get the name
            headeredPanel1.Text = mControl.Name;
            //convert it to a control
            UserControl FunctionControl = mControl as UserControl;
            FunctionControl.Dock = DockStyle.Fill;
            //add to the panel
            headeredPanel1.Controls.Add(FunctionControl);
            //init the object to start the display
            mControl.Init();
        }
     }
}
From now you can start the application but nit much will show. If you do run you’ll see

 

Building a function you can use in the framework environment.

A simple function that will be imported by the application. This will cause the main application to add a tab and display the function.


#region Using using System;
 using Gizmox.WebGUI.Forms;
using System.ComponentModel.Composition;
using IMEFInterface;
 #endregionnamespace MEFExport
{
    //mark the object as a function and implement the interface as declared before.
    [Export(typeof(IMEFApplication))]
    public partial class Function_1 : UserControl,IMEFApplication
    {
        public Function_1()
        {
            //Notice that the InitializeComponent function is moved to the Init funcion.. This to preserve instantiatin and thus improve performance as no controls are build yet!
            //and that you alwys will start with a new clean screen
        }
         #region IMEFApplication Members
         public void Init()
        {
            InitializeComponent();
        }
        private string _Name;
        public string Name
        {
            get
            {
                return "Function 1";
            }
                  }
         #endregion
         private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show(String.Format("Hello world started from {0}", Name));
        }
    }
}

Again all comments should be suficient enough to get you started.

Build the component and copy the DLL to the bin directory of you application. If you do not copy it you will not see any function. If you’ve copied the function you should see the tab and the label named function 1 with a button. Also notice that all the function are inside the component itself.

Now you can add function as much as you like. Group them in DLL’s. In the sample included is a the code as above and an object with 2 functions in 1 dll and an object with a single function.

 

Finally you can build up complete applications like this screen shows.
 

End Note

I use MEF myself in my applications as this gives me a form of freedom and easy of extending an application. Also I can choose to distribute functions specific to a client or choose to have the component be part of my main application

As you can see only imagination is your limit!

Happy programming and if you have any questions please send me a message at mh[at]WebVize.nl

You can find more tutorials here.

1 Comment

  • I think the LoadMef and AssemblyDirectory
    belongs in Global.asax

    can Activator.CreateInstance be resolved on mef import?
    with session lifetimemanagement?
    only wrapping the imports with layout control if needed?
    then only switching controls in navTabs_SelectedIndexChanged

Comments have been disabled for this content.