Friday, October 30, 2009

MEF in .NET 4.0/Visual Studio 2010 – Simple Example

The Managed Extensibility Framework (MEF) is a new library in .NET Framework 4.0 that helps in simplifying the design of extensible applications and components. If you are developing a .NET application (WinForms, WPF, Silverlight or ASP.Net) that supports plugins (independent libraries with different functionalities), then MEF is very handy.

Let us examine the basics of MEF through a very plain example application. I am building a WinForms showroom application which will display different vehicles. I have my own vehicles to display, and the application also supports vehicles from other vendors (aka plugins). All they need to do is to stick to my contract (aka implement the interface I give) while they develop their vehicles.

Let us first define the required contract. In Visual Studio 2010 (My version is Beta 2), create a new C# class library project and name it as Showroom.Contracts. Add an interface to it as below:
namespace Showroom.Contracts
{
    public interface IVehicle
    {
        string GetVehicleDetails();
    }
}

Now let us assume that we distribute this contract (interface library) to other vendors so that they can create vehicles that will fit in our showroom. To portray this scenario add another class library project named Vehicle.Hyundai. Add Showroom.Contracts project as a reference to this project as we need to implement the contract interface. This is going to be a plugin (extension or ComposablePart in MEF terminology) and hence we need to also add a reference to the MEF library System.ComponentModel.Composition. Add a class as shown below to this project:

namespace Vehicle.Hyundai
{
    using System.ComponentModel.Composition;
    using Showroom.Contracts;

    [Export(typeof(IVehicle))]
    public class HyundaiSonata : IVehicle
    {
        string IVehicle.GetVehicleDetails()
        {
            return "Hyundai Sonata";
        }
    }
}

Notice the Export attribute. This informs MEF that this is a plugin of type IVehicle. Now let us add one more similar class:

namespace Vehicle.Hyundai
{
    using System.ComponentModel.Composition;
    using Showroom.Contracts;

    [Export(typeof(IVehicle))]
    public class HyundaiSantro : IVehicle
    {
        string IVehicle.GetVehicleDetails()
        {
            return "Hyundai Santro!";
        }
    }
}

To portray one more vendor, add another class library project named Vehicle.Maruti and add a similar class to this as well:

namespace Vehicle.Maruti
{
    using System.ComponentModel.Composition;
    using Showroom.Contracts;

    [Export(typeof(IVehicle))]
    public class MarutiSwift : IVehicle
    {
        string IVehicle.GetVehicleDetails()
        {
            return "Maruti Swift";
        }
    }
}

Now that the contract and plugins based on these contracts are ready, let us build the showroom host or shell application to display these vehicles. Add a new WinForms project to the solution named Showroom.Shell. Add a project reference to Showroom.Contracts and reference to System.ComponentModel.Composition. Add a listbox to the form and name it as uxVehicles. This will display all the vehicles we have.

Remember we have our own vehicles to display in the showroom (besides vehicles from other vendors). Let us add a new class to the project:

namespace Showroom.Shell
{
    using System.ComponentModel.Composition;
    using Showroom.Contracts;

    [Export(typeof(IVehicle))]
    public class ShowroomVehicle1: IVehicle
    {
        string IVehicle.GetVehicleDetails()
        {
            return "Showroom Vehicle Normal";
        }
    }
}

And one more similar one:

namespace Showroom.Shell
{
    using System.ComponentModel.Composition;
    using Showroom.Contracts;

    [Export(typeof(IVehicle))]
    public class ShowroomVehicle2 : IVehicle
    {
        string IVehicle.GetVehicleDetails()
        {
            return "Showroom Vehicle Special";
        }
    }
}

Note that these are also decorated with Export attribute for MEF to pick them up later. Now go to the source code of Form1 to build the shell logic:

namespace Showroom.Shell
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;
    using System.Windows.Forms;
    using Showroom.Contracts;

    public partial class Form1 : Form
    {
        [ImportMany(typeof(IVehicle))]
        public List<IVehicle> Vehicles { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.Vehicles = new List<IVehicle>();

            AggregateCatalog cat = new AggregateCatalog();
            cat.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            cat.Catalogs.Add(new DirectoryCatalog("Plugins"));

            CompositionContainer contnr = new CompositionContainer(cat);

            contnr.ComposeParts(this);

            foreach (IVehicle item in this.Vehicles)
            {
                uxVehicles.Items.Add(item.GetVehicleDetails());
            }
        }
    }
}

We have declared a property named Vehicles to contain all the vehicles we plan to display in our showroom. Notice the ‘ImportMany’ attribute. It tells MEF that this property need to be loaded using plugins of type ‘IVehicle’. We have used ‘ImportMany’ as there are more than one vehicle and will be using ‘Import’ instead if there is only one plugin.

MEF's core is comprised of a catalog and a CompositionContainer. A catalog is responsible for discovering extensions (plugins) and the container coordinates creation and satisfies dependencies. The AssemblyCatalog gathers plugins from an assembly. Here we have asked it to find all the plugins (classes with ‘Export’ attributes) from the currently executing assembly (Showroom.Shell). The DirectoryCatalog gathers plugins from a directory. Here we have asked the catalog to look in a directory named ‘Plugins’ for the assemblies containing classes that have ‘Export’ attribute. We have used AggregateCatalog to aggregate the tow different (Assembly and Directory) catalogs of plugins into one. The ComposeParts method of the CompositionContainer does all the magic by importing the required types from these plugins and loading them to the corresponding properties. Here in our code, the property ‘Vehicles’ will now be loaded with all the available vehicle plugins and we call their ‘GetVehicleDetails’ methods to add the results to a the listbox.

mefsample
Before running this application, copy the plugin assemblies (Vehicle.Hyundai.dll and Vehicle.Maruti.dll) to a folder named ‘Plugins’ under ..\bin\Debug\ folder of the Showroom.Shell project.

Download the complete sample (works in Visual Studio 2010 Beta 2) from here http://wayfarer.bizhat.com/techbites/downloads/mef_showroom1.zip

1 comment: