dotnet / standard

This repo is building the .NET Standard
3.06k stars 427 forks source link

Issues with .NET Standard 2.0 with .NET Framework & NuGet #481

Closed terrajobst closed 5 years ago

terrajobst commented 7 years ago

Summary

We've designed .NET Standard & our tooling so that projects targeting .NET Framework 4.6.1 can consume NuGet packages & projects targeting .NET Standard 2.0 or earlier. Unfortunately, we've seen a few issues around that scenario. The purpose of this document is to summarize the issues, outline our plan on addressing them, and providing workarounds you can deploy with today's state of our tooling.

Symptoms and root cause

The primary symptom is that applications crash with a FileLoadException or a FileNotFoundException. Another symptom is warnings at build time regarding assembly versions. This is due to one or both of the following issues:

  1. Missing binding redirects
  2. Missing binaries that come from indirect NuGet packages

Missing binding redirects

.NET Standard 1.x was based around contracts. Many of these contracts shipped with .NET Framework 4.5 and later. However, different versions of .NET Framework picked up different versions of these contracts, as by-design of contract versioning. As a side effect of marking .NET Framework 4.6.1 as implementing .NET Standard 2.0, some projects will now start picking up binaries built for .NET Standard 1.5 and 1.6 (as opposed to previously where .NET Framework 4.6.1 was considered as implementing .NET Standard 1.4). This results in mismatches of the assembly versions between what was shipped in .NET Framework and what was part of .NET Standard 1.5/1.6.

This can be addressed by binding redirects. As writing them by hand sucks, we added an Automatic Binding Redirect Generation feature in .NET Framework 4.5.1. This feature is opt-in. Unfortunately, it's not enabled based on target framework, but by which target framework was selected when the project was created (as the feature is turned on via an MSBuild property that is conditionally emitted by the template). In practice, this means it's mostly off if you often upgrade existing projects, rather than creating new ones.

Missing binaries

There are two primary flavors of NuGet: packages.config and PackageReference.

The default for .NET Framework projects is packages.config. This ensures more compatibility because PackageReference doesn't support all the features that packages.config did, for example, PowerShell install scripts and content.

The only supported mode for SDK-style projects (.NET Core/.NET Standard) is PackageReference. This means that a .NET Framework project referencing a .NET Standard project ends up crossing the streams between two different NuGet models. When the .NET Standard project references NuGet packages that the .NET Framework project doesn't reference, the application ends up missing all binaries coming from those packages.

Why has this worked before? Because withpackages.config, all dependencies are copied to each project's output folder. MSBuild copies them up from there. With PackageReference, we don't copy the binaries because it relies on the consuming project to see its dependencies and extract the proper asset itself. This allows the consuming project to pick up the right assets for packages that use bait & switch (which many of the .NET packages must do).

Plan

The plan is to address these issues moving forward as follows:

  1. Converge on PackageReference for all project types, including .NET Framework. The short-term plan for (1) is to start blocking project-to-project references in Visual Studio 15.4 that will end up crossing the streams between packages.config and PackageReference. This block is UI only; you can still edit the reference by editing the project by hand. The error message will instruct you to switch the .NET Framework project to PackageReference if you want to reference a .NET Standard project. Referencing .NET Standard binaries or NuGet packages will not require this, it's only about project-to-project references. In later releases, we plan on providing a converter. The challenge is that packages.config has features we can't offer for PackagReference across the board, in particular PowerShell install scripts and content. We'll need good guidance and mitigations, if applicable.

  2. Ensure binding redirects are on by default. Short term, this means we need to fix our target files to make sure we turn on automatic binding redirect generation. However, binding redirects don't work well in all scenarios, when there is no application project (like unit tests or add-ins). We need to work on a plan to bring the regular “higher wins” binding policy without binding redirects. This needs a proposal and proper vetting, but it seems we've reached the point where this is necessary.

Workarounds

Regular .NET Framework projects

  1. Enable automatic binding redirects in the root .NET Framework application.
  2. Make sure your root application project doesn't use packages.config but uses PackageReference for NuGet packages
    • If you currently don't have packages.config, simply add <RestoreProjectStyle>PackageReference</RestoreProjectStyle> to your project file
    • If you currently do have a packages.config, convert the contents to packages references in the project file. The syntax is like this:
      • <PackageReference Include="package-id" Version="package-version" />

ASP.NET web applications and web sites

  1. Web applications and web sites don't support automatic binding redirect generation. In order to resolve binding conflicts, you need to double click the warning in the error list and Visual Studio will add them to your web.config file.
  2. In web application projects, you should enable PackageReference like mentioned above. In web sites, you cannot use PackageReference as there is no project file. In that case, you need to install all NuGet packages into your web site that any of the direct or indirect project references depend on.

Unit tests projects

By default, binding redirects aren't added to class library projects. This is problematic for unit testing projects as they are essentially like apps. So in addition to what's outlined in automatic binding redirects you also need to specify GenerateBindingRedirectsOutputType:

<PropertyGroup>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>
mungojam commented 7 years ago

Thanks for this, things do seem a little unpolished, so it's useful to have some clarity.

Other things that currently make it painful for us to mix .net framework with .net standard are a FileNotFoundException that will make it difficult for us to migrate our internal packages in a safe way: https://github.com/NuGet/Home/issues/5715 and the lack of visibility of indirect packages when switching to PackageReferences: UserVoice to convert all projects to new common project format

mungojam commented 7 years ago

Is it correct that PackageReference is not supported for anything other than .net core, .net standard and UWP? See the documentation:

At present, package references are supported in Visual Studio 2017 only, for .NET Core projects, .NET Standard projects, and UWP projects targeting Windows 10 Build 15063 (Creators Update).

Hopefully that will change in future as it is an option in Visual Studio and is the workaround that you describe.

0x53A commented 7 years ago

Hi @terrajobst, is there a document detailing which scenarios are supported for .NET Framework 4.6.1 and .NET Standard 1.6?

I created a net461 old-sdk project in VS2017.3 and added a .NET Standard 1.6 NuGet. The build automagically copied the required shims to the output, totalling 100 files. ✅

If I open the exact same project in VS2015 and build, the output contains only 4 files, so the shims are missing. ❌


Where do the shims come from, anyway? I was following some earlier discussions, and I know at first they were deployed via a NuGet package. But later discussions talked about some machine-wide installation. What was the final result? What do we need to deploy to a build server to enable this scenario?

terrajobst commented 7 years ago

@mungojam

Is it correct that PackageReference is not supported for anything other than .net core, .net standard and UWP?

You can use PackageReference in .NET Framework projects, but as I outlined there is a loss in certain features. For instance, if you install packages that rely on PowerShell scripts (for instance, EF6) they will no longer work. Also, packages that require content (i.e. source files that are copied to the consuming project) they will also no work.

@0x53A

Which NuGet package are you consuming? For .NET Standard 1.x, the package is supposed to depend on NETStandard.Library which will bring in the shims.

0x53A commented 7 years ago

@terrajobst repro is here: https://github.com/0x53A/n16test

The nupkg was created calling "dotnet pack" on https://github.com/0x53A/n16test/blob/75fcfdc0d540531c30b805e74bb239e42d13ffea/ns16lib/ns16lib.csproj#L1-L7.

The resulting package looks like this: image

To reproduce my issue:

1) open https://github.com/0x53A/n16test/blob/master/net461exe/net461exe/net461exe.sln in VS 2017.3 and build. 2) note that /bin/ contains 100 files. 3) clean /bin/, re-open in VS 2015.3 and build 4) note that /bin/ contains only 4 files.

FransBouma commented 7 years ago

In vs2015, in a csproj targeting v4.6.2, I wanted to add a reference to Entity Framework Core 2.0, but that failed with this error:

Attempting to gather dependency information for package 'Microsoft.EntityFrameworkCore.SqlServer.2.0.0' with respect to project 'EF Core 2\NWEFCore2.Persistence', targeting '.NETFramework,Version=v4.6.2'
Gathering dependency information took 0,87 ms
Attempting to resolve dependencies for package 'Microsoft.EntityFrameworkCore.SqlServer.2.0.0' with DependencyBehavior 'Lowest'
Resolving dependency information took 0 ms
Resolving actions to install package 'Microsoft.EntityFrameworkCore.SqlServer.2.0.0'
Resolved actions to install package 'Microsoft.EntityFrameworkCore.SqlServer.2.0.0'
Retrieving package 'Microsoft.EntityFrameworkCore.SqlServer 2.0.0' from 'nuget.org'.
Install failed. Rolling back...
Package 'Microsoft.EntityFrameworkCore.SqlServer.2.0.0' does not exist in project 'NWEFCore2.Persistence'
Package 'Microsoft.EntityFrameworkCore.SqlServer.2.0.0' does not exist in folder 'C:\Myprojects\VS.NET Projects\LLBLGen Pro v5.3\UnitTests\EntityFramework\packages'
Executing nuget actions took 62,07 ms
install-package : Could not install package 'Microsoft.EntityFrameworkCore.SqlServer 2.0.0'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.6.2', but the package does not contain any assembly references or content fi
les that are compatible with that framework. For more information, contact the package author.
At line:1 char:1
+ install-package Microsoft.EntityFrameworkCore.SqlServer
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Install-Package], Exception
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PackageManagement.PowerShellCmdlets.InstallPackageCommand

Likely a bug in the Nuget client in 2015 (but I am under the assumption I have the latest version installed), but IMHO this should simply work as .NET 4.6.2 supports .NET standard 2.0

(edit) This does work in vs2017 15.3.x. That's not an excuse to not fix this! Many devs are on 2015 and have no plans to move to 2017 (yet) and as .net standard 2.0 is supported on .net 4.6.2, they should be able to expect this to just work.

I don't know if this is the right location to file this (likely not!), if not please point me to the repo where I should file this, thanks.

isaacabraham commented 7 years ago

cc @forki I wonder what impact this has on Paket vis-a-vis automatic binding redirects (Paket has the option to generate BRs on demand based on the dependency graph).

@terrajobst If there's a BR already in the app.config file and you turn on the automatic BR redirection, who wins?

forki commented 7 years ago

@isaacabraham I have no idea. this will bring interesting new problems to us.

FransBouma commented 7 years ago

And another problem:

Using EF Core 2.0 in a .NET 4.6.2 project: as soon as you create a DbContext derived type, you'll get this error:

System.IO.FileLoadException : Could not load file or assembly 'System.ValueTuple, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
  Exception doesn't have a stacktrace

Likely caused by the same issue as the ValueTuple containing assembly isn't there. This is in vs2017 15.3.x btw.

