JoshClose / CsvHelper

Library to help reading and writing CSV files
http://joshclose.github.io/CsvHelper/
Other
4.75k stars 1.06k forks source link

System.Io.FileNotFoundException: Could not load file or assembly 'Microsoft.bcl.AsyncInterfaces, V1.0.0.0' #1597

Closed mocolicious closed 3 years ago

mocolicious commented 4 years ago

When using V15.0.0.6 when I compile a release package it doesn't work on the deployed server and gives

System.Io.FileNotFoundException: Could not load file or assembly 'Microsoft.bcl.AsyncInterfaces, V1.0.0.0'

works fine on local instance of Visual Studio. If I downgrade to version 12.3.2 everything works fine. From my perspective the latest versions of CsvHelper don't support production use because of the compilation bug

To Reproduce Steps to reproduce the behavior:

  1. create .net core project
  2. use version 15.0.0.6
  3. create a web deploy package or compile build files
  4. deploy to external server
cybertyche commented 3 years ago

I am also having this problem, and reverting to 12.3.2 isn't an option for me.

JoshClose commented 3 years ago

How are you creating the release code?

cybertyche commented 3 years ago

msbuild from a root directory - Microsoft.Bcl.AsyncInterfaces is a package we reference elsewhere with 5.0.0.

mocolicious commented 3 years ago

using msbuild and then package deploy to .zip. Target framework netcoreapp2.2. tried it with multiple different target frameworks and had consistent problems.

JoshClose commented 3 years ago

Is Microsoft.Bcl.AsyncInterfaces included in the zip you're deploying?

mocolicious commented 3 years ago

I've tried manually adding it and it doesn't work.

cybertyche commented 3 years ago

I was able to work around the issue by adding version redirects to everything in the world, including every .config file in the product.

JoshClose commented 3 years ago

Wouldn't you just need a redirect for Microsoft.Bcl.AsyncInterfaces?

cybertyche commented 3 years ago

That's what I did, but I needed to do it in different places for different deployments, some of which made no sense. Things were working locally but not elsewhere.

JoshClose commented 3 years ago

There is another version of Microsoft.Bcl.AsyncInterfaces somewhere? Possibly in the GAC?

mocolicious commented 3 years ago

@JoshClose I think that's what it is, its referencing it somewhere globally by default so it works in the debugger but on deployment does not

brase commented 3 years ago

With 27.0.2 (in a .net 4.7.2 webapp) we face this bug too. On our dev machines it is working. On the Build-Server we have failing tests with the runtime exception mentioned above. Adding a assembly redirect fixes it:

  <dependentAssembly>
      <assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
  </dependentAssembly>

The strange thing is, that the exception says v1.0.0.0 is missing. Why it wants 1.0.0.0 instead of the higher version actually referenced?

nukeme1 commented 3 years ago

To be clear, We are using 27.0.2 in a .net standard 2.0 lib and this lib is tested in a xunit project that targets framework 4.7.2. The only way we were able to make this work was by pinning the transitive dependency of microsoft.bcl.asyncinterfaces to 1.1 in the .net standard library project.

JoshClose commented 3 years ago

FYI: I found an issue submitted to dotnet regarding this, and they said that binding redirects are the correct way to solve this, and it's a feature. This is intended behavior. There isn't anything on the CsvHelper side that can be changed to "fix" it, from what I understand.

nukeme1 commented 3 years ago

Interesting, I know that we currently configure our projects to auto-generate binding redirects, so that is no longer sufficient and we must somehow know what packages we are using have as transitive dependencies and manually add binding redirects in our projects? Something about that just does not seem right to me. Normally, if you have a direct dependency and this dependency requires a specific version to work, the onus should normally be on you to pin the dependency to the correct version, not downstream consumers of that package... but I may be mistaken.

JoshClose commented 3 years ago

As a library, CsvHelper should be able to use whatever version it needs. Consumers of CsvHelper shouldn't have to worry about what version of any library is being used, and binding redirects should happen automatically in Visual Studio.

I know that we currently configure our projects to auto-generate binding redirects, so that is no longer sufficient and we must somehow know what packages we are using have as transitive dependencies and manually add binding redirects in our projects?

I have no clue. From my reading on how things are supposed to work, I as a library shouldn't worry about it. If things aren't working correctly, it's something that needs to be filed with .NET or NuGet.

Please link any relevant issues you find/create.

Here is another issue talking about this. #1488

JoshClose commented 3 years ago

And another. #1462

nukeme1 commented 3 years ago

Strange because if I pin the transitive dependency to 1.1 for microsoft.bcl.asyncinterfaces, it all works. If you configured csvhelper to be pinned to that version it probably will "just work" for downstream consumers afterwards, wouldn't it? If I understand what is happening here, it's that v5.0.0 of Microsoft.Bcl.AsyncInterfaces is not compatible with csvhelper 27.0.2 because when I check our output folder, bcl.Async is there but cannot be loaded by csvhelper. by pinning that dependency to 1.1, then it all works, so either csvhelper needs to adapt the code to work with bcl.asyncInterfaces 5.0.0 OR it needs to pin that dependency to v1.1 which is the version that seems to be compatible from our testing here.

JoshClose commented 3 years ago

If I update to 5.0.0, will that cause problems for people using 1.1?

What is pinning? Do you have a link to the docs that discuss that?

This is what the references look like now.

