Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MEF Features - with Examples

0.00/5 (No votes)
28 Jul 2011 1  
In this article, I will explain some of the useful features that MEF provides

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

  1. 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.
  2. 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:

// Query the object of ClassA
            var classa = (from o in objects
                          where o.GetType().ToString().Contains("ClassA")
                          select o).First();

// Using the Object just call the method 
            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:

  1. A simple class A and let's export it.
    [Export]
    class A
        {
          
        }
  2. 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;
            }
        }
  3. 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()
            {
                // This constructor would not be called. 
                // Since we use a ImportingConstructor attribute
            }
            
            [ImportingConstructor]    
            public A(string str)
            {   
                message = str;
            }
        }
  4. 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()
            {
    //  This constructor would not be called. 
    //  Since we use a ImportingConstructor attribute
            }
            
            [ImportingConstructor]    
            public A([Import("Msg")]string str)
            {   
                message = str;
            }
        }
  5. Finally, add a method, say show to the class and our class is ready.

    [Export]
    class A
        {
            public string message;
            
            public A()
            {
    //  This constructor would not be called. 
    //  Since we use a ImportingConstructor attribute
            }
            
            [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
    {
        //This part will not be discovered
        //by the catalog.
    }

    [PartNotDiscoverable]
    [Export]
    public class B
    {
        //This part will also not be discovered
        //by the catalog.
    }

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(); // Creating instance of Program
            var cat = new AssemblyCatalog(typeof(Program).Assembly); // Creating catalog
            var container = new CompositionContainer(cat);
            container.ComposeParts(instance); // Compose parts by passing the 
					// Program 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(); // Get Value using Lazy instantiation
            }
            Console.ReadLine();
        }
        [ImportMany]
        public Lazy<iplug, />[] Plugins 
        { 
            get; 
            private set; 
        } // Use of Lazy Instantiation for Plugins
    }

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here