Brb, throwing some heavy furniture against the wall screaming.

0x53A commented 7 years ago

@FransBouma This looks familiar: https://github.com/dotnet/standard/issues/476#issuecomment-326726613

matthid commented 7 years ago

How about dropping this scenario and make it work "cleanly" with a future version instead of trying to get with the head through the wall?

dasMulli commented 7 years ago

Note that the binding redirection documentation is missing the critical

<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>

property which is crucial to making "classic" test projects work (as well as libraries loaded via reflection).

FransBouma commented 7 years ago

@0x53A yes, I think that's similar. The bindingredirect however kills nunit for some reason (the test is now simply not seen by it and not run).

@dasMulli Thanks for that, but I am at a loss where to add that... adding it to the bindingredirect nor the csproj etc. does make nunit work again.

And here I am hoping that after weeks of fighting with MS pre-alpha quality tooling (Where's the QA testing? We paid for this software!), this week would go smoothly. Nope

(edit) fsck it, converting to new csproj format, perhaps that gives some results.

(edit) for ref: https://github.com/dotnet/sdk/issues/1070 adding the 2 redirects made nunit work again. With the new csproj format the bindingredirect isn't needed, the valuetuple issue isn't popping up, but the attribute referred by @dasMulli are required to make things work.

Man... one could start wondering what 'alpha' quality means these days if 'RTM' is this quality.

aliostad commented 7 years ago

On the announcement, the "primary symptom" link is broken (via @forki )

image

dasMulli commented 7 years ago

@FransBouma I wasn't directly referring to your problem but rather pointing out that the official documentation and announcement issue lacks this information (cc @terrajobst).

For VS 2015, there is an additional extension to install for .NET Standard 2.0 support - When using NuGet 3.6.0, it will print out the link. However, I've tried it but it is currently broken anyway for most scenarios: https://github.com/dotnet/sdk/issues/1539

FransBouma commented 7 years ago

@dasMulli oh sorry about that, but in any case it helped me a lot, so thanks for your input :) Yeah I think 2015 is a lost cause at the moment. Thanks for referring to the nuget link, I didn't know I had to install a separate extension. In my 2015 installation it didn't mention that.

mariusGundersen commented 7 years ago

When you say

simply add PackageReference to your project file

I'm assuming this means the csproj has to use the VS2017 style csproj, as in, it needs to start with <Project Sdk="Microsoft.NET.Sdk"> and it needs to be built using msbuild 15 or the dotnet cli, right? Only working in Visual Studio is not much use for everyone (and I hope this is the majority) who use CI/CD pipelines. So this means that we can't really use netstandard nugets without converting the csproj file to vs2017 style.

dasMulli commented 7 years ago

@mariusGundersen all csproj format ("classic" + "sdk-style") can use PackageReference items since VS 2017 15.2. NuGet tries to detect if there is any <PackageReference> item in the project or if there is a packages.config in the project to determine which "restore style" is to be used.

However, if neither of those are present, the <RestoreProjectStyle> can be set to PackageReference to enable all features of projects using this style. This then enables transitive dependencies from referenced projects also using PackageReference. The "sdk-style" (<Project Sdk="Microsoft.NET.Sdk">) projects already set this by default.

mariusGundersen commented 7 years ago

So I need to install the latest nuget client on the build server to be able to use <PackageReference>? And I guess that as long as the project file is still classic style it needs the <Reference> and <HintPath> to work?

terrajobst commented 7 years ago

@0x53A

Thanks for the repo, we'll take a look.

@FransBouma

Make sure you have the latest NuGet client for VS 2015. It looks your client doesn't know that .NET Framework 4.6.1 supports .NET Standard 2.0. If the error persist after upgrading, I'd file a bug in the NuGet org (https://github.com/NuGet/Home/issues/new).

@isaacabraham and @forki

I wonder what impact this has on Paket vis-a-vis automatic binding redirects (Paket has the option to generate BRs on demand based on the dependency graph).

My recommendation is: don't add BR via a package manager. We've worked with the NuGet folks to disable their BR generation as well. It should be generated during build, because that's the only place where all the necessary context is available. If anything, I'd consider making Paket add the AutoGenerateBindingRedirects property to the project file if it isn't set already.

Eventually, I'd like the desktop binder to do the right thing here, but we'll see whether we can actually pull this off...

If there's a BR already in the app.config file and you turn on the automatic BR redirection, who wins?

The automatic binding redirect feature will attempt to merge them. Generally speaking, manual BRs will be honored and win.

@matthid

How about dropping this scenario and make it work "cleanly" with a future version instead of trying to get with the head through the wall?

What would you do differently from what I outlined in the plan above? To me, the plan is precisely what you're asking for.

@dasMulli

Excellent point. Fixed.

@mariusGundersen

When you say

simply add PackageReference to your project file

I'm assuming this means the csproj has to use the VS2017 style csproj

No, you can use non-SDK style projects with PackageReference.

scottsauber commented 7 years ago

@terrajobst when you say "No, you can use non-SDK style projects with PackageReference" does this mean I can just delete my packages.config and then in my .csproj remove the hint paths and replace with PackageReferences and it'll just work (provided I'm on the correct NuGet version)?

Or is there some other magic you need to do to use PackageReference with non-SDK style projects?

forki commented 7 years ago

