Custom AssemblyLoadContext failing to load Microsoft.AspNetCore.Components

0

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?

c#
asp.net-core
.net-core
asked on Stack Overflow Oct 25, 2019 by Bryan S • edited Oct 28, 2019 by Bryan S

2 Answers

0

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.

answered on Stack Overflow Oct 25, 2019 by Airizzo
0

I believe the library might also need to be built with the target framework set to .NetCore 3.0 (netcoreapp3.0).

answered on Stack Overflow Oct 28, 2019 by bit7

User contributions licensed under CC BY-SA 3.0