Table of Contents
- Introduction
- Background
- What is MEF?
- Assembly in which MEF resides
- MEF Constituents
- MEF related terminologies
- How MEF works
- A simple working example with only a single export part in current assembly
- A simple working example with multiple export part in current assembly
- A simple working example with multiple export part using Aggregate Catalog and Directory Catalog
- Advantages of MEF
- Conclusion
- History
Though Managed Extensibility Framework (MEF) has been there a long time now, but still I think that there should be a simple article for a beginner to start with. It has now become an integral part of .NET Framework 4.0 and can be used in any kind of application. This is the first part of the series that I will be continuing on MEF. In the first part, we will learn about the basics and have a feel of MEF and at the end of this we should be able to figure out the benefit of MEF, how it helps in the development activities and in which situation we will adopt this. The article is written by keeping in mind that many developers are yet to start working on this and this article or the series will give them the feel of using it from scratch. So let us begin the journey.
Working on MEF since the last few months was real fun. Henceforth, I thought of sharing the same with those who are about to start learning MEF from more of a step by step approach as well as for those who have done some MEF programming but did not get much exposure to it and wanted to learn more. We will start the journey from here with the very basic examples and will move ahead from time to time. This will be an n-th series of articles so in every part we will learn some new feature.
Managed Extensibility Framework is a new framework from Microsoft Corporation to build Extensible applications. Its basic purpose is to plug-in components to an already running application. It is an integral part of .NET 4.0 Framework and it finds its applicability in any kind of .NET applications.
- System.ComponentModel.Composition.dll
- Import, Export and Compose
Before going to talk more about MEF, let us see some of the basic terminologies that will help us in understanding MEF better.
- Part: A Part is an object (e.g. a class, a method or a property) that can be imported or exported to the application.
- Catalog: An object that helps in discovering the available composable parts from an assembly or a directory.
- Contract: The import and exported parts need to talk between themselves via some contract (e.g. an Interface or predefined data type like
string
)
- Import Attribute: It defines the need that a part has. It is applicable only for single Export Attribute.
- ImportMany Attribute: It is similar to Import Attribute but supports for multiple Export Attributes.
- Export Attribute: The import attribute creates the needs. The Export attribute fulfills that. It exposes those parts that will participate in the composition.
- Compose: In MEF jargon, Compose is that area where the Exported parts will be assembled with the imported ones.
At this point of time, the terminologies may not be clear to us. In order for the terminologies to be fathomable, let us first try to understand how MEF works.
MEF works upon the principle of demand/need and supply of parts. The suppliable parts must be discovered and both the need and the suppliable parts must agree to the contract. Whenever there is a requirement of some parts to be plugged into the application, MEF will perform a dynamic discovery of those parts from some location (which we call as Catalog). The composition engine will assemble those parts as long as they satisfy the contract.
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:
CompositionHelper
CalculatorContract
CalculatorUI
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.
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
}
}
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.
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; }
public void AssembleCalculatorComponents()
{
try
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
catch (Exception ex)
{
throw ex;
}
}
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();
objCompHelper.AssembleCalculatorComponents();
var result = objCompHelper.GetResult(Convert.ToInt32(txtFirstNumber.Text),
Convert.ToInt32(txtSecondNumber.Text));
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:
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; }
public void AssembleCalculatorComponents()
{
try
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
catch (Exception ex)
{
throw ex;
}
}
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.
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
{
var aggregateCatalog = new AggregateCatalog();
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).
MEF has many advantages as we have seen by this time. However, some of the common advantages are listed below:
- MEF breaks the tightly coupled dependencies across the application but respects the type checking of the loosely coupled parts.
- Applications can be extended.
- Components can be added at runtime.
- Dynamic discovery of the components.
- Great piece of reusability.
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. In the next part or subsequent parts, we will look into some attributes, catalogs, more practical examples with different kind of applications using MEF. Thanks for reading.
- 28th April, 2011: Initial post