Actually we often set it to make sure nuget/build does not mess with what we intended (or at least to know when that happens). As double-entry bookkeeping if you will. Now this usecase is basically broken - or in some sense you are basically removing all use cases of the redirects if you make the automatic all the time.

Am 04.09.2017 9:48 nachm. schrieb "Scott Sauber" notifications@github.com:

@terrajobst https://github.com/terrajobst when you say "No, you can use non-SDK style projects with PackageReference" does this mean I can just delete my packages.config and remove the hint paths and replace with PackageReference and it'll just work (provided I'm on the correct NuGet version)?

Or is there some other magic you need to do to use PackageReference with non-SDK style projects?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/standard/issues/481#issuecomment-327020442, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNOBR7y8_ejww5Pg3tcM878YaubHQks5sfFQPgaJpZM4PKtHI .

matthid commented 7 years ago

@terrajobst

How about dropping this scenario and make it work "cleanly" with a future version instead of trying to get with the head through the wall?

What would you do differently from what I outlined in the plan above? To me, the plan is precisely what you're asking for.

I appreciate the response. Well yes you are dropping packages.config support. But I meant dropping the compatibility of net461 with netstandard20 from the compatibility table and remove all the special handling around this. This would be the honest way of saying: "Well the runtime did not ship with the proper support for netstandard20 but a future version will". What are the reasons which justify the amount of pain you put everyone through and the amount of effort it generates everywhere (not only internally in Microsoft but also externally like in Paket). There must be really important reasons to have this single compatibility connection but I have no clue what they are.

Could this been fixed with net462 or net463? Maybe I'm missing something here.

terrajobst commented 7 years ago

@scottsauber

when you say "No, you can use non-SDK style projects with PackageReference" does this mean I can just delete my packages.config and then in my .csproj remove the hint paths and replace with PackageReferences and it'll just work (provided I'm on the correct NuGet version)?

Two options: remove the packages.config and convert all package references by hand as explained above. You can also control the default for .NET Framework works via Tools | Options | Package Manager | General.

@matthid

But I meant dropping the compatibility of net461 with netstandard20 from the compatibility table and remove all the special handling around this.

Believe me, no one on my team is keen on on the way the net461 support story has unfolded. In a sense, what you're asking has already been implemented: .NET Framework 4.7.1 will ship with full built-in support of .NET Standard 2.0. The challenge is that (1) it hasn't been shipped yet and (2) many of our customers will not be able to upgrade immediately because the target environment is outside their control. The option was to support it the best we can or can it altogether. Yes, this unfortunately requires jumping through hoops but at least there is something app developers can do. If you fully control the environment, by all means, jump to .NET Framework 4.7.1.

terrajobst commented 7 years ago

@forki

Actually we often set it to make sure nuget/build does not mess with what we intended (or at least to know when that happens). As double-entry bookkeeping if you will. Now this usecase is basically broken - or in some sense you are basically removing all use cases of the redirects if you make the automatic all the time.

The problem with double bookkeeping is that automatic binding redirect generation has to assume the project owner knows best and has to honor the BRs that are present. I don't know Paket well enough, but with NuGet we had the issue that NuGet assumed that the same package & version will have consistent assembly versions and not generate redirects based on the referenced assemblies but based on whether package versions differed. If Paket gets it right a 100% then there is arguably nothing against it, but I'd also argue nothing in favor of it either. Outside of .NET Framework you already rely on the binding behavior that higher wins. A generated app.config with the right redirects will do the same, so I don't see a reason why a checked in copy with the project is helping. I just think it causes noise and adds potential for conflicts...

forki commented 7 years ago

Binding redirects where the only way to protect against the issue that nuget can have multiple versions of the same package in the repository and to make sure the right one was selected. Without explicit redirects this is gone and the flaw in the package manager model is still there.

@isaac_abraham I think the issue is not really related to paket since we only added them when the user asked for it. Or at least I can't really see the implications yet. But one thing is for sure - all these changes are not feeling sound yet. But that's probably only my personal feeling.

Am 05.09.2017 1:33 vorm. schrieb "Immo Landwerth" <notifications@github.com

:

@forki https://github.com/forki

Actually we often set it to make sure nuget/build does not mess with what we intended (or at least to know when that happens). As double-entry bookkeeping if you will. Now this usecase is basically broken - or in some sense you are basically removing all use cases of the redirects if you make the automatic all the time.

The problem with double bookkeeping is that Automatic Binding Redirect generation has to assume the project owner knows best and has to honor the BRs that are present. I don't know Paket well enough, but with NuGet we had the issue that NuGet assumed that the same package & version will have consistent assembly versions and not generate redirects based on the referenced assemblies but based on whether package versions differed. If Paket gets is right a 100% then there is arguably no reason nothing against it, but I'd also argue nothing in favor of it either. Outside of .NET Framework, you already rely on the binding behavior that higher wins. A generated app.config with the right redirects will do the same, so I don't see a reason why a checked in copy with the project is helping. I just think it causes noise and potential for conflicts...

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/standard/issues/481#issuecomment-327039160, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNIDuw8N8Nn9S0egMUPdu9bfDds8Vks5sfIjfgaJpZM4PKtHI .

isaacabraham commented 7 years ago

If what @terrajobst is saying is that simply adding that single element to a vbproj, fsproj or csproj magically fixes all binding redirects, all the time, then I'm not averse to using that instead and deprecating Paket support for BRs. But only if this works consistently and can be applied to e.g. net452 projects running on old MSBuild project format.

