在运行时手动加载程序集和依赖项(nuget依赖项和FileNotFoundException)

时间:2019-12-11 11:00:11

标签: c# .net-core nuget-package .net-standard assembly-loading

我目前在使用Assembly.LoadFrom(String)在运行时加载程序集时遇到问题。 虽然可以很好地加载指定的程序集,但是当目标框架为netcoreappnetstandard时,不会加载引用的第三方程序集(例如nuget程序包)。

为了解决这个问题,我创建了一个由三个项目组成的简单解决方案。 每个项目仅包含一个类。 我在这里使用Newtonsoft.Json作为nuget示例,但它可以是任何其他程序集。

ClassLibrary0.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net20;netstandard1.0</TargetFrameworks>
  </PropertyGroup>

</Project>
namespace ClassLibrary0 {
    public class Class0 {

        public System.String SomeValue { get; set; }

    }
}

ClassLibrary1.csproj

具有通过Newtonsoft.Jsonnuget的程序包引用。 依赖于ClassLibrary0引用了附加程序集TargetFramework(有条件的ItemGroups)。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net20;net35;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='net20' OR '$(TargetFramework)'=='net35' OR '$(TargetFramework)'=='net40' OR '$(TargetFramework)'=='net45' OR '$(TargetFramework)'=='net451' OR '$(TargetFramework)'=='net452' OR '$(TargetFramework)'=='net46' OR '$(TargetFramework)'=='net461' OR '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net47' OR '$(TargetFramework)'=='net471' OR '$(TargetFramework)'=='net472'">
    <Reference Include="ClassLibrary0">
      <HintPath>..\net20\ClassLibrary0.dll</HintPath>
    </Reference>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.0' OR '$(TargetFramework)'=='netstandard1.1' OR '$(TargetFramework)'=='netstandard1.2' OR '$(TargetFramework)'=='netstandard1.3' OR '$(TargetFramework)'=='netstandard1.4' OR '$(TargetFramework)'=='netstandard1.5' OR '$(TargetFramework)'=='netstandard1.6' OR '$(TargetFramework)'=='netstandard2.0'">
    <Reference Include="ClassLibrary0">
      <HintPath>..\netstandard1.0\ClassLibrary0.dll</HintPath>
    </Reference>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='netcoreapp1.0' OR '$(TargetFramework)'=='netcoreapp1.1' OR '$(TargetFramework)'=='netcoreapp2.0' OR '$(TargetFramework)'=='netcoreapp2.1'">
    <Reference Include="ClassLibrary0">
      <HintPath>..\netstandard1.0\ClassLibrary0.dll</HintPath>
    </Reference>
  </ItemGroup>

</Project>
namespace ClassLibrary1 {
    public class Class1 {

        public System.String SomeValue { get; set; }

        public Class1() {
            var tmp = new ClassLibrary0.Class0();
            var tmp2 = new Newtonsoft.Json.DefaultJsonNameTable();
        }

    }
}

ClassLibrary2.csproj

具有对ClassLibrary1的项目引用。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net20;net35;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
  </ItemGroup>

</Project>
namespace ClassLibrary2 {
    public class Class2 {

        public System.String SomeValue { get; set; }

        public Class2() {
            var tmp = new ClassLibrary1.Class1();
        }

    }
}

运行dotnet restore并重建解决方案后,可以在输出目录中观察到根本问题:

问题:

  • 所有输出目录中都存在ClassLibrary0.dll的副本(=>引用第三方是好的)。
  • ClassLibrary1.dll的副本出现在ClassLibrary2的所有输出目录中(=>项目引用也很好)。
  • Newtonsoft.Json的副本仅出现在net的输出目录中,而在所有netcoreappnetstandard中都丢失。
  • 所有netcoreappnetstandard输出目录都包含一个*.deps.json文件,该文件正确地将Newtonsoft.Json软件包作为依赖项。

Assembly.LoadFrom(String)的调用不会在Newtonsoft.Jsonnetcoreapp的情况下将这些依赖项加载到netstandard。 从指定的已加载程序集中运行代码后,将在运行时产生FileNotFoundException

我尝试过的事情:

我正在尝试通过附加到AppDomain.AssemblyResolve事件来解决这些问题,但到目前为止我还很走运。 那些*.deps.json不包含依赖项的位置路径。

我尝试在Path环境变量内的所有位置查找程序集,但是nuget软件包的位置似乎没有列出。 我所有机器上的位置似乎都是%userprofile%\.nuget\packages\package-name\version\。 但是我不是100%肯定这将一直是所有可能执行我的代码的机器上nuget包的正确位置。

实际问题:

在手动加载程序集时,是否有可靠的方法在运行时解决nuget依赖项?

限制:

  • 这需要是一个脱机解决方案,不能即时下载软件包版本。
  • 不能依赖原始项目中的<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  • 不能依靠我引用相关的依赖项。 这样做的全部目的是能够动态加载我在编译时不知道的程序集。

1 个答案:

答案 0 :(得分:1)

我已经通过编写自己的NuGet程序包解析器解决了该问题,该程序在运行时会寻找合适的程序包。我还没有时间准备适当的文档,但现在已经可以使用了。在运行时进行解析需要将AppDomain.AssemblyResolve附加到以下内容:

private Assembly OnAssemblyResolve(Object sender, ResolveEventArgs args) {
    if(AssemblyResolver.Nuget.TryResolve(args, out IEnumerable<FileInfo> files)) {
        foreach(FileInfo file in files) {
            if(AssemblyHelper.TryLoadFrom(file, out Assembly assembly)) {
                return assembly;
            }
        }
    }

    return null;
}

这需要使用我的NuGet package,其中包含解析器和一些帮助器。还有一个article涉及解析器的详细信息和设计决策。 我意识到dotnet publish也会复制任何依赖关系,但这是一种特殊情况。