简体   繁体   中英

Alternative for Assembly.LoadFile, Assembly.LoadFrom and Assembly.Load?

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.

Code

  • project Host is a Console Application that has no project references and no package references. It contains the file Program.cs:
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]);
    }
}
  • project Feature is a Class Library that has a ProjectReference for ..\\Subfeature\\Subfeature.csproj and contains the file SomeFeature.cs:
public class SomeFeature
{
    public void PrintText()
    {
        System.Console.WriteLine(new SomeSubfeature().GetText());
    }
}
  • project Subfeature is a Class Library that has a PackageReference for Newtonsoft.Json and contains the file SomeSubfeature.cs:
public class SomeSubfeature
{
    public string GetText()
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject("Some Text");
    }
}

Solved Problems

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

Final Problem

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM