在运行时手动加载程序集和依赖项(nuget 依赖项和 FileNotFoundException)
Manually loading of assemblies and dependencies at runtime (nuget dependencies & FileNotFoundException)
我目前在使用 Assembly.LoadFrom(String)
在运行时加载程序集时遇到问题。
虽然指定的程序集加载得很好,但当目标框架为 netcoreapp
或 netstandard
.
时,不会加载引用的第三方程序集(例如 nuget 包)
为了找出问题,我创建了一个由三个项目组成的简单解决方案。
每个项目恰好包含一个 class。
我在这里使用 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
通过 nuget
引用了 Newtonsoft.Json
的包。
根据 TargetFramework
(糟糕的条件 ItemGroups
)引用附加程序集 ClassLibrary0
。
<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
输出目录中,但在所有 netcoreapp
和 netstandard
中都丢失了。
- 所有
netcoreapp
和 netstandard
输出目录包含一个 *.deps.json
文件,该文件正确地将 Newtonsoft.Json
包作为依赖项提及。
然而,在 netcoreapp
和 netstandard
的情况下,调用 Assembly.LoadFrom(String)
不会将这些依赖项加载到 Newtonsoft.Json
。
这会在指定加载程序集的 运行 代码之后在运行时产生 FileNotFoundException
。
我试过的:
我试图通过附加到 AppDomain.AssemblyResolve
事件来解决这些问题,但到目前为止我运气不好。
那些 *.deps.json
不包含依赖项的位置路径。
我已经尝试在 Path
环境变量中的所有位置查找程序集,但 nuget 包位置似乎未在此处列出。
我所有机器上的位置似乎都是 %userprofile%\.nuget\packages\package-name\version\
。
但是我不是 100% 肯定这将始终是所有可能执行我的代码的机器上 nuget 包的正确位置。
真题:
在手动加载程序集时,是否有可靠的方法在运行时解决 nuget 依赖关系?
限制:
- 这需要一个离线解决方案,不能即时下载软件包版本。
- 不能依赖原始项目中的
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
。
- 不能指望我引用相关依赖项。
这样做的全部意义在于能够动态加载我在编译时不知道的程序集。
我已经通过编写自己的 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 which contains the resolver and some helpers. There is also an article 来了解解析器的细节和设计决策。
我意识到 dotnet publish
也会复制任何依赖项,但这是一个特殊的边缘情况。
我目前在使用 Assembly.LoadFrom(String)
在运行时加载程序集时遇到问题。
虽然指定的程序集加载得很好,但当目标框架为 netcoreapp
或 netstandard
.
为了找出问题,我创建了一个由三个项目组成的简单解决方案。
每个项目恰好包含一个 class。
我在这里使用 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
通过 nuget
引用了 Newtonsoft.Json
的包。
根据 TargetFramework
(糟糕的条件 ItemGroups
)引用附加程序集 ClassLibrary0
。
<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
输出目录中,但在所有netcoreapp
和netstandard
中都丢失了。- 所有
netcoreapp
和netstandard
输出目录包含一个*.deps.json
文件,该文件正确地将Newtonsoft.Json
包作为依赖项提及。
然而,在 netcoreapp
和 netstandard
的情况下,调用 Assembly.LoadFrom(String)
不会将这些依赖项加载到 Newtonsoft.Json
。
这会在指定加载程序集的 运行 代码之后在运行时产生 FileNotFoundException
。
我试过的:
我试图通过附加到 AppDomain.AssemblyResolve
事件来解决这些问题,但到目前为止我运气不好。
那些 *.deps.json
不包含依赖项的位置路径。
我已经尝试在 Path
环境变量中的所有位置查找程序集,但 nuget 包位置似乎未在此处列出。
我所有机器上的位置似乎都是 %userprofile%\.nuget\packages\package-name\version\
。
但是我不是 100% 肯定这将始终是所有可能执行我的代码的机器上 nuget 包的正确位置。
真题:
在手动加载程序集时,是否有可靠的方法在运行时解决 nuget 依赖关系?
限制:
- 这需要一个离线解决方案,不能即时下载软件包版本。
- 不能依赖原始项目中的
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
。 - 不能指望我引用相关依赖项。 这样做的全部意义在于能够动态加载我在编译时不知道的程序集。
我已经通过编写自己的 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 which contains the resolver and some helpers. There is also an article 来了解解析器的细节和设计决策。
我意识到 dotnet publish
也会复制任何依赖项,但这是一个特殊的边缘情况。