I'm using MEF in my WebApi project to load plugins located in a folder other than the bin folder. I do the following:
var directoryCatalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins"));
var container = new CompositionContainer(directoryCatalog);
container.ComposeParts();
IList<IPlugin> plugins = container.GetExportedValues<IPlugin>().ToList();
Then I'm setting up a plugin by doing:
plugins[0].Startup(logService, unitOfWork);
Note: I'm not using [ImportingConstructor]
to pass the above dependencies because I'm using existing instances already instantiated in the controller.
Then I schedule the job as follows:
BackgroundJob.Schedule(() => plugins[0].Start(), new TimeSpan(0, 0, 1));
However, I get the following exception when Hangfire tries to start the job:
Could not load file or assembly 'App.Plugins.FirstPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Is it even possible to use MEF and Hangfire together in this kind of scenario? If yes, what is the correct procedure?
NOTE: If the plugin DLL's are located in the same bin folder as the main app, Hangfire works fine. But this defeats the purpose to have a separate plugins folder.
You need to guide hangfire (or rather - .NET itself) for the location of this assembly by subscribing to AppDomain.AssemblyResolve
event (somewhere before starting hangfire server):
var pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins");
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
var asmName = new AssemblyName(args.Name);
var plugin = Path.Combine(pluginPath, asmName.Name + ".dll");
if (File.Exists(plugin))
return Assembly.LoadFrom(plugin);
return null;
};
Since your plugins are actually already loaded into current app domain (by MEF), the following should also work (and I think might be better than above):
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
var asmName = new AssemblyName(args.Name);
var existing = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(c => c.FullName == asmName.FullName);
if (existing != null) {
return existing;
}
return null;
};
That's necessary because hangfire, to deserialize instance of your plugin from state in database, uses Type.GetType
providing assembly qualified name, and because of certain complications which (I think) are not relevant for this question - Type.GetType
will not find your plugin assembly, even though it is already loaded by MEF into current app domain, so you have to help it a bit.