Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assembly.Load(string assemblyName) throws a FileNotFoundException #13511

Closed
bitbonk opened this issue Oct 1, 2019 · 9 comments
Closed

Assembly.Load(string assemblyName) throws a FileNotFoundException #13511

bitbonk opened this issue Oct 1, 2019 · 9 comments
Labels
area-AssemblyLoader-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@bitbonk
Copy link
Contributor

bitbonk commented Oct 1, 2019

Assuming that the correct version of NewtonSoft.Json.dll was located in the same folder as the executing exe (i.e. in the probing path), this code used to work in classic .NET (4.7.2):

class Program
{
    static void Main(string[] args)
    {
        System.Reflection.Assembly.Load("Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed");
    }
}

But in .NET Core, we will get the following exception:

System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified.
File name: 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'
at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, RuntimeAssembly assemblyContext, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)
at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, StackCrawlMark& stackMark, AssemblyLoadContext assemblyLoadContext)
at System.Reflection.Assembly.Load(String assemblyString)
at AssemblyTest.Program.Main(String[] args) in C:\temp\AssemblyTest\Program.cs:line 9

This leaves anyone who is porting a .NET classic application or library to .NET Core with the following questions:

  1. Obviously, the way how probing for assemblies and resolving them is done, somehow works differently in .NET Core. But there is no (or I haven't been able to find) a concise documentation that describes those differences or generally explains how it works in .NET Core. After some googling I found a hint that I may need to add the probing path to runtimeconfig.json and (or?) runtimeconfig.dev.json like so
  "runtimeOptions": {
    "additionalProbingPaths": [
      ...
      "C:\\the\\path\\to\\my\\application"
    ]
  }

but that didn't change anything.

  1. In the old days we had fuslogvw (aka Fusion Log Viewer) or @awaescher's excellent Fusion++ to diagnose why an assembly was not found. It clearly listed binding erros and also showed where it looked (but couldn't find) the assembly. Now we have COREHOST_TRACE=1 and COREHOST_TRACEFILE=<path>. But the output that is generated by it absolutely does not help to figure out why the assembly was not found. It doesn't show the binding error itself and also doesn't show where it tried to find that assembly.
@bitbonk
Copy link
Contributor Author

bitbonk commented Oct 1, 2019

@bitbonk
Copy link
Contributor Author

bitbonk commented Oct 2, 2019

I also tried to add the additional dependency to the AssemblyLoadTest.deps.json file and run it using the following command:

dotnet --additional-deps .\AssemblyLoadTest.deps.json .\AssemblyLoadTest.dll

Still, the assembly does not get loaded and the FileNotFoundException is thrown.

This is my modified AssemblyLoadTest.deps.json:

{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v3.0",
    "signature": ""
  },
  "compilationOptions": {},
  "targets": {
    ".NETCoreApp,Version=v3.0": {
      "AssemblyLoadTest/1.0.0": {
        "dependencies" : {
          "Newtonsoft.Json": "12.0.0"
        },
        "runtime": {
          "AssemblyLoadTest.dll": {}
        }
      }
    }
  },
  "libraries": {
    "AssemblyLoadTest/1.0.0": {
      "type": "project",
      "serviceable": false,
      "sha512": ""
    }
  }
}

@jkotas
Copy link
Member

jkotas commented Oct 2, 2019

Assembly.Load in .NET Core works by default only for assemblies that have been published as part of the app. Is there a reason why you are coping Newtonsoft.Json.dll to your app manually instead of just referencing Newtonsoft.Json NuGet package?

There are two primary ways to solve the problem:

  • (Preferred) Specify the Newtonsoft.Json NuGet package in your .csproj file. It will make Newtonsoft.Json.dll to be part of your app and Assembly.Load will work for it.
    or
  • Load Newtonsoft.Json.dll via Assembly.LoadFile using a full path

@bitbonk
Copy link
Contributor Author

bitbonk commented Oct 2, 2019

