2
votes

Thanks in advance for reviewing this question!

I am using MEF to load some assemblies in a project. Everything was working great until we changed the name of the file that contains the interface.

To make it clearer, I'll summarize the scenario where things worked, then the scenario where things did not work, then specifically show the exception and the code that caused the exception in the scenario that didn't work.

Here's the working scenario:

We've got an interface called IPlugin that is defined in an assembly called Common-1-0.dll.

We have some plugin assemblies that are compiled against Common-1-0.dll.

The application that loads the plugin is compiled against Common-1-0.dll.

Here's the non working scenario:

We've got this an called IPlugin that is defined in an assembly called Common-1-1.dll. The interface has not changed from Common-1-0.dll.

We have some plugin assemblies that are compiled against Common-1-0.dll.

The application that loads the plugin is compiled against Common-1-1.dll.

Now the issue:

When I go to run this code below in the second scenario, I get a CompositionException (shown under the code below). It appears to be caused because the the plugin was compiled against Common-1-0.dll while the application trying to do the composition was compiled against Common-1-1.dll. Nothing within the code has changed between the two files, just the name.

So we'd like to be able to load plugins built against whatever assembly so long as that assembly exports the right interface but I'm not sure if I can do that with MEF or not. That is what I'd like to know as a result of this question.

Code :

    private void LoadPlugins(string directory, string searchPattern = "", bool recursive = false)
    {
        Trace.Agent.Status("Loading plugin(s) from {0}{1}{2}", directory, Path.DirectorySeparatorChar, searchPattern);

        try
        {
            var directoryCatalog = string.IsNullOrEmpty(searchPattern)
                                           ? new DirectoryCatalog(directory)
                                           : new DirectoryCatalog(directory, searchPattern);
            _container = new CompositionContainer(new AggregateCatalog(directoryCatalog));

            _container.ComposeParts(this);
        }
        catch (CompositionException exc)
        {
            Trace.Agent.Exception(exc);
        }

        if (recursive)
        {
            foreach (string dir in Directory.GetDirectories(directory))
            {
                LoadPlugins(Path.Combine(directory, dir), recursive:true);
            }
        }
    }

CompositionException :

The export 'TestPlugin.TestPlugin (ContractName="Common.IPlugin")' is not assignable to type 'Common.IPlugin'.

1
did you try to use assembly binding redirects? - MichaC
I am not familiar with this and did not try it, but I will look into it. - MadmanWilson
Do you think an assembly binding redirect would work if the name of the assembly is actually different? It looks fairly easy to specify a common name with that and have it use an updated version but is there an example of a case where you say when you see 'common-1-0.dll' use 'common-1-1.dll'? - MadmanWilson
it doesn't look for the name of the dll, it will look for the publicKeyToken. You would have to sign your assembly and compile both versions of the assembly with the same key, so that the publicKeyToken doesn't change. This way you can define a redirect from version 0.0.0.0-1.1.0.0 to 1.1.0.0 which means everything should use the new version. - MichaC
ok sorry, of course the assembly name must be equal for both dlls. So if you really have different names for those assemblies (suffixed with version) your are f... - MichaC

1 Answers

2
votes

I think I found a solution for your issue, it is a little bit hacked but anyways.

So if you have named your assemblies differently, e.g. assembly1.0 and assembly1.1 this causes issues because you cannot simply redirect the assembly to a new version. Usually you would just keep the same assembly name and increase the version number. This way you can redirect without any issues (as long as the code supports it).

The solution is to hack into the assembly resolve mechanism by attaching to the AssemblyResolve event of the current AppDomain.

        AppDomain currentDomain = AppDomain.CurrentDomain;

        currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);

        LoadPlugins(currentDomain.BaseDirectory);

        var x = _container.GetExport<IPlugin>().Value;

within the event handler you can simply return the "new" assembly

    private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
    {
        if (args.Name.StartsWith("PluginCore1.0"))
        {
            return typeof(IPlugin).Assembly;
        }
        return null;
    }

This works, also without signed assemblies (without public key tokens).

To trigger the resolve event you still have to define the assembly redirection within your app.config though:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
    <assemblyIdentity name="PluginCore1.0"
                      culture="neutral" />

    <bindingRedirect oldVersion="1.0.0.0" newVersion="1.1.0.0" />
  </dependentAssembly>
</assemblyBinding>

Again, I would strongly recommend to use the same assembly name (without the version suffix) and simply use the assembly redirection mechanism which should work just fine.