forki commented 7 years ago

magically fixes all binding redirects

think about what that means. It means there are effectively no binding redirects anymore. So in some sense the feature is gone.

isaacabraham commented 7 years ago

Doesn't it just mean that the redirects are calculated by the runtime to just promote all assemblies to the version that actually is being used?

forki commented 7 years ago

that's effectively the same thing, right? It "calculates a redirect to what it actually uses" - what sense does that even make? It's not "redirecting" anything or checking against an external file if what it uses is what was intented.

KevinRansom commented 7 years ago

@forki, @isaacabraham

I may think of binding redirects slightly differently to you. I'm not sure that I do, but your last questions, indicates a slight difference I think.

I don't think of it as impacting an assembly, I think it impacts the reference to the assembly.

The binding redirect in an app config file, is a policy statement to the runtime. It says to the runtime, when after applying all versioning policies you see an assembly reference in the range MIN to MAX, go and set the version of the reference to X.

What the AutoGenerateBindingRedirects build property does, is to calculate during the build the highest version number of an assembly by examining /r: assemblies and their referenced dependencies. If it sees multiple values for an assembly version, it generates a binding redirect in the app-config to ensure that users of the application doesn't see assembly load errors, because of version mismatches. It then requires the developer to ship this 'highest referenced version' with the app. The build will of course ensure that this is the version copied to the output directory, to make it easy for the developer to identify the correct dll to ship.

I hope this helps, it is for sure how I think of this stuff.

Kevin

esentio commented 7 years ago

Regarding Unit Test projects (as I already mentioned in dotnet/sdk#901):

I'm using VS2017 15.3.3 and .NET Core 2.0.0 SDK. Solution consists of:

I'm hitting FileNotFoundException in B. As a workaround, I put

<PropertyGroup>
  <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>

in B's project file. Using <RestoreProjectStyle>PackageReference</RestoreProjectStyle> has side effect though: all tests in B disappears from Test Explorer window. To fix this issue, I had to upgrade test adapters and test framework via nuget as described here.

Is this intended behavior of package reference or a bug? Either way, I think others might experience it, so I would suggest to update workaround info above.

forki commented 7 years ago

@KevinRansom

yes I do understand redirects and their influence on the runtime ;-) I never said it would influence an assembly.

I think it boils down to the recurring theme that I (and others) happen to use it in a different way than many MS employees may do. For me it makes absolutely no sense to let the build automate it, because then we could just throw it away completely or at least gitignore it or something.

For me it's a way for users to make sure the the build (and deploy!) process is doing the right thing and in the end we have the correct version of the assembly loaded into our program. This is especially important when you have a bigger solution and many dependencies. In such cases we often saw just one version of an assembly (like FSharp.Core/newtonsoft) win and then in the end we had MissingMethodExceptions all the time. We then made sure package management worked and only uses one package in the whole repo - but even that wasn't enough. So binding redirects where needed so that at least at app start you know if things are still sane.

FransBouma commented 7 years ago

@terrajobst

Make sure you have the latest NuGet client for VS 2015. It looks your client doesn't know that .NET Framework 4.6.1 supports .NET Standard 2.0. If the error persist after upgrading, I'd file a bug in the NuGet org (https://github.com/NuGet/Home/issues/new).

here's the thing, in vs2015, I have nuget extension v3.5.1484 installed, but if I browse to the gallery in vs2015 and search for 'nuget' I see it offers v3.4.4.1321. vs 2015 itself doesn't say I need to update the client. The gallery states it's been updated on 6/3/2016 (that's more than a year ago) and the one in vs2015 has been updated on 10-11-2016.

This is in vs2015 update 3. (v14.0.25431.01). AFAIK there's no update 4 for vs2015, and the build of nuget client for vs2015 is very old. I can now go over to the nuget repo and have an unpleasant conversation with the folks over there, but I think there's something else not right: where is the updated client for nuget for 2015?

TIA

KevinRansom commented 7 years ago

I understand.

What algorithm would a developer to use to determine which version of an assembly is correct, given a solution with many dependencies?

Is it something other than : “The highest version of the assembly discoverable during the build”?

isaacabraham commented 7 years ago

@KevinRansom the problem is that you might still implicitly break something e.g. imagine you have two projects A and B. A depends on V1 of dep, B depends on V2. At runtime you pick V2, as it's the higher one. But V2 introduced breaking changes compared to V1 - either behavioural (different code paths), or structural (different interface, change of method signatures).

What happens at runtime when Project A tries to call a method that was in V1 but doesn't exist in V2 any more? Boom - MissingMethodException.

forki commented 7 years ago

Is it something other than : “The highest version of the assembly discoverable during the build”?

yes definetely. because that is often just a mistake. Consider you have 100 projects in your sln. All of them use Newtonsoft (which is not that unlikely). Now one upgrades it version from 5.x to 10.x for some (maybe even good) reason.

Now 99 compile against v5 and one against v10 - these are clearly not compatible in any way. It's just a matter of time until something crashes. And there is no resolution to this. "Highest" is as good as "pick one at random" here.

