简体   繁体   English

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

[英]Manually loading of assemblies and dependencies at runtime (nuget dependencies & FileNotFoundException)

I currently have an issue with loading assemblies at runtime using Assembly.LoadFrom(String) .我目前在使用Assembly.LoadFrom(String)在运行时加载程序集时遇到问题。 While the specified assembly is loaded just fine, referenced third-party assemblies (eg nuget packages) are not loaded when the targeted framework is either netcoreapp or netstandard .虽然指定的程序集加载得很好,但当目标框架是netcoreappnetstandard时,不会加载引用的第三方程序集(例如 nuget 包)。

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.我在这里使用Newtonsoft.Json作为 nuget 示例,但它可以是任何其他程序集。

ClassLibrary0.csproj 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 ClassLibrary1.csproj

Has a package reference to Newtonsoft.Json via nuget .通过nuget有一个对Newtonsoft.Json的包引用。 Has a reference to additional assembly ClassLibrary0 depending on TargetFramework (shitty conditional ItemGroups ).根据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 ClassLibrary2.csproj

Has a project reference to ClassLibrary1 .有一个对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:运行dotnet restore并重建解决方案后,可以在输出目录中观察到根本问题:

The Problem:问题:

  • Copies of ClassLibrary0.dll are present in all output directories (=> references to third-party are good).所有输出目录中都存在ClassLibrary0.dll副本(=> 对第三方的引用很好)。
  • Copies of ClassLibrary1.dll are present in all output directories of ClassLibrary2 (=> project references are good too).副本ClassLibrary1.dll存在于所有的输出目录ClassLibrary2 (=>项目引用也很好)。
  • Copies of Newtonsoft.Json are only present in net output directories but are missing in all netcoreapp and netstandard . Newtonsoft.Json副本仅出现在net输出目录中,但在所有netcoreappnetstandard中都netstandard
  • All netcoreapp and netstandard output directories contain a *.deps.json file that correctly mentions the Newtonsoft.Json package as a dependency.所有netcoreappnetstandard输出目录都包含一个*.deps.json文件,该文件正确地将Newtonsoft.Json包作为依赖项提及。

A call to Assembly.LoadFrom(String) however won't load these dependencies to Newtonsoft.Json in case of netcoreapp and netstandard .但是,在netcoreappnetstandard情况下,调用Assembly.LoadFrom(String)不会将这些依赖项加载到Newtonsoft.Json This results in FileNotFoundException at runtime after running code from the specified loaded assemblies.从指定的加载程序集运行代码后,这会在运行时导致FileNotFoundException

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.我试图通过附加到AppDomain.AssemblyResolve事件来解决这些问题,但到目前为止我不走运。 Those *.deps.json don't contain a location path of the dependency.那些*.deps.json不包含依赖项的位置路径。

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.我尝试在Path环境变量中的所有位置查找程序集,但 nuget 包位置似乎没有列在那里。 The location on all my machines seems to be %userprofile%\\.nuget\\packages\\package-name\\version\\ .我所有机器上的位置似乎都是%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.但是,我并不是 100% 肯定这将始终是所有可能执行我的代码的机器上 nuget 包的正确位置。

The actual question:实际问题:

Is there a solid way to resolve nuget dependencies at runtime when manually loading assemblies?手动加载程序集时,是否有可靠的方法在运行时解决 nuget 依赖项?

Restrictions:限制:

  • This needs to be an offline solution, no downloading of package versions on the fly.这需要是一个离线解决方案,不能即时下载软件包版本。
  • Cannot rely on <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> in the original projects.不能依赖原始项目中的<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  • Cannot rely on me having references to the dependencies in question.不能依赖我对相关依赖项的引用。 The whole point of doing this is to be able to dynamically load assemblies that i can't know about at compile time.这样做的全部意义在于能够动态加载我在编译时不知道的程序集。

I have solved the problem by writing my own NuGet package resolver, which looks for the appropriate package at runtime.我通过编写自己的 NuGet 包解析器解决了这个问题,它在运行时查找合适的包。 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:在运行时解析需要使用以下内容附加到 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;
}

This requires the use of my NuGet package which contains the resolver and some helpers.这需要使用我的NuGet 包,其中包含解析器和一些帮助器。 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.我意识到dotnet publish也会复制任何依赖项,但这是一个特殊的边缘情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM