Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core Assembly.LoadFile at PostBuild event

I need generate typescript files from some of my C# classes after build.

I created dotnet cli tool and added post-build event

dotnet tsgenerator "$(TargetPath)"

where $(TargetPath) is macros pointing, for example, D:\Test\bin\Release\netcoreapp2.0\my.dll

Next, i tried to load assembly next way:

public static void Main(string[] args)
{
    var dllPath = args[0]; // "D:\Test\bin\Release\netcoreapp2.0\my.dll"
    var assembly = Assembly.LoadFile(dllPath);
    var types = assembly.GetExportedTypes(); // Throws exception
}

But i got ReflectionTypeLoadException that says Could not load file or assembly for some references assemblies (for example, Microsoft.AspNetCore.Antiforgery).

How i can load assembly for .NET Core applications?

like image 828
maxswitcher Avatar asked Jun 26 '18 17:06

maxswitcher


2 Answers

I'm found solution at github issue. Message by amits1995 and angelcalvasp.

I'm added <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> to my csproj and using this code to load assembly:

public static class AssemblyLoader
{
    public static Assembly LoadFromAssemblyPath(string assemblyFullPath)
    {
        var fileNameWithOutExtension = Path.GetFileNameWithoutExtension(assemblyFullPath);
        var fileName = Path.GetFileName(assemblyFullPath);
        var directory = Path.GetDirectoryName(assemblyFullPath);

        var inCompileLibraries = DependencyContext.Default.CompileLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase));
        var inRuntimeLibraries = DependencyContext.Default.RuntimeLibraries.Any(l => l.Name.Equals(fileNameWithOutExtension, StringComparison.OrdinalIgnoreCase));

        var assembly = (inCompileLibraries || inRuntimeLibraries)
            ? Assembly.Load(new AssemblyName(fileNameWithOutExtension))
            : AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFullPath);

        if (assembly != null)
            LoadReferencedAssemblies(assembly, fileName, directory);

        return assembly;
    }

    private static void LoadReferencedAssemblies(Assembly assembly, string fileName, string directory)
    {
        var filesInDirectory = Directory.GetFiles(directory).Where(x => x != fileName).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
        var references = assembly.GetReferencedAssemblies();

        foreach (var reference in references)
        {
            if (filesInDirectory.Contains(reference.Name))
            {
                var loadFileName = reference.Name + ".dll";
                var path = Path.Combine(directory, loadFileName);
                var loadedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
                if (loadedAssembly != null)
                    LoadReferencedAssemblies(loadedAssembly, loadFileName, directory);
            }
        }
    }
}

Usage:

public static void Main(string[] args)
{
    var dllPath = args[0]; // "D:\Test\bin\Release\netcoreapp2.0\my.dll"
    var assembly = AssemblyLoader.LoadFromAssemblyPath(dllPath);
    var types = assembly.GetExportedTypes(); // No exceptions
}
like image 91
maxswitcher Avatar answered Nov 15 '22 21:11

maxswitcher


Well you are able to load assembly but GetTypes() and GetExportedTypes() depend on the public classes within that assembly, if they have external references you get this exception.

Answer: This means the Types of that assembly depend on other assembly which the current .NetCore does not have access at the run time because it can not connect to other dependent assemblies

Solution:
Get dependencies of the DLL assemblies and compile all of them, then load each assembly iteratively to get all ExportedTypes (i.e publicly visible Types)

Code:

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;// add this nuget

class Program
{
    static void Main(string[] args)
    {
        var asl = new AssemblyLoader();
        var asm = asl.LoadFromAssemblyPath(@"C:\temp\Microsoft.AspNetCore.Antiforgery.dll");
        try
        {

            var y = asm.GetExportedTypes();
            Console.WriteLine(y);
        }
        catch (Exception e1)
        {
            Console.WriteLine("Got exception at first attempt of GetExportedTypes ");
            Console.WriteLine("\t*********" + e1.Message + "**************");

            var deped = asl.CallForDependency(asm.GetName());

            try
            {
                Console.WriteLine("\n" + deped.ToString());
                Console.WriteLine("----------All Exported Types------------");

                foreach (var item in deped.ExportedTypes)
                {
                    Console.WriteLine(item);
                }
            }
            catch (Exception e2)
            {

                Console.WriteLine("Got exception at second attempt of GetExportedTypes ");
                Console.WriteLine("\t*********" + e2.Message + "**************");
            }

        }
        Console.ReadLine();

    }
}

public class AssemblyLoader :AssemblyLoadContext
{
    protected override Assembly Load(AssemblyName assemblyName)
    {
        var deps = DependencyContext.Default;
        var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
        var assembly = Assembly.Load(new AssemblyName(res.First().Name));
        return assembly;
    }

    public Assembly CallForDependency(AssemblyName assemblyName)
    {
        return this.Load(assemblyName);
    }
}

Output :

Got exception at first attempt of GetExportedTypes
        *********Could not load file or assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. An operation is not legal in the current state. (Exception from HRESULT: 0x80131509)**************

Microsoft.AspNetCore.Antiforgery, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
----------All Exported Types------------
Microsoft.Extensions.DependencyInjection.AntiforgeryServiceCollectionExtensions
Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions
Microsoft.AspNetCore.Antiforgery.AntiforgeryTokenSet
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException
Microsoft.AspNetCore.Antiforgery.IAntiforgery
Microsoft.AspNetCore.Antiforgery.IAntiforgeryAdditionalDataProvider
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryFeature
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryOptionsSetup
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgerySerializationContext
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgerySerializationContextPooledObjectPolicy
Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryToken
Microsoft.AspNetCore.Antiforgery.Internal.BinaryBlob
Microsoft.AspNetCore.Antiforgery.Internal.CryptographyAlgorithms
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryAdditionalDataProvider
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenGenerator
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenStore
Microsoft.AspNetCore.Antiforgery.Internal.DefaultClaimUidExtractor
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryFeature
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryTokenGenerator
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryTokenSerializer
Microsoft.AspNetCore.Antiforgery.Internal.IAntiforgeryTokenStore
Microsoft.AspNetCore.Antiforgery.Internal.IClaimUidExtractor

Reference and explanation on ReflectionTypeLoadException: Assembly.GetTypes Method ()

ReflectionTypeLoadException

The assembly contains one or more types that cannot be loaded. The array returned by the Types property of this exception contains a Type object for each type that was loaded and null for each type that could not be loaded, while the LoaderExceptions property contains an exception for each type that could not be loaded.

Remarks

The returned array includes nested types.

If the GetTypes method is called on an assembly and a type in that assembly is dependent on a type in an assembly that has not been loaded (for example, if it derives from a type in the second assembly), a ReflectionTypeLoadException is thrown. For example, this can happen if the first assembly was loaded with the ReflectionOnlyLoad or ReflectionOnlyLoadFrom methods, and the second assembly was not loaded. It can also happen with assemblies loaded using the Load and LoadFile methods if the second assembly cannot be located when the GetTypes method is called.

Note

If a type has been forwarded to another assembly, it is not included in the returned array. For information on type forwarding, see Type Forwarding in the Common Language Runtime.

Linked :

  • How to load assemblies located in a folder in .net core console app
  • How to dynamically load assemblies in dotnet core
like image 44
Morse Avatar answered Nov 15 '22 22:11

Morse