UvodDoKomponent: MEFExample

from Wiki KIVu

Original site of this example.

MEF Related Terminologies

Let's repeat terminology again :)

A Simple Working Example with Only a Single Export Part in Current Assembly

Let us create a simple calculator for the demonstration. Our sample application will have four basic arithmetic operations, viz. Add, Subtract, Multiply and Division. With that in mind, let us create our project structure as under:

The solution file CalculatorDemoUsingMEF has three projects:

a) Description of CalculatorContract Project

Here, we basically define the contract or interface that the concrete components of Calculator say Add, Subtract, etc. must implement. The ICalculator interface is very simple as described below:

namespace CalculatorContract
{
    public interface ICalculator
    {
        int GetNumber(int num1, int num2);
    }
}

b) Description of CompositionHelper Project

In this project, we will create the various components needed for the Calculator like Addition, subtraction, etc. The Add.cs file is the component used for Addition operation.

First of all, we need to add reference to System.ComponentModel.Composition assembly into the project.

Also a reference of the CalculatorContract.dll is added.

Let us look into the Add.cs file:

using System.ComponentModel.Composition;
using CalculatorContract;

namespace CompositionHelper
{
    [Export(typeof(ICalculator))]
    public class Add:ICalculator
    {
        #region Interface members
        public int GetNumber(int num1, int num2)
        {
            return num1 + num2;
        }
        #endregion
    }
}

The class is decorated by the Export attribute which indicates that this class will be exported to the composition model and it respects only of the ICalculator contract/ interface type.

The CalcliComposition.cs file is the part where the Composition happens.

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using CalculatorContract;

namespace CompositionHelper
{
    public class CalciCompositionHelper
    {
        [Import(typeof(ICalculator))]
        public ICalculator CalciPlugin { get; set; }

        /// <summary>
        /// Assembles the calculator components
        /// </summary>
        public void AssembleCalculatorComponents()
        {
            try
            {
                //Step 1: Initializes a new instance of the 
                //        System.ComponentModel.Composition.Hosting.AssemblyCatalog  
                //        class with the current executing assembly.
                var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

                //Step 2: The assemblies obtained in step 1 are added to the 
                //CompositionContainer
                var container = new CompositionContainer(catalog);

                //Step 3: Composable parts are created here i.e. 
                //the Import and Export components 
                //        assembles here
                container.ComposeParts(this);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Sends the result back to the client
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns></returns>
        public int GetResult(int num1, int num2)
        {
           return CalciPlugin.GetNumber(num1, num2);
        }
    }
}

The AssembleCalculatorComponent function needs some attention. In this function, we are first looking for the catalogs from where the parts are coming. Once identified, the catalogs are held in a container and lastly the parts are composed.

c) Description of CalculatorUI Project

In this project, the UI is displayed. It adds a reference to the CalculatorComponentComposition.dll .

Let us look into the Add button click event code:

 
objCompHelper = new CalciCompositionHelper();

//Assembles the calculator components that will participate in composition
objCompHelper.AssembleCalculatorComponents();

//Gets the result
var result = objCompHelper.GetResult(Convert.ToInt32(txtFirstNumber.Text), Convert.ToInt32(txtSecondNumber.Text));

//Display the result
txtResult.Text = result.ToString();

First, we are assembling the calculator components that will participate in the composition. Then calling the Addition component for the needed operation.

The final output is as shown below:

A Simple Working Example with Multiple Export Part in Current Assembly

Well by this time, we have learnt something about MEF, how it works and also we have successfully added one component in our application. Now let us enhance our application so that it can handle all the other components like Subtraction, Multiplication and Division.

So let us add the various component parts in the application. The project structure will now become:

As can be figured out, there is not much change expected, we have added three new parts which are highlighted.

Let us now visit the Add.cs file again:

using System.ComponentModel.Composition;
using CalculatorContract;

namespace CompositionHelper
{
    [Export(typeof(ICalculator))]
    [ExportMetadata("CalciMetaData", "Add")]
    public class Add:ICalculator
    {
        #region Interface members
        public int GetNumber(int num1, int num2)
        {
            return num1 + num2;
        }
        #endregion
    }
}

We have found that there is a new attribute call ExportMetadata. This new attribute will help us to determine at runtime which implementation to use. It is a form of name value pair. The first parameter of this attribute is the name of the metadata. It's type is string. The next one which is an object holds the value of the metadata. It is by which we will decide which operation will be called. A similar implementation has been followed for the rest of the Exportable parts.

Next, let us have a look at the CalciCompositionHelper.cs file:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using CalculatorContract;

namespace CompositionHelper
{
    public class CalciCompositionHelper
    {
        [ImportMany]
        public System.Lazy<ICalculator, 
	IDictionary<string, object>>[] CalciPlugins { get; set; }

        /// <summary>
        /// Assembles the calculator components
        /// </summary>
        public void AssembleCalculatorComponents()
        {
            try
            {
                //Step 1: Initializes a new instance of the 
                //        System.ComponentModel.Composition.Hosting.AssemblyCatalog  
                //        class with the current executing assembly.
                var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

                //Step 2: The assemblies obtained in step 1 are added to the 
                //CompositionContainer
                var container = new CompositionContainer(catalog);

                //Step 3: Composable parts are created here i.e. 
                //the Import and Export components 
                //        assemble here
                container.ComposeParts(this);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Sends the result back to the client
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns></returns>
        public int GetResult(int num1, int num2, string operationType)
        {
            int result = 0;
            foreach (var CalciPlugin in CalciPlugins)
            {
                if ((string)CalciPlugin.Metadata["CalciMetaData"] == operationType)
                {
                    result = CalciPlugin.Value.GetNumber(num1, num2);
                    break;
                }                               
            }
            return result;           
        }
    }
}

The first change that we observe is that now we have ImportMany attribute instead of Import attribute. The Import attribute is filled always by a single Export attribute while ImportMany attribute can be filled by any number of Export attribute.

When we run the application, we can figure out the various parts that participate in composition.

One more noticeable point is the Lazy<T> class basically defers the creation of large objects till the time we need that. In the sample code, the second parameter is the metadata that will be handled by MEF at runtime.

Correspondingly, the GetResult function has been changed a little bit. It now accepts a third parameter which specifies the operation type and based on that, the Exported parts will be invoked.

Finally, on the Concrete operations click event, we are passing the Operation types as:

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
            DoCalciOperation("Add");
}

Once we run the application, the output will be as under (output shown only for multiplication operation):

Hope this is equally helpful as the previous one. In the next part, we will explore some new catalog types and more features of MEF.

A Simple Working Example with Multiple Export Part using Aggregate Catalog and Directory Catalog

Until now, we have seen that our Composable parts were residing in the current assembly. But what if they reside in different assemblies or in different location?

Well, we have a solution for this too. We will see how Aggregate and Directory Catalog help us in such situations.

Let us see the project structure now.

We can figure out that one new project has been added to the solution file, i.e., Export Components. In that, we have a folder called Components where our export parts in the form of assemblies are stored (.dll files).

We have added only two DLLs there, viz., Add and Subtraction and removed the Add.cs and Subtract.cs files from CompositionHelper project.

Now let us visit the AssembleCalculatorComponent function again.

public void AssembleCalculatorComponents()
        {
            try
            {
                //Creating an instance of aggregate catalog. It aggregates other catalogs
                var aggregateCatalog = new AggregateCatalog();

                //Build the directory path where the parts will be available
                var directoryPath =
                    string.Concat(Path.GetDirectoryName
			(Assembly.GetExecutingAssembly().Location)
                          	.Split('\\').Reverse().Skip(3).Reverse().Aggregate
			((a, b) => a + "\\" + b)
                                , "\\", "ExportComponents\\Components");

                //Load parts from the available DLLs in the specified path 
                //using the directory catalog
                var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll");

                //Load parts from the current assembly if available
                var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

                //Add to the aggregate catalog
                aggregateCatalog.Catalogs.Add(directoryCatalog);
                aggregateCatalog.Catalogs.Add(asmCatalog);

                //Crete the composition container
                var container = new CompositionContainer(aggregateCatalog);

                // Composable parts are created here i.e. 
                // the Import and Export components assembles here
                container.ComposeParts(this);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

As can be seen, initially we have created an AggregateCatalog that will aggregate other catalogs. Then we are picking up the Exportable Components from the Components directory.

If we run the application, we can find that the Directory Catalog has two parts (Add and Subtract).

And the AssemblyCatalog has two parts (Divide and Multiply):

After the parts have been discovered, they are then added to the AggragateCatalog which is being added to the CompositionContainer. And finally, the parts gets composed.

Running the application brings out the desired output (output shown only for Addition operation).

Advantages

MEF has many advantages as we have seen by this time. However, some of the common advantages are listed below:

Conclusion

So in this first part of the MEF series, we learnt about the basics of MEF, its applicability, importance of Export, Import, ImportMany attributes, Assembly and Directory catalogs, dynamic component discovery, etc. But there is a lot more stuff to learn about MEF ;)

Retrieved from http://wiki.kiv.zcu.cz/UvodDoKomponent/MEFExample
Content last modified on 28 January 2015, 15:05