What uses can do is add an binding redirect to the app project that ensures that everything points to v5 (because that's the one we blessed). If the build now ships v10 then you know this "earlier" during deploy and not during on call night hours.

Of course even this is not a perfect solutions since there are still endless ways to mess up, but practice showed that these tricks work very effectively against common human errors.

KevinRansom commented 7 years ago

Yep,

relying on increasing version numbers places a compatibility burden on developers of long-lived libraries. You are certainly more likely to have missing method exceptions if you downgrade a reference from V2 to V1. The old policy assemblies were abandoned (or at least deprecated) before we even shipped V1.0 of the CLR, because of the complexity of getting the definition of it right.

The coreclr resolver won’t satisfy a higher reference with a lower assembly version, although I suppose a custom loadcontext may circumvent that policy.

For developers who want a simpler and likely correct experience a simple tooling experience such as AutoGenerateBindingRedirects seems like a reasonable approach. For the developer who wants explicit control, she can turn it off, and do it manually, or use tools to help them with another approach such as paket.

Kevin

forki commented 7 years ago

I like the use of "likely correct" - that's exactly what this whole thing feels in regards to soundness. ;-)

Am 05.09.2017 10:48 vorm. schrieb "Kevin Ransom (msft)" < notifications@github.com>:

Yep,

relying on increasing version numbers places a compatibility burden on developers of long-lived libraries. You are certainly more likely to have missing method exceptions if you downgrade a reference from V2 to V1. The old policy assemblies were abandoned (or at least deprecated) before we even shipped V1.0 of the CLR, because of the complexity of getting the definition of it right.

The coreclr resolver won’t satisfy a higher reference with a lower assembly version, although I suppose a custom loadcontext may circumvent that policy.

For developers who want a simpler and likely correct experience a simple tooling experience such as AutoGenerateBindingRedirects seems like a reasonable approach. For the developer who wants explicit control, she can turn it off, and do it manually, or use tools to help them with another approach such as paket.

Kevin From: Isaac Abraham [mailto:notifications@github.com] Sent: Tuesday, September 5, 2017 1:31 AM To: dotnet/standard standard@noreply.github.com Cc: Kevin Ransom Kevin.Ransom@microsoft.com; Mention < mention@noreply.github.com> Subject: Re: [dotnet/standard] Issues with .NET Standard 2.0 with .NET Framework & NuGet (#481)

@KevinRansomhttps://na01.safelinks.protection.outlook. com/?url=https%3A%2F%2Fgithub.com%2Fkevinransom&data=02% 7C01%7CKevin.Ransom%40microsoft.com%7C3d62daffa0e44ee2653408d4f4387f54% 7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636401970887004067&sdata= 508t4AGiqOn3kEDsTzfSDxWmyvB5MXBSbLvE57zeqsc%3D&reserved=0 the problem is that you might still implicitly break something e.g. imagine you have two projects A and B. A depends on V1 of dep, B depends on V2. At runtime you pick V2, as it's the higher one. But V2 introduced breaking changes compared to V1 - either behavioural (different code paths), or structural (different interface, change of method signatures).

What happens at runtime when Project A tries to call a method that was in V1 but doesn't exist in V2 any more? Boom - MissingMethodException.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://na01.safelinks. protection.outlook.com/?url=https%3A%2F%2Fgithub.com% 2Fdotnet%2Fstandard%2Fissues%2F481%23issuecomment-327108370&data=02%7C01% 7CKevin.Ransom%40microsoft.com%7C3d62daffa0e44ee2653408d4f4387f54% 7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636401970887014069&sdata= YikhPPjPtyOOuxNp7WbwpEld5uQVw6686jA%2BOrS2j9M%3D&reserved=0, or mute the threadhttps://na01.safelinks.protection.outlook.com/?url= https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth% 2FAE76FkkpgTEjX7a2rYZh11xDTpfmiRtXks5sfQbcgaJpZM4PKtHI&data= 02%7C01%7CKevin.Ransom%40microsoft.com%7C3d62daffa0e44ee2653408d4f4387f54% 7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636401970887014069&sdata= qbNJMpbkokxH27LX4C2vRXzn5mPsSWmw7D3zjVtjUqw%3D&reserved=0.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/standard/issues/481#issuecomment-327112382, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNDJVSkCtNlmcv8dd2IGzIApcDWQSks5sfQrMgaJpZM4PKtHI .

KevinRansom commented 7 years ago

Yes I used a caveat, but so did you::
Of course even this is not a perfect solutions since there are still endless ways to mess up, but practice showed that these tricks work very effectively against common human errors.

forki commented 7 years ago

Yes absolutely. The thing was also only a workaround for more fundamental issues.

But there is one important thing to notice here. The explicit diff in the app.config file is often valuable, because that's where we can bless things. I just wanted to point that out.

Am 05.09.2017 10:54 vorm. schrieb "Kevin Ransom (msft)" < notifications@github.com>:

Yes I used a caveat, but so did you:: Of course even this is not a perfect solutions since there are still endless ways to mess up, but practice showed that these tricks work very effectively against common human errors.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/standard/issues/481#issuecomment-327113818, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNDd8OL3LD9bKmJvS69cZacEOZs3tks5sfQwzgaJpZM4PKtHI .

KevinRansom commented 7 years ago

@forki I don't disagree with you, I know it sounds like I do, but I don't. I think the position you take, where the developer of the app is the only one who knows the right answer is a decent position to take.

I think I would take a different one though. I would suggest that the most common position is that future versions of useful assemblies will be compatible and incompatibility will be a bug in the library that will be quickly fixed, if found quickly. I could be wrong ... but I know that we try hard to maintain compatibility and to respond quickly when we mess up.

isaacabraham commented 7 years ago

Major releases that introduce breaking changes is a completely normal thing that happens, all the time. That's not a bug and might never be fixed.

KevinRansom commented 7 years ago

Sigh !!!! I wish I worked in a world where I could get away with that, it must be lovely.

0x53A commented 7 years ago

Sigh !!!! I wish I worked in a world where I could get away with that, it must be lovely.

In my experience, breaking changes (potentially each minor version) is the default state if the maintainers don't explicitly promise binary compat.

Sad, but realistic.


Just my two cents and a different take on the app.configs: I like to have binding redirects in the source-tree version of my app.config, because that means I can copy-paste an app.config from source control to production.

Instead I would want the build to verify and fail if the app.config is not compatible with the actual binary output (e.g. if the app.config contains 5.0 but the bin directory contains 10.0).

This is not possible with nuget because dependencies float and so the source tree would be modified on each restore.

But I think this also slightly shows the different philosophies between nuget and paket:

Paket tries to resolve dependencies once and then locks them. Later restores will not modify this state.

Nuget instead resolves each restore, potentially different between runs, making reproducable builds impossible, and then has to implement hacks like generating an app.config at build time to hopefully make things run.

I've said it before, but just wanted to repeat myself: I didn't move our work solution to paket because paket is so great (well, it actually is). I moved it because nuget is so bad.

forki commented 7 years ago

And I think that's exactly why MS position (or let's say my perception of their position) and position of (some not so small part of) the community differs:

1) Microsoft did a really good job in maintaining backwards compatibility for a very long time. 2) It also synchronizes basically all releases of the whole thing to the same day. 3) It uses very few external dependencies (Newtonsoft and xunit and the major ones)

