Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Managed Extensibility Framework: Part 2

4.94/5 (10 votes)
23 Jun 2009CPOL4 min read 46.5K   735  
Managing information flow between an extension and an application.

Introduction

This is in continuation with my previous post in this series. Part 1 covered the problem MEF tried to solve. In this part, we will see how MEF solves the problem scenario described in Part 1.

The example covers the scenario where information flows from application to an extension as well as from an extension to the application. In MEF terms, an extension is a composable part or simply a part. I chose to exchange a custom class instead of built-in types like string or int. I intended to cover the exchange of events as well. But, I will hold the events for the next part.

Background

At the end of Part 1, I showed a working code that could be used as a managed extension. But, that solution is at a lower level and is not so very flexible. MEF provides discoverable catalogs to search parts from various sources like DirectoryCatalog or AssemblyCatalog. And, the container takes the responsibility to co-ordinate the catalog to match export/import pairs. More information about this can be found at http://mef.codeplex.com/Wiki/View.aspx?title=Overview.

Example

The code for this example is available in three solutions. The console application can consume an extensible parts class library which contains the contract, and the interfaces class library contains one of the extensible parts that implements the contract. And, you just need one library from the MEF framework. As of now, MEF is all in one DLL named System.ComponentModel.Composition. You can download this framework from CodePlex. OK, let us implement these three solutions.

Console application which can consume an extensible part

Here, we build a console application which can consume an extensible part. This application even exports a part.

  1. Create a console application and call it "meflab1".
  2. Add a reference to System.ComponentModel.Composition.dll (from the bin directory of the downloaded zip file).
  3. Replace the code in Program.cs with the following snippet:
C#
using System;
using System.IO;
using System.Reflection;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
namespace meflab1
{
   class Program
   {
       [Import]
       public IGreetings Greetings { get; set; }

       static void Main(string[] args)
       {
           Console.WriteLine("Enter 0 to quit, any other number to continue");
           while (Console.ReadLine() != "0")
           {

               Program program = new Program();
               program.Compose();
               program.Run();
           }
       }

       void Compose()
       {
           try
           {
               DirectoryCatalog catalog =
                   new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory);

               AggregateCatalog agcatalogue = 
                   new AggregateCatalog(new ComposablePartCatalog[] {catalog,
                   new AssemblyCatalog(Assembly.GetExecutingAssembly())});

               CompositionContainer container = new CompositionContainer(agcatalogue);

               CompositionBatch batch = new CompositionBatch();

               batch.AddPart(this);

               container.Compose(batch);
           }
           catch (FileNotFoundException fnfex)
           {
               Console.WriteLine(fnfex.Message);
           }
           catch (CompositionException cex)
           {
               Console.WriteLine(cex.Message);
           }
       }

       void Run()
       {
           if (Greetings != null)
           {
               Console.WriteLine(Greetings.SayHello());
           }
           Console.Read();
       }
   }
}

Add a class by name SimpleGreeting and replace the code in SimpleGreeting.cs with the following snippet:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;

namespace meflab1
{
   [Export(typeof(IContextInfo))]
   public class UserInfo : IContextInfo
   {
       public IDictionary<string,> GetContextInfo()
       {
           return new Dictionary<string,> { { "UserName",
                  Environment.UserDomainName + "\\" + Environment.UserName } };
       }
   }
}

Save this solution.

Class library which contains the contract, and interfaces

  1. Create a class library and call it "meflibrary".
  2. Rename Class1.cs to Contract.cs.
  3. Now, replace the contents of Contract.cs with the following snippet:

We are just defining two interfaces here:

C#
using System.Collections.Generic;
namespace meflab1
{
   public interface IContextInfo
   {
       IDictionary<string,> GetContextInfo();
   }

   public interface IGreetings
   {
       string SayHello();
   }
}

Build it and add a reference of this class library to the application we built in the previous section.

Class library which contains one of the extensible parts that implements the contract

  1. Create a class library and call it "mefpart".
  2. Add a reference to System.ComponentModel.Composition.dll (from the bin directory of the downloaded zip file).
  3. Rename Class1.cs to SimpleGreeting.cs.
  4. Now, replace the contents of SimpleGreeting.cs with the following snippet:

Here, we are implementing one of the extensible parts:

C#
using System.ComponentModel.Composition;

namespace meflab1
{
   [Export(typeof(IGreetings))]
   public class SimpleGreeting : IGreetings
   {
       [Import(typeof(IContextInfo))]
       public IContextInfo ContextInfo { get; set; }

       public string SayHello()
       {
           string userName;
           var props = ContextInfo.GetContextInfo();
           props.TryGetValue("UserName", out userName);
           return "Hello " + (userName ?? "<null>") + 
                  " from Visual Studio 2010";
       }
   }
}

Build it!

Having this infrastructure in place lets us do two exercises.

  1. Run the application without the dependent assembly.
  2. Run the application without the dependent assembly during start up, but make it available before JIT compiling.

Run the application without the dependent assembly

Now, build the first solution and try to run it.

  1. Open a command prompt.
  2. Navigate to the output folder of the first solution, start mefpart.exe.
  3. Application will be waiting at "Enter 0 to quit, any other number to continue".
  4. Enter any non-zero value.

We end up with the following error:

The composition produced a single composition error. The root cause is provided below.
Review the CompositionException.Errors property for more detailed inf
ormation.

1) No exports were found that match the constraint
'((exportDefinition.ContractName = "meflab1.IGreetings") && 
  (exportDefinition.Metadata.ContainsKey("Expor
tTypeIdentity") && "meflab1.IGreetings".Equals(exportDefinition.Metadata.get_Item
("ExportTypeIdentity"))))'.

Resulting in: Cannot set import 'meflab1.Program.Greetings
(ContractName="meflab1.IGreetings")' on part 'meflab1.Program'.
Element: meflab1.Program.Greetings (ContractName="meflab1.IGreetings")
--> meflab1.Program

This is expected as we don't have any library implementing the IGreetings interface.

Run application without dependent assembly during start up, but make it available before JIT compiling

  1. Open a command prompt.
  2. Navigate to the output folder of the first solution, start mefpart.exe.
  3. Application will be waiting at "Enter 0 to quit, any other number to continue"
  4. Copy mefpart.dll from the output directory of mefpart's solution to the output directory of meflab1. That is nothing but the current directory of the command prompt.
  5. Enter any non zero value.

Observe that the application could seamlessly load the part and execute the code in it. So, what didn't work in part-1 works now.

I will explain the exchange of events and some scenarios (point 2 in the next section) where MEF fails and why it fails to load, in my next post.

Points of interest

  1. Here, AggregateCatalog is used because the application exports 'UserInfo', and thus even the current assembly needs to be added to the catalog.
  2. If the contract is not built as a library and if the interfaces are defined as the source, the following error is observed:
  3. Unhandled Exception: System.Reflection.ReflectionTypeLoadException: 
      Unable to load one or more of the requested types. 
      Retrieve the LoaderExceptions property for more information.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)