In the previous part of the series I gave you a very brief overview of the Managed Extensibility Framework and about how it is used by the editor extensibility in Visual Studio 2010. As an extensibility guy, I am interested in writing new extensions for the editor, and because it is built on MEF, I think it is worth getting familiar with MEF concepts and their application.
If you want to get more detailed information about MEF, you should visit the MEF Homepage. I really like MEF and all the information collected on this page, but I really miss information that helps with positioning MEF in order developers can put it into their everyday toolset and small applications demonstrating the basic concepts.
However, I’ve seen a very good presentation at TechEd 2009 North America from Jason Olson about MEF that helped me a lot in understanding the basics. Unfortunately, the video is available only for registered attendees…
In this article I am going to create some very basic applications using MEF—this is where the title comes from J—and treat the details in order you can get familiar with the concepts.
The Greetings Sample
Let’s start our first tour with a simple console application—an “advanced” version of the “Hello World” application where the greeting text is decoupled from the console application class outputting it. This application is simple, and this time we do not use MEF yet:
// --- Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GreetingsWithMEF
{
class Program
{
static void Main(string[] args)
{
var greetings = new SimpleGreeting();
Console.WriteLine(greetings.SayHello());
}
}
}
// --- IGreetings.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GreetingsWithMEF
{
interface IGreetings
{
string SayHello();
}
}
// --- SimpleGreeting.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GreetingsWithMEF
{
class SimpleGreeting : IGreetings
{
public string SayHello()
{
return "Hello from Visual Studio 2010";
}
}
}
Although, the greeting text is decoupled from the console class through the IGreetings interface, we directly instantiate the SimpleGreeting class, so we cannot speak about a loosely-coupled implementation. Now, let us turn this console application to a “MEFish” one.
MEF and composition
The first step we have to do is adding a reference to the System.ComponentModel.Composition assembly that holds the MEF types we are going to use. Now we can separate the greeting component from the console class in four steps:
Step 1: Decorate the SimpleGreeting class with the Export attribute
Step 2: Declare a catalog to search the application parts in
Step 3: Create a composition container using the catalog
Step 4: Query the part represented by IGreetings from the composition container
The resulting application looks like this; I highlighted the most important changes:
// --- IGreeting.cs: Nothing changed
// --- SimpleGreeting.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace GreetingsWithMEF
{
[Export(typeof(IGreetings))] // --- Step 1
class SimpleGreeting : IGreetings
{
public string SayHello()
{
return "Hello from Visual Studio 2010";
}
}
}
// --- Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel.Composition.Hosting;
namespace GreetingsWithMEF
{
class Program
{
static void Main(string[] args)
{
// --- Step 2
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
// --- Step 3
var container = new CompositionContainer(catalog);
// --- Step 4
var greetings = container.GetExportedObject<IGreetings>();
Console.WriteLine(greetings.SayHello());
}
}
}
When you run this code by Ctrl+F5 it displays the expected greeting message. You can see from the sample that here we do not directly instantiate the SimpleGreeting type, we even do not use its name!
The type of catalog is AssemblyCatalog that scans through an assembly—in our case—the console application assembly to find all parts in the catalog. This time we have only one part, the SimpleGreeting part. The composition container is the matchmaker that uses the specified catalog to bind parts with each other and the hosting app as required. In this case we have only one part in this catalog, but we could have more.
The little cogs of the composition container start spinning up when we call the GetExportedObject<IGreetings> method that makes all the matches among the parts in the composition container’s catalog and retrieves an instance of the part we request—this time a part exporting the IGreetings contract. Of course this time the composition container does not have to invest too much work into matchmaking, because we have only one part in the catalog. However, do not underrate the role of the container, because in case of many parts it has a really hard job!
Playing with the contract
One of the core concepts of MEF is the contract that has the same semantics as generally used in object-oriented design or in WCF or in distributed systems. In MEF the contract is the binding link between a part proffering a (service) end-point and a part requesting an end-point. In is obvious that the composition process is based on contracts: this is the core concepts used when making the match among parts.
To get a feeling about contracts and their usage in MEF, let’s play with them. Just to make something crazy, change the Export attribute of the SimpleGreeting class to System.Int32:
[Export(typeof(System.Int32))]
class SimpleGreeting : IGreetings { ... }
Now, when trying to run this little app, you will get an ImportCardinalityMismatchException with the following message: “No exports were found that match the constraint '((exportDefinition.ContractName == "GreetingsWithMEF.IGreetings") AndAlso …”
What this message says is—actually it tells much more—that our composition container was unable to find a part with the IGreetings contract we have used in the GetExportedObject<IGreetings> call. This is natural, because we changed the contract type of our SimpleGreeting class to System.Int32 and even if this class implements IGreetings, not this is the way to match parts.
In the sample above we identified the contract with a type. MEF uses strings to identify contracts. The composition engine uses this string and a so-called type identity when binding parts. So let’s temporarily add a new class called AnotherSimpleGreeting to the console application and let’s change the Export attributes like this:
[Export("FirstContract", typeof(IGreetings))]
class SimpleGreeting : IGreetings
{
public string SayHello()
{
return "Hello from Visual Studio 2010";
}
}
[Export("SecondContract", typeof(IGreetings))]
class AnotherSimpleGreeting : IGreetings
{
public string SayHello()
{
return "Another Hello from Visual Studio 2010";
}
}
Now, we have two parts using the IGreetings type identifier in the contracts with names “FirstContract” and “SecondContract”. We can tell the composition engine which contract to use by naming it explicitly:
var greetings = container.GetExportedObject<IGreetings>("SecondContract");
This time when you run the sample you can recognize that the AnotherSimpleGreeting part gets bound to the host application. I know, at this point you may have questions emerging about how the composition engine works and how contracts are exactly identified. In this article we won’t treat these details, but be patient, in a future article I definitely will handle these questions.
One small remark: I have told you that the GetExportedObject call spins up the composition engine; this is where matchmaking is done to bind together the host app with the appropriate parts. From this sample where we have two contract, it can be seen that matchmaking really happens this time and not at the time when the composition container is created. While GetExportedObject is not called, the host application does not declare the contract to be used for binding.
Now, let’s add some salt to the soup! Remove the strings from the Export attributes and use only the types to set up the definition; and also remove the string parameter from the GetExportedObject call. This time we have two parts satisfying the IGreetings contract. The container cannot select the one we are looking for and throws an ImportCardinalityMismatchException with the following message: “More than one exports were found that match the constraint '((exportDefinition.ContractName == "GreetingsWithMEF.IGreetings") AndAlso …”.
I’m sure you are not surprised. The message tells just like what you would have said.
Parts Accessing Host Application End-points
Undoubtedly the sample we have seen is really simple: a host application loads independent parts at runtime and consumes services. In the real life generally parts also can access the host application’s endpoints and consume their services. So, let’s see how we can do this with MEF!
Let’s change the configuration so that our greeting component can ask the host application for context information defined by the following contract type:
// --- IContextInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GreetingsWithMEF
{
public interface IContextInfo
{
IDictionary<string, string> GetContextInfo();
}
}
The GetContextInfo method will provide a dictionary of key and value pairs where—just for simplicity—keys and values are both strings. Let’s create a type named UserInfo that implements this contract, and mark it with the Export attribute:
// --- UserInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace GreetingsWithMEF
{
[Export(typeof(IContextInfo))]
class UserInfo: IContextInfo
{
public IDictionary<string, string> GetContextInfo()
{
return new Dictionary<string, string>
{ {"UserName", Environment.UserName } };
}
}
}
We change the declaration of SimpleGreeting so that now it will include a property that Imports the IContextInfo contract, and we change the SayHello method to use that contract:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace GreetingsWithMEF
{
[Export(typeof(IGreetings))] // --- Step 1
class SimpleGreeting : IGreetings
{
[Import(typeof(IContextInfo))]
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";
}
}
}
The Main method of our application remains the same:
static void Main(string[] args)
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
var greetings = container.GetExportedObject<IGreetings>();
Console.WriteLine(greetings.SayHello());
}
When you run the app, it will work as expected! So what is going behind the scenes? Actually, nothing new happens compared to the previous runs. When the GetExportedObject method is called it instantiates the appropriate IGreetings-implementing type. The composition container recognizes that SimpleGreeting wants to access a part with the IContextInfo contract. The container sees that UserInfo is the only type implementing this and constructs a UserInfo instance, sets the SimpleGreeting instance’s ContextInfo property to this newly constructed one. Now, the SimpleGreeting instance is set up and so it is passed back to the host application for consumption.
Where are we?
In this article we have seen a very simple example of using MEF to dynamically compose a simple application from a host and a one or two parts. This composition is based on contracts. Parts can declare themselves as end-point providers for the contract (Export attribute) or sign that they require parts with the contract (Import attribute). MEF uses catalogs to enumerate all candidate parts contributing in an application. The composition process is carried out by so-called composition containers.
Posted
May 28 2009, 08:07 AM
by
inovak