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.
- Create a console application and call it "meflab1".
- Add a reference to System.ComponentModel.Composition.dll (from the bin directory of the downloaded zip file).
- Replace the code in Program.cs with the following snippet:
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:
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
- Create a class library and call it "meflibrary".
- Rename Class1.cs to Contract.cs.
- Now, replace the contents of Contract.cs with the following snippet:
We are just defining two interfaces here:
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
- Create a class library and call it "mefpart".
- Add a reference to System.ComponentModel.Composition.dll (from the bin directory of the downloaded zip file).
- Rename Class1.cs to SimpleGreeting.cs.
- Now, replace the contents of SimpleGreeting.cs with the following snippet:
Here, we are implementing one of the extensible parts:
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.
- Run the application without the dependent assembly.
- 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.
- Open a command prompt.
- Navigate to the output folder of the first solution, start mefpart.exe.
- Application will be waiting at "Enter 0 to quit, any other number to continue".
- 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
- Open a command prompt.
- Navigate to the output folder of the first solution, start mefpart.exe.
- Application will be waiting at "Enter 0 to quit, any other number to continue"
- 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.
- 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
- Here,
AggregateCatalog
is used because the application exports 'UserInfo', and thus even
the current assembly needs to be added to the catalog. - If the contract is not built as a library and if the interfaces are defined as the source, the following error is observed:
Unhandled Exception: System.Reflection.ReflectionTypeLoadException:
Unable to load one or more of the requested types.
Retrieve the LoaderExceptions property for more information.