Custom AssemblyLoadContext failing to load Microsoft.AspNetCore.Components - c#

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).

Related

Mixed assembly in a collectible (can unload) AssemblyLoadContext?

I need to be able to load and unload a mixed assembly library from my C# .net 6 program. I was previously using appdomains to do this and am trying to replicate in .net 6. Because we have to switch between different versions of a C++ mixed assembly library, I want to be able to unload the previous one before loading a new one.
From examples online I got the following code for creating an AssemblyLoadContext in .net 6, I am able to load my mixed assembly as long as isCollectible is set to false. If I set it to true I get the error: System.BadImageFormatException:
'Cannot load a mixed assembly into a collectible AssemblyLoadContext. The format of the file (name of the mixed c++ dll) is invalid.'
PluginLoadContext loadContext = new PluginLoadContext(pluginLocation);
AssemblyName assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation));
Assembly assembly = loadContext.LoadFromAssemblyName(assemblyName);
public class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath)
: base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return this.LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}

How to use a namespace from an Assembly loaded with Assembly.LoadFrom

Based on this question How to get Namespace of an Assembly?:
I can retrieve the namespace(s) from assembly with Assembly.GetTypes() but how can I provide this retrieved namespace file wide like using Namespace in first lines of C# file when loaded with "Add Reference" in project settings?
What is with other C# files? How can I provide this loaded namespace to them?
My approach:
// using NameSpaceFromLib; ??? how to do this with Assembly.LoadFrom
namespace Test
{
public class ClassA : DisposableObject
{
static ClassA()
{
AppDomain currentDomain = AppDomain.CurrentDomain;
// switch between 32 and 64 bit version
currentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomainAssemblyResolve);
}
private const string LibraryName = "LibToLoad";
private static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith(LibraryName, StringComparison.InvariantCultureIgnoreCase))
{
return LoadBitDepthDependentAssembly(LibraryName + ".dll");
}
return null;
}
private static Assembly LoadBitDepthDependentAssembly(string assemblyname)
{
var currentDirectory = Environment.CurrentDirectory;
try
{
var platformDirectory = Environment.Is64BitProcess ? "x64" : "x86";
var newPath = Helper.GetApplicationPath(platformDirectory);
Environment.CurrentDirectory = newPath;
var p = Path.Combine(newPath, assemblyname);
var assembly = Assembly.LoadFrom(p);
return assembly;
}
finally
{
Environment.CurrentDirectory = currentDirectory;
}
}
}
}
Thank you!
PS: I need this to distinguish during runtime if x86 or x64 variant of C++/CLI dll to load

Unable to load NuGet dll with platform specific dlls in netcoreapp

I am unable to load System.Data.Client dll from it's nuget package using the ICompilationAssemblyResolver I have available in a netcoreapp. The bulk of the Assembly resolving is borrowed from here, and works great for the most part. It looks like so:
internal sealed class AssemblyResolver : IDisposable
{
private readonly ICompilationAssemblyResolver assemblyResolver;
private readonly DependencyContext dependencyContext;
private readonly AssemblyLoadContext loadContext;
public AssemblyResolver(string path)
{
this.Assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
this.dependencyContext = DependencyContext.Load(this.Assembly);
this.assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
{
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(path)),
new ReferenceAssemblyPathResolver(),
new PackageCompilationAssemblyResolver()
});
this.loadContext = AssemblyLoadContext.GetLoadContext(this.Assembly);
this.loadContext.Resolving += OnResolving;
}
public Assembly Assembly { get; }
public void Dispose()
{
this.loadContext.Resolving -= this.OnResolving;
}
private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name)
{
bool NamesMatch(RuntimeLibrary runtime)
{
return string.Equals(runtime.Name, name.Name, StringComparison.OrdinalIgnoreCase);
}
RuntimeLibrary library =
this.dependencyContext.RuntimeLibraries.FirstOrDefault(NamesMatch);
if (library != null)
{
var wrapper = new CompilationLibrary(
library.Type,
library.Name,
library.Version,
library.Hash,
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
library.Dependencies,
library.Serviceable);
var assemblies = new List<string>();
this.assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies);
if (assemblies.Count > 0)
{
return this.loadContext.LoadFromAssemblyPath(assemblies[0]);
}
}
return null;
}
}
However I am unable to load a dll that references System.Data.Client # 4.3.1 as at runtime I get the error message:
Exception has occurred: CLR/System.IO.FileNotFoundException
An unhandled exception of type 'System.IO.FileNotFoundException'
occurred in System.Private.CoreLib.ni.dll: 'Could not load file or
assembly 'System.Data.SqlClient, Version=4.1.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.'
I am not sure why it is trying to load 4.1.0 when I have specified 4.3.0 but I think that is a bit of a red herring. I suspect that the PackageCompilationAssemblyResolver only looks under the lib folder, and the package in question does not have one for netstandard. It does however have one for specific runtimes:
Armed with this information I have created an incredibly crude AssemblyLoader that looks under the runtimes folder for a nuget package and I am able to load the dll and run my program as I expect.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
namespace Loader
{
public class CrudeCompilationAssemblyResolver : ICompilationAssemblyResolver
{
private readonly string[] _nugetPackageDirectories;
public CrudeCompilationAssemblyResolver()
{
var basePath = Environment.GetEnvironmentVariable("HOME");
var defaultPath = Path.Combine(basePath, ".nuget", "packages");
_nugetPackageDirectories = new [] { defaultPath };
}
public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> assemblies)
{
if (_nugetPackageDirectories == null || _nugetPackageDirectories.Length == 0 || !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
{
return false;
}
foreach (var directory in _nugetPackageDirectories)
{
string packagePath;
var fullPath = Path.Combine(directory, library.Name, library.Version, "runtimes", "unix", "lib", "netstandard1.3", $"{library.Name}.dll");
if (File.Exists(fullPath))
{
assemblies.AddRange(new[] { fullPath });
return true;
}
}
return false;
}
}
}
My question is: Is there a better/officially sanctioned way of loading this troublesome assembly from a nuget package? Or do I need to make my crude loader a lot less crude?
Full repo is here: CustomAssemblyResolver