So many of the incompatibilities that we see day to day in the wild just don't happen with these things. So if you only use that part of the ecosystem (which a lot of Microsoft's internal stack does) then you can come to the conclusion that it's not a big deal. There are only smaller conflicts and Microsoft is usually fast to address them in whatever way.

But all I can say is: "reality is a bitch".

The ecosystem is much more messy and many maintainers of packages are just figuring out what the latest bestest way to structure a nupkg is. And it still changes A LOT. So A LOT of different versions are floating around with even misspelled or made up target framework monikers. Things where people assume .NET Standard would work as a standard but crash on mono and and and. It's really really hard. And whenever a user discovered an incompatibility he or she needs a way out.

Yes automatic stuff is nice, but we need to be able to review it easily and to change it if needed. That's the important thing that the ecosystem needs to solve. Dependencies lock files and App.config settings are two ways on that challenge. Regarding binding redirects I would even say a poor man's solution, but still....

Am 05.09.2017 11:12 vorm. schrieb "Kevin Ransom (msft)" < notifications@github.com>:

@forki https://github.com/forki I don't disagree with you, I know it sounds like I do, but I don't. I think the position you take, where the developer of the app is the only one who knows the right answer is a decent position to take.

I think I would take a different one though. I would suggest that the most common position is that future versions of useful assemblies will be compatible and incompatibility will be a bug in the library that will be quickly fixed, if found quickly. I could be wrong ... but I know that we try hard to maintain compatibility and to respond quickly when we mess up.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/standard/issues/481#issuecomment-327118338, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNDM6TR8Z5K5pnMZRDNVMw1AfKyMAks5sfRBkgaJpZM4PKtHI .

terrajobst commented 7 years ago

Binding Redirects

@forki

think about what that means. It means there are effectively no binding redirects anymore. So in some sense the feature is gone.

You're the first person I encounter who thinks of the .NET Framework exact match binding policy as a feature :-)

Would you go so far and say that all the other .NET runtimes (CoreCLR, .NET Native, Mono) are missing a feature, because they will happily bind to the version deployed by the application -- so long the version is equal to or higher than the version that is requested?

@isaacabraham

Doesn't it just mean that the redirects are calculated by the runtime to just promote all assemblies to the version that actually is being used?

Not quite. The way binding redirect works is like this: when an assembly load is requested, the binder applies policy. This includes the runtime unification table, binding redirects, publisher policy stored in the GAC, as well as the AssemblyResolveEvent (not sure about the order). This policy might change the assembly version. After the version is determined, the binder will use the probing locations to find the excact version. If no assembly in that version is found, you get the canonical FileLoadException.

The way we see the "higher version wins" policy is like this: the application can specifcy a set of probing locations. By default, this only includes the application folder. The binder will then load the highest assembly version among all probing locations that satisfies the assembly load request. Allowing for multiple probing locations allows applications with plug-ins (like VS) to coordinate the load when multiple plug-ins share an assembly (like JSON.NET). In that case, the only sensible thing is to load the highest version across all plugins because it resolves plug-in activation order issues. Of course, one could argue that the best way is to load all shared assemblies side-by-side so that each plugin gets its own copy, but that requires multiple app domains or assembly load contexts and doesn't work in all cases where types need to be exchanged.

@isaacabraham

@KevinRansom the problem is that you might still implicitly break something e.g. imagine you have two projects A and B. A depends on V1 of dep, B depends on V2. At runtime you pick V2, as it's the higher one. But V2 introduced breaking changes compared to V1 - either behavioural (different code paths), or structural (different interface, change of method signatures).

That is true but binding redirects cannot solve this issue because there is no version that will work with both A and B. The application is toast. Now, I'd argue the only sensible thing for the app is to load V2 in the hopes hat A wasn't affected by the breaking change introduced in V2.

