Introduction
In this article, I will be explaining some of the useful features that MEF provides. In my article, MEF WCF Startup, I had used the Import
and Export
attributes. Let's move on now and check what else is there.
InheritedExport Attribute
MEF has a really nice little gem of a feature called InheritedExport
. It lets you put an InheritedExport
attribute on an interface or a base, which automatically propagates to inheritors / implementers. This is ideal for MEFfing up existing framework libraries without pushing any MEF details to the consumer.
Create the Interface and add the attribute InheritedExport
to it as shown below:
[InheritedExport]
interface I
{
void display();
}
By using the attribute InheritedExport
, we let the container to compose those parts which have inherited the interface I
.
Let's say we have a class ClassA
as shown below which implements the interface I
.
class ClassA : I
{
public void display()
{
Console.WriteLine("Class A is Imported");
}
}
Now create another class, say Importer
. We are going to put the importing logic here.
public class Importer
{
[Import]
I i;
public void Run()
{
var catalog = new AssemblyCatalog
(System.Reflection.Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
i.display();
}
}
Advantages
- The most obvious advantage here is the
Importer
class is completely ignorant of the Type
that is being referred. This is the most amazing feature which MEF offers through the InheritedExport
attribute.
- I could very easily now replace
ClassA
with ClassB
as shown below:
class ClassB : I
{
public void display()
{
Console.WriteLine("Class B is imported");
}
}
And now, when I run the program, ClassB
is imported. This is what we want in our application.
So MEF not only brings extensibility to our applications, it also does what an AOP would do for you. Reduce, if not completely remove the dependency among the classes.
Now we can simply call our Importer
class in Main
as shown:
class Program
{
static void Main(string[] args)
{
Importer imp = new Importer ();
imp.Run();
Console.ReadLine();
}
}
ImportMany Attribute
ImportMany
is a way to express that you want to import all “items” into a type capable of exposing many items (usually IEnumerable
). This is another very important attribute which makes extensibility so simple.
Let's define a class ClassB
now.
class ClassB : I
{
public void display()
{
Console.WriteLine("Class B");
}
}
Let's make the changes to Importer
class. This time, I would create a Type IEnumerable<I>
.
public class Importer
{
[ImportMany(typeof(I))]
private IEnumerable<I> objects { get; set; }
public void Run()
{
var catalog = new AssemblyCatalog
(System.Reflection.Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
Once I have added the ImportMany
attribute on the IEnumerable
, I would be able to import all the objects that are of Type I
.
Below is the screenshot. Objects are composed of two parts here, both of Type I
.
Once we have imported the objects of Type I
, we could either loop through them as shown below:
foreach (I i in objects)
{
i.display();
}
We could also use Linq to query a specific object as shown below:
var classa = (from o in objects
where o.GetType().ToString().Contains("ClassA")
select o).First();
classa.display();
ImportingConstructor
There would be situations where we would run into scenarios where we would have multiple constructors in the part which we would like to Import
. In that situation, you would want to mention which constructor we want to use while Importing. ImportingConstructor
offers you that feature.
Ok, let's look into this attribute with a short example:
Let's just code a simple class A
as shown below:
- A simple class
A
and let's export it.
[Export]
class A
{
}
- Let's add two constructors. One default and another which accepts a single parameter as
string
.
[Export]
class A
{
public string message;
public A()
{
}
public A([string str)
{
message = str;
}
}
- I would now choose my
ImportingConstructor
. The constructor I want the catalog to use when the part is being composed. Really good feature of MEF.
[Export]
class A
{
public string message;
public A()
{
}
[ImportingConstructor]
public A(string str)
{
message = str;
}
}
- In the importing constructor, I need to add a custom parameter and import it which I would later in my code create an instance of specific value. So add an
[Import("Msg")]
to the ImportingConstructor
as shown below:
[Export]
class A
{
public string message;
public A()
{
}
[ImportingConstructor]
public A([Import("Msg")]string str)
{
message = str;
}
}
- Finally, add a method, say
show
to the class and our class is ready.
[Export]
class A
{
public string message;
public A()
{
}
[ImportingConstructor]
public A([Import("Msg")]string str)
{
message = str;
}
public void show()
{
Console.WriteLine("Welcome"+message);
}
}
Now let's move to our Program
class. Here, I will have to perform a couple of new things to create an instance of a specific value which I have imported previously.
Let's pass the value for the imported Msg
parameter as shown below:
container.ComposeExportedValue("Msg", "How are You!!!");
Getting the specific exported value as shown below:
var person = container.GetExportedValue<A>();
Program
class is shown below:
class Program
{
[Import]
A a;
public void Run()
{
var catalog = new AssemblyCatalog
(System.Reflection.Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeExportedValue("Msg", "How are You!!!");
container.ComposeParts(new Program());
var person = container.GetExportedValue<A>();
person.show();
}
static void Main(string[] args)
{
Program p = new Program();
p.Run();
Console.ReadKey();
}
}
When we give it a Run
, we would get:
Welcome How are You!!!
Director Catalog
To discover all the exports in all the assemblies in a directory, MEF offers you with an attribute called System.ComponentModel.Composition.Hosting.DirectoryCatalog
.
Let's discuss the attribute with an example.
Let us create a new Class Library Project named as Extensions
.
Let's create a new Interface
here.
[InheritedExport]
public interface ILogger
{
void Write(string message);
}
Note that I have added the InheritedExport
attribute to it.
Now let me create a ConsoleLogger
Component which would implement the interface Ilogger
.
This would be one of my extension components.
public class ConsoleLogger : ILogger
{
public ConsoleLogger()
{
}
public void Write(string message)
{
Console.WriteLine(message);
}
}
Create a console project and add a class Application
. Add a reference of Class Library DLL.
Composing the Container
var catalog = new DirectoryCatalog(@"C:\Extensions\Extensions\bin\Debug");
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
Pass the directory path where the Class Library DLL exists.
Import
the component.
[Import]
public ILogger Logger;
Call the method using the component.
Logger.Write("Hello World");
Extension is loaded and works perfectly.
The DirectoryCatalog
will do a one-time scan of the directory and will not automatically refresh when there are changes in the directory. However, you can implement your own scanning mechanism, and call Refresh()
on the catalog to have it rescan.
catalog.Refresh();
Once it rescans, recomposition will occur. The part would be added to the catalog and would be ready to use.
Dynamic Instantiation
Consider ClassB
as shown below:
[Export]
class B
{
public void display()
{
Console.WriteLine("Display Method Of B Class");
}
I can use the dynamic instantiation as well with MEF as shown below:
class Program
{
[Import]
Lazy<B> b;
void Run()
{
var catalog = new AssemblyCatalog
(System.Reflection.Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
b.Value.display();
}
The object would only be instantiated when it is actually used as b.Value
. The IsValueCreated
property would be set to true
when it is used.
PartNotDiscoverable (Avoid Discovery of a Part)
This is something similar to private
access modifier. You don’t want a particular component to be discovered by the catalog, so use this attribute to hide the component.
For example, the part may be a base class intended to be inherited from, but not used. There are two ways to accomplish this.
Use the abstract
keyword on the part class. Abstract
classes never provide exports, although they can provide inherited exports to classes that derive from them.
If the class cannot be made abstract
, you can decorate it with the PartNotDiscoverable
attribute. A part decorated with this attribute will not be included in any catalogs. The following example demonstrates these patterns:
[Export]
public abstract class A
{
}
[PartNotDiscoverable]
[Export]
public class B
{
}
Creation Policies
When a part specifies an import and composition is performed, the composition container attempts to find a matching export. If it matches the import with an export successfully, the importing member is set to an instance of the exported object. Where this instance comes from is controlled by the exporting part's creation policy.
The two possible creation policies are shared and non-shared.
Create a Class B as shown below:
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
class B
{
public void show()
{
Console.WriteLine("B - object is Accessed");
}
}
Note the PartCreationPolicy
attribute on the class B. I have set the CreationPolicy.Shared
. So this part will be shared between every import in the container for a part with that contract. When the composition engine finds a match and has to set an importing property, it will instantiate a new copy of the part only if one does not already exist; otherwise, it will supply the existing copy. Create a Class A as shown below:
[Export]
class A
{
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
B b;
public void display()
{
b.show();
}
}
Note that I am importing the Object
of B Class and I have set the CreationPolicy
as Shared
.
Now I can access the parts from my Program
: I can just Import
the Part A in the Program
. The Part A will take care of Importing B. So the Program
Class would look as follows:
class Program
{
[Import]
A a;
public void run()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
a.display();
}
static void Main(string[] args)
{
Program p = new Program();
p.run();
Console.ReadKey();
}
}
Note that changing the CreationPolicy
on just one of the places would result in an error. You would have to change the CreationPolicy
on both to make the application work.
Now let's add another part to our application. Let's say the CreationPolicy
for the imported part B is set as Nonshared
as shown below:
[Export]
class C
{
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
B b;
public void display()
{
b.show();
}
}
This would not work for sure. No valid exports were found that match the constraint Exception results.
To make this scenario work, just remove the part creation policy on the Class B. The default part creation is Any
. Hence now it would work fine.
Disposing MEF Objects
The MEF container is responsible for managing the lifetime of your exports, so regardless of which CreationPolicy
is used (default is Shared
), the final call to the container's Dispose
method will dispose of any Export
instances (which wrap your actual class instances). Also, calling Dispose
on an Export
instance will cause the actual class instance to dispose
.
Let us check out an example of Dispose
method as shown below:
class Program
{
[Import]
A a;
public void run()
{
CompositionContainer container = null;
try
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
container = new CompositionContainer(catalog);
container.ComposeParts(this);
a.display();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
container.Dispose();
}
}
static void Main(string[] args)
{
Program p = new Program();
p.run();
Console.ReadKey();
}
}
[Export]
class A
{
public void display()
{
Console.WriteLine("The Object A is accessed");
}
}
Exporting MetaData
Exports can provide additional information about themselves known as metadata. Metadata can be used to convey properties of the exported object to the importing part.
The importing part can use this data to decide which exports to use, or to gather information about an export without having to construct it. For this reason, an import must be lazy to use metadata.
I used this example as my reference for posting this section.
Let's create an enumeration Type Category
.
public enum Category
{
Sports, National, International, News
}
public interface IPlug
{
void Write();
}
public interface IMetadataView
{
Category[] Categories { get; }
}
Create a class National
which would implement the Type IPlug
. Note the ExportMetadata
attribute attached on the class.
[Export(typeof(IPlug))]
[ExportMetadata("Categories", Category.News, IsMultiple = true)]
public class National : IPlug
{
public void Write()
{
Console.WriteLine("National");
}
}
Let's create the Program
class:
Let's us create the Program
class now.
class Program
{
static void Main(string[] args)
{
var instance = new Program(); var cat = new AssemblyCatalog(typeof(Program).Assembly); var container = new CompositionContainer(cat);
container.ComposeParts(instance);
foreach (var plug in instance.Plugins)
{
foreach (var category in plug.Metadata.Categories)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("{0}, ", category);
Console.ResetColor();
}
Console.WriteLine(string.Empty);
plug.Value.Write(); }
Console.ReadLine();
}
[ImportMany]
public Lazy<iplug, />[] Plugins
{
get;
private set;
} }
Let's give it a run.
Alternative Method of Exporting MetaData: Use of MetadataAttribute
We can make use of the MetadataAttribute
and create a class CategoryAttribute
which would extend the Attribute
class. I like this approach.
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CategoryAttribute : Attribute
{
public CategoryAttribute(Category category)
{
Categories = category;
}
public Category Categories
{
get;
private set;
}
}
Once we have created the CategoryAttribute
, we can just create a class Cricket
which would inherit from the IPlug
.
Let's give it a run now:
[Export(typeof(IPlug))]
[Category(Category.Sports)]
public class Cricket : IPlug
{
public void Write()
{
Console.WriteLine("Cricket");
}
}
Conclusion
So friends, I have tried to put together some of the features that MEF offers. Please let me know your thoughts about the article. I plan to compose some advanced MEF features in my next article.
Thanks. Happy coding.