<!-- .NET 4.5 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
    <PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
    <PackageReference Include="System.Buffers" Version="4.5.1" />
    <PackageReference Include="System.Memory" Version="4.5.4" />
    <PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>

<!-- .NET 4.7 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
    <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" />
    <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
    <PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
    <PackageReference Include="System.Buffers" Version="4.5.1" />
    <PackageReference Include="System.Memory" Version="4.5.4" />
    <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.2" />
</ItemGroup>

<!-- .NET Standard 2.0 -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
    <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" />
    <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
    <PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
    <PackageReference Include="System.Buffers" Version="4.5.1" />
    <PackageReference Include="System.Memory" Version="4.5.4" />
    <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.2" />
</ItemGroup>

<!-- .NET Standard 2.1 -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
    <PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
</ItemGroup>
nukeme1 commented 3 years ago

When I say pin it's probably because that is how we refer to it when using paket. Paket allows to specify version or version subrange using semantic versionning to pull dependencies, for example a dependency of nuget somePackage ~> 5.5 will fetch the most recent available version of that package within 5.5 so if there is a 5.5.0 and 5.5.2 it will pull 5.5.2. if there is a 6.0 it will only fetch 5.5.2. There clearly is some breaking change between v5 and v1.1 of asyncinterfaces which is stopping it from being functional with csvhelper (so they probably have a breaking change here and that forces you to also have a breaking change or have some adaptation code that allows to step-up whatever is not compatible. Currently, you have to understand that your package shows as supporting any bcl.asyncinterfaces >= 1.1.0, since there is a v5 now available, any consumers of your package will automatically be pulling v5 and encounter the issue we are having unless they pin that package (force a specific version of that package) to 1.1. So your options are making it so your package does not support >= 1.1.0 but specifically 1.1.0 OR adapt the library to work with v5 and depending on what the breaking change is, you may need to have some adaptation code to make whatever that library consumes compatible from the old data. Your config looks good though, I'm starting to suspect that this may be a problem with paket, the other people having similar issues here are probably also on something like paket I imagine.. well, that is, if nuget reference for dependency locking is to be believed.

nukeme1 commented 3 years ago

wait. I just noticed you specified 1.1.0, if you specify [1.1.0] then it should change the dependency to be = to 1.1.0 on your package instead of >= 1.1.0. as per package versioning

nukeme1 commented 3 years ago

so the issue is 2-fold. 1: paket is not respecting the correct behaviour, since what you specified should normally take 1.1.0 or the closest minimal version available if that version is not available. This is a paket issue. But you are not actually compatible with v5 which is the latest available version for the async package, so you could more explicitly control the allowed versions by using the ( or [ etc markup for version referencing as specified in the package versioning doc which would be good since you don't want people getting v5 because it is clearly not compatible.

JoshClose commented 3 years ago

Microsoft.Bcl.AsyncInterfaces 5.0.0 uses a higher version of System.Threading.Tasks.Extensions than what is referenced. Maybe that's where the issue lies.

I didn't know about the package version ranges. That's good to know.

I updated all the package references to the latest and there is no issue. I could release that and have all future release use the latest referenced packages.

nukeme1 commented 3 years ago

maybe that is the issue, but it really is trying to resolve v 1.0.0 of bcl.async when version 5 is available which is really weird. but yes, updating all the refs (and possibly specifying with the square brackets or with a subrange such as 5.0. or 5.) would ensure that this does not happen until you actually want people to be getting a more recent version of those dependencies due to new features etc.

nukeme1 commented 3 years ago

I'm starting to think that having packages configured with >= someVer is really not good since you are effectively at the mercy of whatever logic the package manager is using, in the case of nuget it means that version or next closest match, paket apparently sees that as latest available since you stated >= someVer.
For example: "Many package authors made the mistake of omitting the brackets, effectively specifying >= 1.2.3 when they wanted to specify = 1.2.3" taken from paket controlling nuget resolution

nukeme1 commented 3 years ago

And as an aside, I really must commend you for your rapid response time. You really maintain this package well and it is quite nice dealing with you :)

JoshClose commented 3 years ago

Many package authors made the mistake of omitting the brackets, effectively specifying >= 1.2.3 when they wanted to specify = 1.2.3

This is the default when installing a package via NuGet. I didn't even know about the version ranges. I think a major version range should be ok.

And as an aside, I really must commend you for your rapid response time. You really maintain this package well and it is quite nice dealing with you :)

Thanks! I'm not always this responsive. It really depends on many life factors. ;)

nukeme1 commented 3 years ago

I agree that a major range should normally be good enough. as for the responsiveness, very understandable, I guess I've just been lucky :) At any rate, your support is much appreciated!

JoshClose commented 3 years ago

I should probably specify a specific version with brackets. That will ensure it always works, correct?

nukeme1 commented 3 years ago

well, it locks that specific version of the dependency, which is generally a good thing in my opinion because that is the version that you used and tested. Until it is either no longer available, has a security issue or you need to update for other reasons, I think locking to specific versions is in fact preferable. The exception to this rule would be in the case of dependencies that you control directly, in which case, you know what you are doing and may want your downstreams to automatically take the latest at all times etc. but generally, yes, locking a specific version or small sub-range would probably be a better approach, certainly a more deterministic one where package resolution is concerned.

JoshClose commented 3 years ago

I changed all the references to exact version match. Between that and redirects, I think we should be good.