Marco
Marco

Reputation: 2245

Inspect Types via MetadataLoadContext fails due to missing assembly (.NET core)

I want to inspect assemblies if they have a specific type without loading the assembly in the current scope, which is available via MetadataLoadContext in .NET Core 3.

But if I try the following example

internal static class Program
{
    // ReSharper disable once UnusedParameter.Local
    private static void Main(string[] args)
    {
        var paths = new string[] { @"Plugin.dll" };
        var resolver = new PathAssemblyResolver(paths);
        var pluginInterface = typeof(IPlugin);
        using (var context = new MetadataLoadContext(resolver))
        {
            var assembly =
                context.LoadFromAssemblyName(@"Plugin");
            foreach (var type in assembly.GetTypes())
            {
                if (type.IsClass && pluginInterface.IsAssignableFrom(type))
                    Console.WriteLine("found");
            }
        }
    }
}

I get an exception

System.IO.FileNotFoundException: Could not find core assembly. Either specify a valid core assembly name in the MetadataLoadContext constructor or provide a MetadataAssemblyResolver that can load the core assembly.

at var context = new MetadataLoadContext(resolver)

What is meant by core assembly ? Or what I am doing wrong ? https://blog.vincentbitter.nl/net-core-3-0/ seems not working for me.

Upvotes: 7

Views: 3852

Answers (4)

Jeremy Thomas
Jeremy Thomas

Reputation: 339

Slightly off topic but this worked from me when I got that error:

public static string GetFullNameFromAssemblyPath(string assemblyPath)
{
  using var context = new MetadataLoadContext(CreatePathAssemblyResolver(assemblyPath));
  var assembly = context.LoadFromAssemblyPath(assemblyPath);
  return assembly.FullName;
}

static PathAssemblyResolver CreatePathAssemblyResolver(string assemblyPath)
{
  var assemblyPaths = new[] { assemblyPath, typeof(object).Assembly.Location, typeof(System.Runtime.GCSettings).Assembly.Location, };
  var resolver = new PathAssemblyResolver(assemblyPaths);
  return resolver;
}

Upvotes: 0

Mihail Shishkov
Mihail Shishkov

Reputation: 15857

Here is a method that works on net6.0. The method extracts custom attributes from the assembly that was provided as an argument.

public static FreshInfo GetInfo(string pathToMainExecutable) {
        if (string.IsNullOrWhiteSpace(pathToMainExecutable))
            throw new ArgumentException(@"Value cannot be null or whitespace.", nameof(pathToMainExecutable));
    
        if (File.Exists(pathToMainExecutable) == false)
            throw new FileNotFoundException($"File [{pathToMainExecutable}] does not exists on disk.");
    
        var runtimeDirectory = RuntimeEnvironment.GetRuntimeDirectory();
    
        var pathToSystemRuntime = Path.Combine(runtimeDirectory, "System.Runtime.dll");
        if (File.Exists(pathToSystemRuntime) == false)
            throw new FileNotFoundException($"Could not find [{pathToSystemRuntime}].");
    
        var pathToSystemPrivateCoreLib = Path.Combine(runtimeDirectory, "System.Private.CoreLib.dll");
        if (File.Exists(pathToSystemPrivateCoreLib) == false)
            throw new FileNotFoundException($"Could not find [{pathToSystemPrivateCoreLib}].");
    
        // make sure that we are referring to the .net dll/assembly but not the exe bootstrapper
        // exe file in net6.0/core is not a managed file it's a native executable
        // TODO do not assume that the exe file has the same name as the dll file
        // managed dll filename can be extracted from a VERSIONINFO Resource in the native exe 
        pathToMainExecutable = pathToMainExecutable.ReplaceEnd("exe", "dll");
    
        var assemblyNames = new List<string> {
            Path.GetFileName(pathToMainExecutable)
          , "Fresh.Updater.dll"
          , pathToSystemRuntime
          , pathToSystemPrivateCoreLib
        };
    
        var metadataAssemblyResolver = new PathAssemblyResolver(assemblyNames);
    
        using (var mlc = new MetadataLoadContext(metadataAssemblyResolver)) {
            var mainAssembly = mlc.LoadFromAssemblyPath(pathToMainExecutable);
    
            var buildTime = ExtractBuildTime(mainAssembly);
            var appId     = ExtractAppId(mainAssembly);
    
            var appFolder = Path.GetDirectoryName(pathToMainExecutable);
            return new FreshInfo(appId, buildTime) {
                AppFolder = appFolder
            };
        }
    }

Upvotes: 1

SmallSoft
SmallSoft

Reputation: 728

The existing answer didn't worked for me (.NET Core 2.1). It fails with error that System.Runtime is not found. If i hardcode full path to System.Runtime, it fails for other assemblies, like System.Private.CoreLib. Also checking types via IsAssignableFrom seems not working when one type isn't from MetadataLoadContext.

The possible solution for assembly load errors is to include all BCL assemblies (all .dll files in directory returned by RuntimeEnvironment.GetRuntimeDirectory). This feels somewhat dumb, because not all of them are actually managed assemblies, but it seems to work. Here's complete example of searching types that implement interface via MetadataLoadContext:

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace MetadataLoadContextSample
{
    class Program
    {
        static int Main(string[] args)
        {
            string inputFile = @"Plugin.dll";

            string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");                        
            var paths = new List<string>(runtimeAssemblies);
            paths.Add(inputFile);            
            var resolver = new PathAssemblyResolver(paths);
            var context = new MetadataLoadContext(resolver);

            using (context)
            {                
                Assembly assembly = context.LoadFromAssemblyPath(inputFile);
                AssemblyName name = assembly.GetName();

                foreach (TypeInfo t in assembly.GetTypes())
                {
                    try
                    {
                        if (t.IsClass && t.GetInterface("IPlugin") != null)
                        {
                            Console.WriteLine(t.Name);
                        }
                    }
                    catch (FileNotFoundException ex)
                    {                        
                        Console.WriteLine("FileNotFoundException: " + ex.Message);
                    }
                    catch (TypeLoadException ex)
                    {
                        Console.WriteLine("TypeLoadException: " + ex.Message);
                    }
                }
            }

            Console.ReadLine();
            return 0;
        }
    }
}

Upvotes: 5

Marco
Marco

Reputation: 2245

Providing the following paths of the .NET core libs works

  var paths = new string[] {@"Plugin.dll", @"netstandard.dll", "System.Runtime.dll"};

Upvotes: 0

Related Questions