Issues

@esentio

I'm not aware of this issue. Did you uninstall the packages first / did you migrate the contents of the packages.config file into the PackageReference elements?

@FransBouma

Ah, I see. Seems like it hasn't bee released through the gallery yet. Go to https://www.nuget.org/downloads and select VS 2015 VSIX v3.6.0. I'll follow up with the NuGet team to see why 3.6 wasn't pushed yet.

forki commented 7 years ago

Maybe I'm the first person who expressed that verbally in public Microsoft thread but yes many people use it like that. I didn't come up with it.

Am 05.09.2017 6:03 nachm. schrieb "Immo Landwerth" <notifications@github.com

:

Binding Redirects

@forki https://github.com/forki

think about what that means. It means there are effectively no binding redirects anymore. So in some sense the feature is gone.

You're the first person I encounter who thinks of the .NET Framework exact match binding policy as a feature :-)

Would you go far and say that all the other .NET runtimes (CoreCLR, .NET Native, Mono) are missing a feature, because they will happily bind to the version deployed by the application -- so long the version is equal to or higher than the version that is requested?

@isaacabraham https://github.com/isaacabraham

Doesn't it just mean that the redirects are calculated by the runtime to just promote all assemblies to the version that actually is being used?

Not quite. The way binding redirect works is like this: when an assembly load is requested, the binder applies policy. This includes the runtime unification table, binding redirects, publisher polict stored in the GAC, as well as the AssemblyResolveEvent (not sure about the order). This policy might change the assembly version. After the version is determined, the binder will use the probing locations to find the excact version. If no assembly in that version is found, you get the canonical FileLoadException .

The way we see the "higher version wins" policy is like this: the application can specifcy a set of probing locations. By default, this only includes the application folder. The binder will then load the highest assembly version among all probing locations that satisfies the assembly load request. Allowing for multiple probing locations allows applications with plug-ins (like VS) to coordinate the load when multiple plug-ins share an assembly (like JSON.NET). In that case, the only sensible thing is to load the highest version across all plugins because it resolves plug-in activation order issues. Of course, one could argue that the best way is to load all shared assemblies side-by-side so that each plugin gets its own copy, but that requires multiple app domains or assembly load contexts and doesn't work in all cases where types need to be exchanged.

@isaacabraham https://github.com/isaacabraham

@KevinRansom https://github.com/kevinransom the problem is that you might still implicitly break something e.g. imagine you have two projects A and B. A depends on V1 of dep, B depends on V2. At runtime you pick V2, as it's the higher one. But V2 introduced breaking changes compared to V1 - either behavioural (different code paths), or structural (different interface, change of method signatures).

That is true but binding redirects cannot solve this issue because there is no version that will work with both A and B. The application is toast. Now, I'd argue the only sensible thing for the app is to load V2 in the hopes hat A wasn't affected by the breaking change introduced in V1. Issues

@esentio https://github.com/esentio

I'm not aware of this issue. Did you uninstall the packages first / did you migrate the contents of the packages.config file into the PackageReference elements?

@FransBouma https://github.com/fransbouma

Ah, I see. Seems like it hasn't bee released through the gallery yet. Go to https://www.nuget.org/downloads and select VS 2015 VSIX v3.6.0 https://dist.nuget.org/visualstudio-2015-vsix/v3.6.0/NuGet.Tools.vsix. I'll follow up with the NuGet team to see why 3.6 wasn't pushed yet.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/standard/issues/481#issuecomment-327222970, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNC_FHlAuv8eZtdKslSrqsrhh9RYwks5sfXDTgaJpZM4PKtHI .

0x53A commented 7 years ago

@terrajobst It would be great if you could explicitly clarify the requirements for a) modifying projects and b) building projects.

For example, there is no updated download for VS2013 on https://www.nuget.org/downloads, so I assume that only VS2015 and up are supported?

a) Modifying projects: To install a .NET Standard 2.0 (or 1.6) NuGet into a project, the package manager needs to know about the mapping, so it needs an updated nuget extension. That one seems obvious.

b) building projects: after I have installed the package into the project, all required information should be inside the project file, so do I also need to update the buildserver? I would expect not. Can I still build with VS2013 after I have installed the package using VS2015 with updated nuget extension?

It is ok if really old tool versions like VS2013 are no longer supported, but please make it explicit which scenarios do and which don't work.

isaacabraham commented 7 years ago

@terrajobst

That is true but binding redirects cannot solve this issue because there is no version that will work with both A and B. The application is toast. Now, I'd argue the only sensible thing for the app is to load V2 in the hopes hat A wasn't affected by the breaking change introduced in V2.

Correct. But I think there are two other points to this:

  1. With explicit BRs, you can control which direction you go. Perhaps you don't want to go upwards to the highest version, but prefer to stay at the lower one.
  2. With explicit BRs, until you put the BR in, AFAIK you will immediately get a runtime error (the canonical FileLoadException that you mentioned). It's not perfect, but I'd rather have that and know as soon as it happens - at which point we can make a decision to upgrade our solution or not - rather than risk finding out two months later when my code goes live and a bug appears.

There's value in both approaches. However, my instinctive feeling is simply that this kind of silent "natural selection" of different assembly versions is going to come back to bite all of us. Perhaps I'll change my mind on this as the discussion progresses, or not :-)