Referencing a PCL library with Roslyn results in .NET version issues

I'm trying to use Roslyn to execute a block of code that references a PCL library. Both my console application and the PCL library are targeted to .NET 4.5
The syntax tree executes a method in the referenced library that constructs a library class. There should be no .NET 4.0 references.
(5,27): error CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Has anyone had issues with PCL and Roslyn, or got it to work before?
MyCompanyApplication:Program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.IO;
using System.Reflection;
namespace MyCompanyApplication
{
class Program
{
static void Main(string[] args)
{
EmitResult Result;
var Options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
CSharpCompilation Compilation = CSharpCompilation.Create(
assemblyName: Path.GetRandomFileName(),
syntaxTrees: new[] { CSharpSyntaxTree.ParseText(
#"class Test
{
public void Run(MyCompanyLibrary.Class Class)
{
var Label = Class.NewLabel();
}
}") },
references: new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(MyCompanyLibrary.Class).Assembly.Location),
},
options: Options);
Assembly Assembly = null;
using (var Stream = new MemoryStream())
{
Result = Compilation.Emit(Stream);
if (Result.Success)
Assembly = Assembly.Load(Stream.GetBuffer());
}
if (Result.Success)
{
var TestType = Assembly.GetType("Test");
var Instance = TestType.GetConstructor(new Type[0]).Invoke(new object[0]);
var RunMethod = TestType.GetMethod("Run");
RunMethod.Invoke(Instance, new object[] { new MyCompanyLibrary.Class() });
}
else
{
Console.WriteLine("Test (PCL) failed");
Console.ReadLine();
}
}
}
}
class Test
{
public void Run(MyCompanyLibrary.Class Class)
{
var Label = Class.NewLabel();
}
}
MyCompanyLibrary:Class.cs
namespace MyCompanyLibrary
{
public class Class
{
public Class()
{
}
public Label NewLabel()
{
return new Label(this);
}
}
public class Label
{
internal Label(Class Class)
{
this.Class = Class;
}
private Class Class;
}
}
You are adding a reference to object from your "MyCompanyApplication", which is not a portable class library.
Change this:
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
to this:
MetadataReference.CreateFromFile(#"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile\Profile7\System.Runtime.dll")

How do I use AppDomain.CreateDomain with AssemblyResolve?

I want to load my assemblies from WCF by using memory. Everything is working good WHEN:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Assembly[] assBefore = AppDomain.CurrentDomain.GetAssemblies();
foreach (byte[] binary in deCompressBinaries)
loadedAssembly = AppDomain.CurrentDomain.Load(binary);
But I want to use AppDomain.CreateDomain, not the current domain:
protected void LoadApplication()
{
this.ApplicationHost = AppDomain.CreateDomain("TestService", null, new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
});
ApplicationHost.AssemblyResolve += new ResolveEventHandler(OnAssemblyResolve);
foreach (AssemblyName asmbly in System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
ApplicationHost.Load(asmbly);
}
List<byte[]> deCompressBinaries = new List<byte[]>();
foreach (var item in AppPackage.Item.AssemblyPackage)
deCompressBinaries.Add(item.Buffer);
var decompressvalues = DeCompress(deCompressBinaries);
deCompressBinaries.Clear();
deCompressBinaries = decompressvalues.ToList();
foreach (byte[] binary in deCompressBinaries)
ApplicationHost.Load(binary);
Assembly[] assAfter = AppDomain.CurrentDomain.GetAssemblies();
}
Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
return Assembly.Load(args.Name);
}
I have two class libraries, ClassLibrary1 and ClassLibrary2 using the below:
namespace ClassLibrary2
{
public class Class1 : MarshalByRefObject
{
public Class1()
{
}
public int GetSum(int a , int b)
{
try
{
ClassLibrary1.Class1 ctx = new ClassLibrary1.Class1();
return ctx.Sum(a, b);
}
catch
{
return -1;
}
}
public int GetMultiply(int a, int b)
{
return a * b;
}
}
}
Classlibrary2 depends on ClassLibrary1. So I am using assemblyresolver. But I get an error on ApplicationHost.Load(binary);:
Error: Could not load file or assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Also it is NOT FIRING ASSEMBLYRESOLVER. My cursor is not going to the Assemblyresolver method. How do I use AppDomain.CreateDomain with the resolve method?
I personally don't like loading assemblies from a byte array. I think it is better to save your assemblies to a temporary folder and then load them from that folder. Take a look at this article: Application Domains is hard….

Categories