MEF: "Unable to load one or more of the requested types. Retrieve the LoaderExceptions for more information"
Asked Answered
A

3

20

Scenario: I am using Managed Extensibility Framework to load plugins (exports) at runtime based on an interface contract defined in a separate dll. In my Visual Studio solution, I have 3 different projects: The host application, a class library (defining the interface - "IPlugin") and another class library implementing the interface (the export - "MyPlugin.dll").

The host looks for exports in its own root directory, so during testing, I build the whole solution and copy Plugin.dll from the Plugin class library bin/release folder to the host's debug directory so that the host's DirectoryCatalog will find it and be able to add it to the CompositionContainer. Plugin.dll is not automatically copied after each rebuild, so I do that manually each time I've made changes to the contract/implementation.

However, a couple of times I've run the host application without having copied (an updated) Plugin.dll first, and it has thrown an exception during composition:

Unable to load one or more of the requested types. Retrieve the LoaderExceptions for more information

This is of course due to the fact that the Plugin.dll it's trying to import from implements a different version of IPlugin, where the property/method signatures don't match. Although it's easy to avoid this in a controlled and monitored environment, by simply avoiding (duh) obsolete IPlugin implementations in the plugin folder, I cannot rely on such assumptions in the production environment, where legacy plugins could be encountered.

The problem is that this exception effectively botches the whole Compose action and no exports are imported. I would have preferred that the mismatching IPlugin implementations are simply ignored, so that other exports in the catalog(s), implementing the correct version of IPlugin, are still imported.

Is there a way to accomplish this? I'm thinking either of several potential options:

  • There is a flag to set on the CompositionContainer ("ignore failing imports") prior to or when calling Compose
  • There is a similar flag to specify on the <ImportMany()> attribute
  • There is a way to "hook" on to the iteration process underlying Compose(), and be able to deal with each (failed) import individually
  • Using strong name signing to somehow only look for imports implementing the current version of IPlugin

Ideas?

Acceptor answered 26/10, 2010 at 3:42 Comment(0)
C
15

I have also run into a similar problem.

If you are sure that you want to ignore such "bad" assemblies, then the solution is to call AssemblyCatalog.Parts.ToArray() right after creating each assembly catalog. This will trigger the ReflectionTypeLoadException which you mention. You then have a chance to catch the exception and ignore the bad assembly.

When you have created AssemblyCatalog objects for all the "good" assemblies, you can aggregate them in an AggregateCatalog and pass that to the CompositionContainer constructor.

Credible answered 26/10, 2010 at 13:55 Comment(5)
I'll have a look. And yes, I am sure I want to ignore them - I can't utilize them anyway, and they mess up for all the ones I DO want, so that's a no-brainer, no? :)Acceptor
@d7samurai: Some people will end up here by googling the exception message. I just wanted to emphasize for them that this doesn't fix the underlying cause of the error. Also, introducing silent failure (aka "on error resume next" behavior) is not exactly a no-brainer; it can vastly complicate bug detection and diagnosis. I recommend to at least give some indication to the user that something bad happened.Credible
Of course. By "ignoring" I mean "moving on without letting it stop the whole import process". There is no user, as this is a Windows Service, but everything this (bootstrapper) does is logged, both normal operations and exceptions (including dumping all exceptions in "LoaderExceptions" - when that is applicable).Acceptor
Also, in this case, the purpose of the import is to detect all available (engine) plugins, to subsequently pick the most eligible one. And there will always be one, since a baseline engine is embedded in the bootstrapper itself, in case no better/newer/compatible engines are found externally.Acceptor
Great answer. Solved my problem. This way, I simply found out the corrupt DLL and solved it.Sergias
O
9

This issue can be caused by several factors (any exceptions on the loaded assemblies), like the exception says, look at the ExceptionLoader to (hopefully) get some idea

Another problem/solution that I found, is when using DirectoryCatalog, if you don't specify the second parameter "searchPattern", MEF will load ALL the dlls in that folder (including third party), and start looking for export types, that can also cause this issue, a solution is to have a convention name on all the assemblies that export types, and specify that in the DirectoryCatalog constructor, I use *_Plugin.dll, that way MEF will only load assemblies that contain exported types

In my case MEF was loading a NHibernate dll and throwing some assembly version error on the LoaderException (this error can happen with any of the dlls in the directory), this approach solved the problem

Octennial answered 2/12, 2011 at 0:31 Comment(1)
Hi @Octennial , Thanks for the tip on the search pattern. Solved my problem.Torero
B
7

Here is an example of above mentioned methods:

var di = new DirectoryInfo(Server.MapPath("../../bin/"));

        if (!di.Exists) throw new Exception("Folder not exists: " + di.FullName);

        var dlls = di.GetFileSystemInfos("*.dll");
        AggregateCatalog agc = new AggregateCatalog(); 

        foreach (var fi in dlls)
        {
            try
            {
                var ac = new AssemblyCatalog(Assembly.LoadFile(fi.FullName));
                var parts = ac.Parts.ToArray(); // throws ReflectionTypeLoadException 
                agc.Catalogs.Add(ac);
            }
            catch (ReflectionTypeLoadException ex)
            {
                Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
            }
        }

        CompositionContainer cc = new CompositionContainer(agc);

        _providers = cc.GetExports<IDataExchangeProvider>();
Bookcraft answered 17/10, 2012 at 19:3 Comment(1)
This doesn't work for ASP.NET MVC if you are not using a separate domain, because Assembly.LoadFile loads the file to a different domain. To solve it you just need to use Assembly.LoadFrom.Emory

© 2022 - 2024 — McMap. All rights reserved.