MEF is a great tool to create plugins. And a great way to extend your
application.
With MEF (or Managed Extensibility Framework) you can create a separate dll which
implements an interface and if you build the application correctly the new
functionality will work right away.
And the greatest thing about it, it's a part of the framework ever since
version 4.
So here we start:
First we create a console application:
And a new Class Library Named Contracts.
In that project we will store the solution wide interfaces.
In that project we will store the solution wide interfaces.
In the newly created project add an interface, named INotification
public interface INotification
{
string Notification { get; }
}
{
string Notification { get; }
}
After we finished with the interface we will create a new class library
named "Notifications".
There, we will store the implementing classes of Add a reference to the Contracts class library and implement a HelloNotification Class,
and for System.ComponentModel.Composition
There, we will store the implementing classes of Add a reference to the Contracts class library and implement a HelloNotification Class,
and for System.ComponentModel.Composition
[Export(typeof(INotification))]
public class HelloNotification : INotification
{
public string Notification
{
get
{
return "Hello World, Mef Style!";
}
}
}
public class HelloNotification : INotification
{
public string Notification
{
get
{
return "Hello World, Mef Style!";
}
}
}
The Export attribute tells the MEF mechanism to export the class as
INotification and if someone imports the INotification from somewhere else he
will get an instance of the exported class.
Now, in order to tell MEF mechanism where to search for the Exported
tags, we need to build a little
BootStrapper which loads the exported types upon startup.
BootStrapper which loads the exported types upon startup.
public class BootStrapper
{
public CompositionContainer Run()
{
var mefCatalog = new AggregateCatalog();
var executablePath = Assembly.GetExecutingAssembly().Location;
var executableDirectory = Path.GetDirectoryName(executablePath);
mefCatalog.Catalogs.Add(new DirectoryCatalog(executableDirectory));
var container = new CompositionContainer(mefCatalog);
container.ComposeParts(this);
return container;
}
}
The two last lines before the return statement are responsible of composing
the classes from the catalog into the MEF Container
In order for that code to work, we need to set the OutputPath of each
project to the directory of the executable. (With exception of the MefSample
Project).
Go to Project Properties, Build tab, and look for Output group.
Now we have one project which implement the INotification Interface, and
we want to use it.
So first we run the BootStrapper to get the exported classes.
then we use it!
then we use it!
But how?
We have 2 ways of doing so.
The first way is to use the [Import] Attribute.
The Import Attribute marks the field or property as good for exported class assignment.
but it won't going to work unless someone will satisfy the import,
so after we marked the property as [Import] we tell the container to satisfy the imports by SatisfyImportsOnce on the object which needs it. In our case it's "this" (Program class)
We have 2 ways of doing so.
The first way is to use the [Import] Attribute.
The Import Attribute marks the field or property as good for exported class assignment.
but it won't going to work unless someone will satisfy the import,
so after we marked the property as [Import] we tell the container to satisfy the imports by SatisfyImportsOnce on the object which needs it. In our case it's "this" (Program class)
After that we can use the field or property as normal object which
implements the interface.
public class Program
{
[Import]
INotification _notification;
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
container.SatisfyImportsOnce(this);
Console.WriteLine(_notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}
{
[Import]
INotification _notification;
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
container.SatisfyImportsOnce(this);
Console.WriteLine(_notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}
But, this will only work if we know we have one exported class only.
If we have two of that kind, it will crash, because the MEF doesn't know which one to take.
If we have two of that kind, it will crash, because the MEF doesn't know which one to take.
Now add another class to the notification project.
Let's call it SecondNotification which implements INotification as well.
And exported by the Type of the interface.
Let's call it SecondNotification which implements INotification as well.
And exported by the Type of the interface.
[Export(typeof(INotification))]
public class SecondNotification : INotification
{
public string Notification
{
get { return "2nd Hello World, Mef Style!"; }
}
}
public class SecondNotification : INotification
{
public string Notification
{
get { return "2nd Hello World, Mef Style!"; }
}
}
Now if we try to run the program we will get an exception:
1) More than one export was found that matches the constraint:
ContractName Contracts.INotification
RequiredTypeIdentity Contracts.INotification
Resulting in: Cannot set import 'MefSample.Program._notification
(ContractName="Contracts.INotification")' on part
'MefSample.Program'.
Element: MefSample.Program._notification (ContractName="Contracts.INotification")
--> MefSample.Program
The Mef tells you it cannot resolve the correct class for the contract.
We can easily solve it by changing the Import attribute to a new
attribute.
[ImportMany], and change the field type to IEnumerable
[ImportMany], and change the field type to IEnumerable
so our Program class will look as follows:
public class Program
{
[ImportMany]
IEnumerable _notifications;
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
container.SatisfyImportsOnce(this);
foreach (var notification in _notifications)
Console.WriteLine(notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}
{
[ImportMany]
IEnumerable
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
container.SatisfyImportsOnce(this);
foreach (var notification in _notifications)
Console.WriteLine(notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}
Please take note that the printing is now running in a foreach loop on
all of the imported values.
We can ditch the Import attributes and use the container directly.
The way we do it is by using the method GetExportedValue() of
the Container.
The way we do it is by using the method GetExportedValue
So for now comment out the Export Attribute from the SecondNotification Class
and make the following changes in the Program Class.
and make the following changes in the Program Class.
public class Program
{
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
var notification = container.GetExportedValue();
Console.WriteLine(notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}
{
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
var notification = container.GetExportedValue
Console.WriteLine(notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}
Now we are getting the export not by an attribute but by direct
reference to the container.
If we want a several Classes, we would do as we did before.
Uncomment the export we did several like above
and notice we get the same exception as above.
Now all we need to do is the GetExportedValues() method and
That’s it!.
Uncomment the export we did several like above
and notice we get the same exception as above.
Now all we need to do is the GetExportedValues
public class Program
{
public void Run()
{
var bootstrapper = new BootStrapper();
var container = bootstrapper.Run();
var notifications = container.GetExportedValues
foreach (var notification in notifications)
Console.WriteLine(notification.Notification);
}
static void Main(string[] args)
{
var program = new Program();
program.Run();
}
}