Migrating from MEF1 to MEF2
jeikabu
Posted on August 17, 2018
N.B. I originally wrote the bulk of this text 2017/12/28 and am posting it now since it was useful at the time.
While moving part of our codebase to .Net Standard 2.0 so everything runs atop .Net Core, we had to deal with moving from MEF1 (which is not in .Net Core) to MEF2 (which is).
This blog post got me started, or at least validated that the move was possible, rather. But, it didn’t really have the details matching our usage.
Packages
The simplest changes involved is moving from .NetFramework:
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
To packages available from nuget:
using System.Composition;
using System.Composition.Hosting;
It’s worth mentioning that System.ComponentModel.DataAnnotations
can be used as-is, but the package still has to be downloaded from nuget.
Code Changes
It seems that several other people are dealing with similar issues, because there were a few StackOverflow questions that were helpful:
- https://stackoverflow.com/questions/41784393/how-to-emit-a-type-in-net-core
- https://stackoverflow.com/questions/24935261/mef2-how-to-get-metadata-from-compositionhost-export-with-convention-based-impor
- https://stackoverflow.com/questions/13914256/strongly-typed-metadata-in-mef2-system-composition
- https://stackoverflow.com/questions/10988447/mef-getexportst-tmetadataview-returning-nothing-with-allowmultiple-true
We had a DynamicLinq.cs:
AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
// SNIP
Type result = tb.CreateType();
That needed to get changed to:
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
// SNIP
Type result = tb.CreateTypeInfo();
A more complicated case is how we load the assemblies, first via MEF1:
CompositionContainer container;
[ImportMany(typeof(IWorker))]
IEnumerable<Lazy<IWorker, IWorkerMetadata>> workerPlugins { get; set; }
public void Init(string path)
{
var files = Directory.EnumerateFiles(path, "*Service.dll", SearchOption.AllDirectories);
var catalog = new AggregateCatalog();
foreach (var file in files)
{
try
{
var asmCat = new AssemblyCatalog(file);
if (asmCat.Parts.ToList().Count > 0)
catalog.Catalogs.Add(asmCat);
}
catch (ReflectionTypeLoadException)
{
}
catch (BadImageFormatException)
{
}
}
container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
And then with MEF2:
CompositionHost container;
public void Init(string path)
{
var configuration = new ContainerConfiguration();
var files = Directory.EnumerateFiles(path, "*Service.dll", SearchOption.AllDirectories);
foreach (var file in files)
{
try
{
var asm = Assembly.LoadFrom(file);
configuration.WithAssembly(asm);
}
catch (ReflectionTypeLoadException)
{
}
catch (BadImageFormatException)
{
}
}
container = configuration.CreateContainer();
}
Both of these are simplified but should still be illustrative.
Side-by-Side
In the original post I decribed here how we were loading both MEF1 and MEF2 assemblies in the same application.
I thought that MEF1 was used with .Net Framework assemblies and MEF2 with .Net Standard. Anything that was incompatible with .Net Standard had to go in a Framework assembly- and therefore MEF1. I since realized MEF2 could load assemblies targetting either framework and have removed all our MEF1 code.
In any case, you can load both in an application. Which might be useful if you have a number of existing/legacy modules.
Todo
One of the tasks that’s been in my backlog for a while is re-visiting how we handle MEF2. In particular:
- Verifying our signed assemblies
- Checking meta-data before loading/initializing
When I do it will certainly result in a follow-up post.
Posted on August 17, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.