In my last article, I showed how to create a Managed Extensibility Framework (MEF) contract using the [Import] and [Export] attributes.  The code in that article created a contract based on a string and imported only a string variable.  I chose that sample because it was the simplest I could think of to illustrate the concepts of a contract.  But if all you are doing is swapping strings at runtime, there are simpler ways to accomplish this than MEF.

In this article, we will create a contract based on an interface.  Interfaces describe public properties and methods of a class without providing any implementation of those properties and methods.  This gives developers the flexibility to decide later which class to instantiate.  With MEF, that flexibility is increased even more because developers do not need to set a reference to classes at compile time.

Recall that MEF uses a contract that matches Import and Export components at runtime.  Contracts are defined by Import and Export attributes.

In our sample, we create three projects:

  • MEFInterface contains the IToDo interface.
  • MEFConsoleApp1 is our console application.  It contains the Import property based on the IToDo interface.
  • MEFComponent1 is a class library containing an exported property implementing the IToDo interface.

MEFConsoleApp1 and MEFComponent1 each have a reference to MEFInterface because each contains a class that implements the IToDo interface.  However, MEFConsoleApp1 does not contain a reference to MEFComponent1, even though the console app will consume classes in the class library.  An MEF contract replaces the tight coupling of a typical application.

Creating the Contract 

To accomplish this, we do the following
1. Create the IToDo interface in the MEFInterface project.  This interface defines two properties: TaskName and HoursRequired, as shown below

    public interface IToDo
    {
        string TaskName { get; }
        double HoursRequired { get; }
    }


2. In the console application, declare a variable of type IToDo and decorate this variable with the Import attribute.  The Import attribute accepts a parameter that defines the contract.  Assign the contract type of the import attribute as TypeOf (IToDo) as shown below

        [Import(typeof(IToDo))]

public IToDo MyTask {get; set;}

3. In the MEFComponent1 project, create a class that implements IToDo.  Return a value from each property's getter and decorate this class with the Export attribute.  Assign the contract type parameter as TypeOf(IToDo).  We now have a contract because we have an import and an export with the same contract parameter.  MEF will match these up when we tall it where to compose its parts.

    [Export(typeof(IToDo))]
    public class FirstTask : IToDo
    {
        public string TaskName
        {
            get
            {
                return "Get out of bed";
            }
        }
        public double HoursRequired
        {
            get
            {
                return .10;
            }
        }
    }

4. As in the code from the last article, we must tell MEF where to find all the parts to compose.  Add the following code to the MEFConsoleApp1.

            string folderToWatch = 
System.Configuration.ConfigurationSettings.AppSettings["MEFExportFolder"];
var catalog = new DirectoryCatalog(folderToWatch); var container = new CompositionContainer(catalog); var batch = new CompositionBatch(); batch.AddPart(this); container.Compose(batch);

5. The above code gets the folder location from App.Config file, so you will need to add to the MEFConsoleApp1 project an application configration file with the following contents. (Set the directory to the location of the compiled DLL for the MEFComponent1 project).

"1.0" encoding="utf-8" ?>

 
    "MEFExportFolder"
         value="C:\Development\MEF\DGCode\MEFDemo2\MEFComponent1\bin\Debug" />
 

6. Finally, add code in MEFConsoleApp1 to output values of our variable.

            Console.WriteLine
(
string.Format
(
"It takes {0} hours to do the task: {1}.",
MyTask.HoursRequired,
MyTask.TaskName
)
);

What if there are Multiple Exports matching an Import? 

In this case, MEF was able to match a single IToDo Export with a single IToDo import. But what would happen if MEF found two Exports to match just one import? 

In our example, we might add to MEFComponent1 a second exportd class that implements IToDo.

    [Export(typeof(IToDo))]
    public class ImportantTask : IToDo
    {
        public string TaskName
        {
            get
            {
                return "Make the donuts";
            }
        }
        public double HoursRequired 
        { 
            get
            {
                return .25;
            }
        }

Which Export would MEF choose to match the Import?  Both satisfy the contract.  The answer is that MEF would choose to throw an exception.  If we can't be sure of how many Exports MEF will find to match our Import contract, we should replace our IToDo declaration with an collection of IToDo objects for the Import, as shown below.

        [Import(typeof(IToDo))]
        public IEnumerable ToDoList { get; set; }

Then, we can loop through this collection and output all the properties:

            foreach (var td in ToDoList)
            {
                Console.WriteLine
(
string.Format
(
"It takes {0} hours to do the task: {1}.",
td.HoursRequired,
td.TaskName
)
); }

In this article, we showed how to create an MEF contract based on an interface and how to handle mutliple exports for a single import in an MEF contract.

Code: MEFDemo2.zip (574.36 KB)

Note: The code in this article uses MEF CTP 5.