简体   繁体   中英

Loading an .NET Standard assembly which references a nuget dependency at runtime?

I'm currently using AssemblyLoadContext.Default.LoadFromAssemblyPath(path/to/netstandard1.6lib.dll) and was curious about how to handle any nuget dependencies that library may have?

For example: Library A dynamically loads Library B. Library B depends on Redis from NuGet.

Library B loads correctly, but upon using the redis client -- we get a nasty FileNotFoundException complaining that the redis assembly cannot be found. The scenario is really a typical module-loader type thing.

Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
if (assembly == null)
    throw new InvalidExtensionException(name, path);

TypeInfo type = assembly.DefinedTypes.FirstOrDefault(x => x.ImplementedInterfaces.Contains(typeof(IExtension)));
if (type == null)
    throw new InvalidExtensionException(name, path);

IExtension extension = Activator.CreateInstance(type.AsType(), name, _dependencyUtility) as IExtension;
if (extension == null)
    throw new InvalidExtensionException(name, path);

extensions.Add(extension);

When Activator creates the instance, the extension's constructor attempts to make a new redis client -- and it all blows up.

Any thoughts on how to handle 3rd level dependencies from nuget at runtime?

DLLs must be there in order to load them, AFAIK you shouldn't download nugget packages on the run, because it will be slow, and it can stop working at any time that the nugget origin is not available or, more probably, that you don't have internet connection.

So make your project depend on that nugget package, and it will be download before building.

If you're not interested on this approach, then I suppose you could try to execute NuGet.exe from your program and make it download the required DLL first, but this will make your program to hang up while it's downloading the package files.

What I ended up needing to do is add this in my project's csproj file: <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

Then adjust my module loader code to iterate through all of the DLLs and load them as well all before attempting to calling the constructor from my assembly via activator.

public void LoadExtensions()
{
    IConfigurationSection[] extensionConfigurations = _config.GetSections(EXTENSION_CONFIGURATION_KEY).ToArray();
    if (extensionConfigurations.Length == 0)
        return;

    HashSet<IExtension> extensions = new HashSet<IExtension>();
    foreach (IConfigurationSection extensionConfiguration in extensionConfigurations)
    {
        string name = extensionConfiguration.Key;
        string path = _config.Get($"{extensionConfiguration.Path}:path");

        _logger.Debug($"Loading extension: {name}");

        if (string.IsNullOrEmpty(path) || !File.Exists(path))
            throw new ConfigurationItemMissingException($"{extensionConfiguration.Path}:path");

        LoadAssembly(path, name);
    }

    foreach (var extensionType in _extensionTypes)
    {
        IExtension extension = Activator.CreateInstance(extensionType.Key.AsType(), extensionType.Value, _dependencyUtility) as IExtension;
        if (extension == null)
            throw new InvalidExtensionException(extensionType.Value, extensionType.Key.AssemblyQualifiedName);

        extensions.Add(extension);
    }

    Extensions = extensions;
}

private void LoadAssembly(string path, string name)
{
    FileInfo[] dlls = new DirectoryInfo(Path.GetDirectoryName(path)).GetFiles("*.dll");

    foreach (FileInfo dll in dlls)
    {
        Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(dll.FullName);

        _logger.Info($"Loading assembly: {asm.FullName}");

        TypeInfo type = asm.DefinedTypes.FirstOrDefault(x => x.ImplementedInterfaces.Contains(typeof(IExtension)) && !x.IsAbstract);

        if (type == null)
            continue;

        _extensionTypes.Add(type, name);
    }
}

You should not manually resolve assembly dependencies.

Just make sure that when you load your Library B dynamically all the dependent dll-s are reachable by the .net runtime. By default, it will check the Working Directory of your app process and GAC. If you want to customize runtime's probing behavior you can do it with <probing> setting in your config file or from within C# code.

I suggest you reading these docs, they should help you understand how probing works in greater details:

https://docs.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies

https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/specify-assembly-location

To troubleshoot the dependency resolution you can use fuslog tool:

https://docs.microsoft.com/en-us/dotnet/framework/tools/fuslogvw-exe-assembly-binding-log-viewer

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