Mar 27, 2012

MEF 2 Preview - Discovering exports from previous versions of MEF

Anybody that's felt adventurous enough to try out a preview of the next version of MEF might have run into a problem: it only discovers exports built with the same version of MEF! This might not be a problem for you if you only have one or two projects in your solution. But what about external assembly dependencies? Even if you technically have the source for them in other projects/solutions, it can be a pain to go through them all and rebuild them against the new MEF assemblies. Plus, anybody wanting to use them against the built-in 4.0 MEF will be screwed, which necessitates forking the projects or something else. And what about those assemblies for which you can't build yourself?

In a project I've been working on, I had need for some of the new features of MEF 2 (as it is being called for now), but ran into the above situation. In my case, I had three assemblies I had brought in that I also wrote, but I didn't want to go through the hassle of rebuilding them with MEF 2, especially since I have other projects that expect them built against the .NET 4.0 MEF. What to do?

This is a problem that some folks ran across when they upgraded to .NET 4.0 and MEF was built-in. They had some assemblies that were built against the .NET 3.5 earlier versions of MEF and the 4.0 one was no longer finding all of their decorated types. It was technically possible, in some cases, to make it work, but not without some serious assembly loading voodoo. And I'm not sure the same incantation would work this time as we'd essentially be trying to resolve a non-GAC assembly with a different public key in place of a known, strong-named GAC assembly, instead of the other way around. Even so, it's neither elegant nor simple.

Thankfully, trying to load .NET 4.0 MEF types from MEF 2 is possible through a new type discovery style they've introduced. Previously, attribute decoration was the only way to go, but they've added a new convention-based style via the RegistrationBuilder class. The BCL Bros. cover this briefly in their Preview 4 update post (under the heading "Convention-based part registration") as well as in a follow-up post on the topic specifically.

We'll use this new style to discover exports from an assembly built against an earlier version of MEF.

var masterCatalog = new AggregateCatalog();

// Get types for our current assembly
// Nothing fancy needed here, as it is built against the newer MEF version
masterCatalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()));

// Here's where we create our convention rules to discover previous MEF version exports
var regBuilder = new RegistrationBuilder();
regBuilder.ForTypesMatching(t =>
    {
        if (t.Namespace.StartsWith("System"))
            return false;

        if (t.GetCustomAttributes(false).Any(o => o.GetType().Name.StartsWith("Export")))
            return true;

        return false;
    }).Export();
regBuilder.ForTypesMatching(t =>
    {
        if (t.Namespace.StartsWith("System"))
            return false;

        if (t.GetInterfaces().Any(i => i.GetCustomAttributes(false).Any(o => o.GetType().Name.StartsWith("InheritedExport"))))
            return true;

        return false;
    }).ExportInterfaces(
        i => i.GetCustomAttributes(false).Any(o => o.GetType().Name.StartsWith("InheritedExport")))
        .SelectConstructor(ctors =>
            {
                return ctors.DefaultIfEmpty(ctors.Where(ci => ci.GetParameters().Count() == 0).FirstOrDefault()).Where(ci => ci.GetCustomAttributes(false).Any(o => o.GetType().Name.StartsWith("ImportingConstructor"))).FirstOrDefault();
            });

// We add a second catalog for our second assembly; this one was built against .NET 4.0 MEF
//so we'll need to pass in the rules object from above
// You can specify any type, so long as it is a type defined in this second assembly
masterCatalog.Catalogs.Add(new AssemblyCatalog(typeof(My.NamespaceInOtherAssembly.MyType).Assembly, regBuilder));

var container = new CompositionContainer(masterCatalog);

// ... continue with normal application bootstrapping ...

Some caveats here, though. This code is at heart a hack, it could use some serious readability cleanup, and it is fairly naïve. It doesn't check for PartCreationPolicy attributes or any ExportMetadata, so types that try to use anything more than the simple Export or InheritedExport attributes will not have these extra settings. It is far better you either not mix MEF versions or you find a way to build all your assemblies against the same version. But if you need a quick-fix... I hope this helps!

No comments:

Post a Comment