Edit: I have uploaded the source code for the issue to GitHub if you would like to download: https://github.com/bryanenroute/assemblyloadcontext-issue
I have a .NET Core 3.0 console application that references a .NET Standard 2.0 class library with a single interface (IModule). I also have a ASP.NET Core 3.0 application that references the same .NET Standard 2.0 class library and implements the interface (Module : IModule).
I am trying to load the ASP.NET Core assembly from the .NET Core console application using a custom AssemblyLoadContext and a common class library interface (IModule)... a simple plugin system.
Unfortunately, the ASP.NET Core module/plugin fails in the ALC override function for Load(AssemblyName) with the following exception:
Could not load file or assembly 'Microsoft.AspNetCore.Components, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
When I try with a different project type (e.g. .NET Core Console Application or .NET Standard 2.0 Class Library), the module/plugin loads as intended.
Here's the Console app code:
using NetStandardCommon;
using System;
using System.IO;
namespace NetCoreConsoleApp
{
class Program
{
static void Main(string[] args)
{
LoadNetCoreModule();
LoadAspNetCoreModule();
}
static void LoadNetCoreModule()
{
//Works!
FileInfo asm = new FileInfo(@"..\..\..\..\NetCoreModule\bin\debug\netcoreapp3.0\NetCoreModule.dll");
var moduleDirectory = asm.DirectoryName;
ModuleAssemblyLoadContext context = new ModuleAssemblyLoadContext(asm.Name, moduleDirectory, typeof(IModule));
context.Scan();
foreach (var module in context.GetImplementations<IModule>())
{
module.Start();
}
}
static void LoadAspNetCoreModule()
{
//Fails!
FileInfo asm = new FileInfo(@"..\..\..\..\AspNetCoreApp\bin\debug\netcoreapp3.0\AspNetCoreApp.dll");
var moduleDirectory = asm.DirectoryName;
ModuleAssemblyLoadContext context = new ModuleAssemblyLoadContext(asm.Name, moduleDirectory, typeof(IModule));
context.Scan();
foreach (var module in context.GetImplementations<IModule>())
{
module.Start();
}
}
}
}
Here's the ModuleAssemblyLoadContext code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using System.Linq;
namespace NetCoreConsoleApp
{
public class ModuleAssemblyLoadContext : AssemblyLoadContext
{
private List<Assembly> _loaded;
private Dictionary<string, Assembly> _shared;
private string _path;
private AssemblyDependencyResolver _resolver;
public ModuleAssemblyLoadContext(string name, string path, params Type[] sharedTypes) : base(name)
{
_path = path;
_resolver = new AssemblyDependencyResolver(_path);
_loaded = new List<Assembly>();
_shared = new Dictionary<string, Assembly>();
if (sharedTypes != null)
{
foreach (Type sharedType in sharedTypes)
{
_shared[Path.GetFileName(sharedType.Assembly.Location)] = sharedType.Assembly;
}
}
}
public void Scan()
{
foreach (string dll in Directory.EnumerateFiles(_path, "*.dll"))
{
var file = Path.GetFileName(dll);
if (_shared.ContainsKey(file))
{
continue;
}
var asm = this.LoadFromAssemblyPath(dll);
_loaded.Add(asm);
}
}
public IEnumerable<T> GetImplementations<T>()
{
return _loaded
.SelectMany(a => a.GetTypes())
.Where(t => typeof(T).IsAssignableFrom(t))
.Select(t => Activator.CreateInstance(t))
.Cast<T>();
}
protected override Assembly Load(AssemblyName assemblyName)
{
string filename = $"{assemblyName.Name}.dll";
if (_shared.ContainsKey(filename))
{
return _shared[filename];
}
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
}
I tried modifying the ALC Load function to load the assemblies directly from the shared folder (C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\3.0.0) which lets the execution continue a bit farther, but it ultimately fails with the following exception:
An attempt was made to load a program with an incorrect format. (0x8007000B)
Here's the revised Load function:
protected override Assembly Load(AssemblyName assemblyName)
{
string filename = $"{assemblyName.Name}.dll";
if (_shared.ContainsKey(filename))
{
return _shared[filename];
}
try
{
if (File.Exists(@"C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\3.0.0\" + filename))
{
return Assembly.LoadFrom(@"C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\3.0.0\" + filename);
}
}
catch (Exception ex)
{
//Message displayed is 'An attempt was made to load a program with an incorrect format. (0x8007000B)'
Console.WriteLine(ex.Message);
}
return Assembly.Load(assemblyName);
}
I'm excited about the possibilities of loading/unloading assemblies for a .net Core plugin system, but I'm struggling to get over this hurdle. What am I missing?
I had this issue about a month ago with loading a Assembly to my SQL server. Are you using virtual drives to store your Assembly? I found out that our share drive actual drive path was a E drive and not a P drive which is what is mapped on my computer. I was virtually connected to it, so I had to give the real Drive path which started with E instead of P. Also, your program might be mapping it to the wrong drive as well. I would check that, and if that doesn't help I have about 3-4 more things to try as far as this particular issue in concerned.
I believe the library might also need to be built with the target framework set to .NetCore 3.0 (netcoreapp3.0).
User contributions licensed under CC BY-SA 3.0