I have two problems with a minimal, reproducible example that has three project targeting .NET Core 3.1. But I also want to target .NET Standard 2.0 .
The example is for an application that needs to load an assembly in run time and makes use of the provided assembly.
The assembly that is loaded references another project, which in turn makes use of a NuGet package.
class Program
{
static void Main(string[] args)
{
var featurePath = System.IO.Path.GetFullPath(@"..\..\..\..\Feature\bin\Debug\netcoreapp3.1\Feature.dll");
var featureAssembly = System.Reflection.Assembly.LoadFile(featurePath);
var featureType = featureAssembly.GetType("SomeFeature");
var featureInstance = System.Activator.CreateInstance(featureType);
featureType.InvokeMember("PrintText",
System.Reflection.BindingFlags.InvokeMethod, null, featureInstance, new object[0]);
}
}
..\\Subfeature\\Subfeature.csproj
and contains the file SomeFeature.cs: public class SomeFeature
{
public void PrintText()
{
System.Console.WriteLine(new SomeSubfeature().GetText());
}
}
Newtonsoft.Json
and contains the file SomeSubfeature.cs: public class SomeSubfeature
{
public string GetText()
{
return Newtonsoft.Json.JsonConvert.SerializeObject("Some Text");
}
}
The first solved problem is because the consumed assemblies reference another project and/or make use of packages. Assembly.LoadFrom
only loads the requested assembly. The referenced project's assembly and packages are not loaded. This results in a FileNotFoundException because Subfeature could not be found.
I could solve this problem by (1) replacing Assembly.LoadFile(featurePath)
with Assembly.LoadFrom(featurePath)
so other required DLLs in the same directory can also be loaded. And (2) by making package DLLs that are only copied to the same directory during publish also get copied during build, by adding into Feature.csproj <PropertyGroup><CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies></PropertyGroup>
.
The second solved problem is that the application locks the DLLs while they are loaded. This prevents me from deploying newer versions of the DLL's while the application is still running. It makes releasing newer versions cumbersome, when let's say, the application is part of an IIS hosted application. .NET Core no longer supports loading a shadow copy of a DLL.
I could solve this problem by (1) replacing Assembly.LoadFile(featurePath)
into Assembly.Load(System.IO.File.ReadAllBytes(featurePath))
so required DLLs are loaded from bytes that are read before loading. And (2) having a filewatcher reload the application if something happens with a DLL file in its directory.
The solution to the first problem is not compatible with the solution to the second problem. Assembly.LoadFrom
fixes my first problem. Assembly.Load
fixes my second problem. But I haven't found an alternative for Assembly.LoadFile
that fixes both problems at the same time.
Add AssemblyResolve
event handler to AppDomain.CurrentDomain
and handle assembly loading as you do with reading all bytes.
const string location = @"..\..\..\..\Dependency\bin\Debug\netcoreapp3.1\";
static void Main(string[] args)
{
var domain = AppDomain.CurrentDomain;
domain.AssemblyResolve += Domain_AssemblyResolve;
var type = Type.GetType("Dependency.DependentClass,Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
var obj = Activator.CreateInstance(type);
Console.WriteLine(obj.ToString());
}
private static Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var dll = $"{args.Name.Split(",")[0]}.dll";
var path = Path.Combine(location, dll);
var asm = Assembly.Load(File.ReadAllBytes(path));
return asm;
}
A side note: You need to provide full name for type else it will throw error. Like: "Dependency.DependentClass"
Dependency assembly has 2 dependencies one is Newtonsoft.Json
NuGet package, the other one is another project. Bar
class resides in another project.
public class DependentClass
{
public int MyProperty { get; set; }
public string AnotherProperty { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(new Bar());
}
}
EDIT: After the downvote, I looked up the above solution again and although it works it seems more likely a . net Framework
. net Framework
kind of a solution rather than a .net Core
So a more . net Core
. net Core
like solution is to use a AssemblyLoadContext
.
Define a custom assembly load context.
public class CustomLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver resolver;
public CustomLoadContext(string mainAssemblyToLoadPath) : base(isCollectible: true)
{
resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath);
}
protected override Assembly Load(AssemblyName name)
{
Console.WriteLine("Resolving : {0}",name.FullName);
var assemblyPath = resolver.ResolveAssemblyToPath(name);
if (assemblyPath != null)
{
return Assembly.Load(File.ReadAllBytes(assemblyPath));
}
return Assembly.Load(name);
}
}
Load your assembly and let the custom loader context to load it's dependencies.
const string location = @"..\..\..\..\Dependency\bin\Debug\netcoreapp3.1\";
static void Main(string[] args)
{
var fullPath = Path.GetFullPath(location + "Dependency.dll");
var clx = new CustomLoadContext(fullPath); // initialize custom context
var asm = clx.LoadFromStream(new MemoryStream(File.ReadAllBytes(fullPath))); // load your desired assembly
var ins = asm.CreateInstance("Dependency.DependentClass");
Console.WriteLine(ins.ToString());
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.