I currently have an issue with loading assemblies at runtime using Assembly.LoadFrom(String)
.
While the specified assembly is loaded just fine, referenced third-party assemblies (e.g. nuget packages) are not loaded when the targeted framework is either netcoreapp
or netstandard
.
To figure out the problem i have created a simple solution consisting of three projects.
Each project contains exactly one class.
I'm using Newtonsoft.Json
as a nuget example here but it could be any other assembly.
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
Has a package reference to Newtonsoft.Json
via nuget
.
Has a reference to additional assembly ClassLibrary0
depending on TargetFramework
(shitty conditional 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
Has a project reference to 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();
}
}
}
After running dotnet restore
and rebuilding the solution the root problem can be observed in the output directories:
The Problem:
ClassLibrary0.dll
are present in all output directories (=> references to third-party are good).ClassLibrary1.dll
are present in all output directories of ClassLibrary2
(=> project references are good too).Newtonsoft.Json
are only present in net
output directories but are missing in all netcoreapp
and netstandard
.netcoreapp
and netstandard
output directories contain a *.deps.json
file that correctly mentions the Newtonsoft.Json
package as a dependency.A call to Assembly.LoadFrom(String)
however won't load these dependencies to Newtonsoft.Json
in case of netcoreapp
and netstandard
.
This results in FileNotFoundException
at runtime after running code from the specified loaded assemblies.
What i've tried:
I am trying to resolve those by attaching to the AppDomain.AssemblyResolve
event but so far i'm out of luck.
Those *.deps.json
don't contain a location path of the dependency.
I've tried looking for the assembly in all the locations within the Path
environment variable but the nuget package location doesn't seem to be listed there.
The location on all my machines seems to be %userprofile%\.nuget\packages\package-name\version\
.
However i'm not 100% positive that this will always be the correct location for nuget packages on all machines that might execute my code.
The actual question:
Is there a solid way to resolve nuget dependencies at runtime when manually loading assemblies?
Restrictions:
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
in the original projects.I have solved the problem by writing my own NuGet package resolver, which looks for the appropriate package at runtime. I haven't had the time for a proper documentation yet but it's already on my plate. Resolving at runtime requires to attach to AppDomain.AssemblyResolve with something like that:
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;
}
This requires the use of my NuGet package which contains the resolver and some helpers. There is also an article that goes into the details and design decisions of the resolver.
I realize that dotnet publish
will also copy any dependencies but this is a special edge case.