Is there a reason why you are coping Newtonsoft.Json.dll to your app manually instead of just referencing Newtonsoft.Json NuGet package?

@jkotas The reason we came across this problem is that one of our public libraries (deployed as a nupkg) depends on an another internal nuget package that

  1. should not show up as a nuget dependency in our public library nuget package because we don't want to publish that internal nuget package
  2. contains APIs that should not be accessible by the consumers of our public library nuget package (at least not out of the box or by accident, the user could still forcibly reference those assemblies, of course)

That's why we set set its assemblies as private assets in the csproj of the public library:

 <PackageReference Include="InternalPackage" Version="12.0.0">
   <PrivateAssets>all</PrivateAssets>
</PackageReference>

And instead just copy the assemblies to the output directory in a nuget build target:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <None Include="$(MSBuildThisFileDirectory)Internal.*" Visible="false">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

In the old days this would have worked because as soon an API in the public library is used, the CLR would have loaded the dependent assemblies automatically from the probing path.

Load Newtonsoft.Json.dll via Assembly.LoadFile using a full path

Based on what I just described, it seems that this is our only option. I have two questions:

  • Does it make a difference whether I use Assembly.LoadFile or AssemblyLoadContext.Default.LoadFromAssemblyPath ?
  • If I load an assembly like this, will it automatically (recursively) load the dependent assemblies too or do I need to load all of the manually as well?

@jkotas
Copy link
Member

jkotas commented Oct 2, 2019

You may consider referencing the InternalPackage with ExcludeAssets tag in your app. It will make the package deployed with the application, without exposing the APIs from the InternalPackage to the compiler:

    <PackageReference Include="InternalPackage" Version="12.0.0">
        <ExcludeAssets>compile</ExcludeAssets>
    </PackageReference>

Does it make a difference whether I use Assembly.LoadFile or AssemblyLoadContext.Default.LoadFromAssemblyPath ?

Assembly.LoadFile loads the assembly into a separate context. The separate context gives you more control over how the dependencies are loaded, e.g. it allows you to load multiple version of the same assembly in the same process.

AssemblyLoadContext.Default.LoadFromAssemblyPath loads in the default context. It basically injects the assembly to be part of the app. It is probably the better option in your case.

will it automatically (recursively) load the dependent assemblies too

It will automatically load the dependent assemblies only if they are part of the app.

@bitbonk
Copy link
Contributor Author

bitbonk commented Oct 2, 2019

It will automatically load the dependent assemblies only if they are part of the app.

What does "part of the app" actually mean? That they are located in the probing path? That they exist in <application>.deps.json?

@jkotas
Copy link
Member

jkotas commented Oct 2, 2019

What does "part of the app" actually mean?

It means that the build system knows about it. If the build system knows about the .dll:

  • It gets listed in .deps.json
  • Tasks like IL linking, AOT compilation will pick it up

@jeffschwMSFT
Copy link
Member

cc @vitek-karas

@bitbonk
Copy link
Contributor Author

bitbonk commented Oct 2, 2019

You may consider referencing the InternalPackage with ExcludeAssets tag in your app. It will make the package deployed with the application, without exposing the APIs from the InternalPackage to the compiler:

<PackageReference Include="InternalPackage" Version="12.0.0">
    <ExcludeAssets>compile</ExcludeAssets>
</PackageReference>

Well first of all, we don't build apps, our customers do. We only build nupkgs that our customers can use.
And if I reference the internal package in the csproj of our public library nupkg using <ExcludeAssets>compile</ExcludeAssets> as you suggested, I can't use any of the APIs from InternalPackage anymore, it won't compile.
So is there any other combination of ExcludeAssets, IncludeAssets, PrivateAssets that would result in the APIs of the assemblies of InternalPackage beeing available in in our nupkg but not in the apps of our customers while at the same time making the assemblies "part of the app" (get listed in .deps.json)?

@jkotas jkotas closed this as completed Jan 28, 2020
@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@msftgits msftgits added this to the Future milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 12, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-AssemblyLoader-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

4 participants