dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.61k stars 1.04k forks source link

How do I force myProject Choose .NetStandard assembly instead of TargetFramework #1791

Open John0King opened 6 years ago

John0King commented 6 years ago

Today , There are Asp.Net (System.Web.HttpContext) and Asp.Net Core (Microsoft.AspNetCore.Http.HttpContext) in .net Framework , and there are a few packages use .net Framwork with System.Web.HttpContext and .net Core (.net standard) with Microsoft.AspNetCore.Http.HttpContext for sharing source , so when I use Asp.Net Core on .Net Framework , I just can not use that package because that package fouce me to use System.Web.HttpContext , but there is an available assembly in that package, How do I choose that ?

livarcocc commented 6 years ago

Can you give us some more information here? What does your project look like? Could you share that? It will pick the appropriate API surface based on your Target Framework.

John0King commented 6 years ago

@livarcocc That's the problem. for example I create a FooLib package with :

<TargetFramework>net45;netstandard2.0</TargetFramework>

<PackageReference Condition="'$(TargetFramework)' == 'NetStandard2.0'" Include="Microsoft.AspNetCore.Http" />
<Reference Condition="'$(TargetFramework)' != 'NetStandard'" Include="System.Web" />

and some code

#if NETSTANDARD20
var context  = IHttpContextAccessor.HttpContext;
#else
var context  = System.Web.HttpContext.Current;
#end if

var q = context.Request["q"];

if I use that lib in an Asp.NET Core application ( .NetFramework Runtime ) , The App will Choose Net4.5's version.

Is there any way to force my app to choose .net standard version ?

livarcocc commented 6 years ago

From the conditions you have in your project above, it seems like the Reference will always be added. Because you are checking against NetStandard alone.

John0King commented 6 years ago

@livarcocc I know it's bad, and I'll never use that pattern , but I can not controll other people to use that like this. There are a few Nuget packages are packaged like this. (they try to make this package work both on Asp.net Core and traditional asp.net , but they forget the asp.net core can running on full .net framework too),

I hope something like this : in my main app I can do

<TargetFramework>net461</TargetFramework>
...
<PackageReference Include="OneOfThePackage" Version="1.0.0" Choose="NetStandard2.0" />

Is there any attribute to do this ?

livarcocc commented 6 years ago

You can condition it with something like this:

  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
    <PackageReference Include="OneOfThePackage" Version="1.0.0" />
  </ItemGroup>

Is there a reason why something like this would not work for you? We use it like this our own projects.

dasMulli commented 6 years ago

they try to make this package work both on Asp.net Core and traditional asp.net , but they forget the asp.net core can running on full .net framework too

I've seen this before - assuming that netstandard* assets will be used on ASP.NET Core and net* assets on classic ASP.NET.

You correctly assume that this does not account for ASP.NET Core on .NET Framework. It is a package authoring issue that needs to be resolved by the NuGet packages' authors who will need to publish a different package for ASP.NET Core in addition to the package for classic ASP.NET.

John0King commented 6 years ago

@dasMulli

It is a package authoring issue that needs to be resolved by the NuGet packages' authors who will need to publish a different package for ASP.NET Core in addition to the package for classic ASP.NET.

That's the right way, but there is an available assembly(.net standard assembly) in that package!!
if I don't use nuget , and menuly reference that .net standard dll , that works .

and there are other condition may need this: many nuget package target net45 and netstandard2.0 , and when you target net471 , which assembly will use ?

That's why I hope there is a choose attribute on PackageReference node

dasMulli commented 6 years ago

I do feel your pain using this. I don't work for microsoft or the .net foundation so I can only give hints to this.

apart from the asp.net core/classic selection, I have also seen issues with different public APIs in NuGet packages: If a netstandard project uses a library, it will compile against netstandard assets. if a net project (csproj) then references this project (csproj), it will use the project reference compiled against the netstandard assets but consume the package's net assets. This even applies when the netstandard project is published as a NuGet package.

Are you having trouble using a public NuGet package? I could probably cook up custom hacky msbuild targets modifying your compile references as a last resort

b-anand commented 5 years ago

I have similar problem. I have a utilities project targeting .net standard which references a third party nuget. I have another exe project referencing the utilities project.

The third party nuget has both .net framework and .net standard dll in it but they have been compiled with different versions of Unity nuget (Unity made a breaking change across version 4 and 5 by changing the dll names). The .net framework version of the dll in third party nuget references version 4 of Unity (Microsoft.Practices.Unity.dll) and .net standard version of the dll in the third party nuget references version 5 of Unity (Unity.dll).

The utilities project in my code is targetting .net standard and references the aforementioned nuget so it compiles against the .net standard version of the dll.

The exe project which references the utilitites project above targets .net Fx and will pick the .net framework version of the dll from the nuget and place it next to the exe.

At runtime the utilities project fails saying the Unity class/method not found.

If there was a way to force the exe project to reference the .net standard version of the nuget then we would binplace the .net standard version of the dlls which would then just work.

carlosrpg commented 5 years ago

I Have the same problem here, I Have a net472 app that requires to be full framework, and I consume a nuget (netStandard2.0), this nuget have a reference for another nuget that provides both net45 and netstandard2.0. Now I'm stuck at using the net45 version cause I can't control how the targetframework will be choosed. This would not be a problem using packages.config because I could just add a reference to this dependency and choose the targetframework for netstandard2.0 but now using PackageReference this is not possible.

This is definitely PackageReference bug or removed feature (that should have being disclaimed).

fschmied commented 5 years ago

Same problem here: We reference NUnit via PackageReference from a net47-csproj. The NUnit package contains net45 and netstandard1.6 libs. NuGet automatically resolves to net45, although netstandard1.6 would also be compatible.

We need to use the netstandard1.6 version because of a certain feature, but this doesn't seem possible at the moment.

cortex93 commented 5 years ago

Same issue : https://github.com/NuGet/Home/issues/7385

John0King commented 5 years ago

suggest to add 2 attributes on <PackageReference />

artimari commented 5 years ago

+1

Working inheritance structure:

[API.csproj] :: net47
    [Infrastructure.csproj] :: netstandard2.0
        PackageReference -> [My.MultiTarget.Package] :: net45, netstandard2.0

I would be looking to 'weight' the selection of the netstandard2.0 assembly over the net45 one when net47 is selected as the TargetFramework and the reference .nupkg contains both targets.

I would think the solution would present something like this:

<ItemGroup>
   <PackageReference Include="My.MultiTarget.Package" TargetFramework="netstandard2.0" Version="1.0.0" />
</ItemGroup>

In the meantime, there is a path to working around it in certain architectures via a second project that targets netstandard2.0 ref'd from the first that targets net47 with CopyLocalLockFileAssemblies set to true:

<PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
cortex93 commented 5 years ago

+1

Working inheritance structure:

[API.csproj] :: net47
    [Infrastructure.csproj] :: netstandard2.0
        PackageReference -> [My.MultiTarget.Package] :: net45, netstandard2.0

I would be looking to 'weight' the selection of the netstandard2.0 assembly over the net45 one when net47 is selected as the TargetFramework and the reference .nupkg contains both targets.

I would think the solution would present something like this:

<ItemGroup>
   <PackageReference Include="My.MultiTarget.Package" TargetFramework="netstandard2.0" Version="1.0.0" />
</ItemGroup>

In the meantime, there is a path to working around it in certain architectures via a second project that targets netstandard2.0 ref'd from the first that targets net47 with CopyLocalLockFileAssemblies set to true:

<PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

Doesn't work in my case with a root project targeting net461. It still detect package version outside of dependency constraint.

matteo-prosperi commented 5 years ago

+1

We are experiencing this problem as well. We would like to make the netstandard 2.0 version of our libraries the "preferred" version and suggest all customers to use them in place of the legacy platform specific versions that are available in the same nuget package. But customers using the PackageReference project format don't have a way to specify that they want to use the netstandard 2.0 version.

We are also experiencing the issue described by @bigbangtheorem: a framework 4.5 application using a netstandard 2.0 class library that uses our multigargeted nuget package (both framework 4.5 and netstandard version are available in the package) will restore the framework 4.5 version of the multitargeted library. This looks more like a bug to me considering that the netstandard 2.0 library was never meant to be used with the framework 4.5 version of its dependency and may very well be incompatible with it.

@livarcocc I can provide you with more information and sample projects if you are working on this.

rustyemmert commented 5 years ago

Thank you @bigbangtheorem for the workaround.

msallin commented 5 years ago

In our case, we want to share a bunch of base libraries between .NET Core 2.2 and .NET 4.7.2 projects. Therefore, the base libraries need to be ported to .NET Standard 2.0, we did this successfully. One of the base library uses Log4net. The log4net assemblies for net45 and netstandard have different method signatures.

The result is an exception at runtime as the following happens: Root Project [FullFx] --> Lib [netstandard] --> Log4net [net45] Root Project [netcore] --> Lib [netstandard] --> Log4net [netstandard]

We need a way to force netstandard for the referenced NuGet.

yyjdelete commented 5 years ago

+1 for consider transitive dependencies, but want it to be smarter when choose between some runtime-library like System.Data.SqlClient or System.Drawing.Common(the ns2.0 version always throw PlatformNotSupportedException), or at least make an warns(for main program and package as .nupkg) in that case and the dependencies tree struct in VS should be the same as SDK does.

Have an similar issues for use any(No build-time warns, so it's hard to see whether it's affacted or not) library which target only ns2.0 and depend an multi-target lib with different dependencies for different target, and then use it in net472 program. And see no warns at build time, but an FileNotFoundException at runtime, seems transitive dependencies never works in that case, and no warns is shown as it does without transitive dependencies. See dotnet/sdk/issues/3103

Dapper<-System.Data.SqlClient

Main(net472)
|-Lib1(netstandard2.0)//And use `System.Data.SqlClient.SqlConnection` transitive from Lib2 directly in the lib, or use any public API which export `System.Data.SqlClient`
    |-Lib2 or [Dapper](https://www.nuget.org/packages/Dapper)
        |-System.Data.SqlClient, Version=4.5.0.0(netstandard2.0)
        |-System.Data(net451, frameworkAssembly)

Wanted:

Main(net472)
|-Lib1(netstandard2.0)//And use `System.Data.SqlClient.SqlConnection` transitive from Lib2 directly in the lib, or use any public API which export `System.Data.SqlClient`
    |-Lib2(net451)
    |-System.Data.SqlClient, Version=4.5.0.0(net461)

And see no build-time error, but System.Data.SqlClient is not published with the main program and an runtime expection FileNotFoundException with System.Data.SqlClient, Version=4.5.0.0, lib1 can also be publish as an nuget package without warns. Visual Studio also show an different dependencies tree struct(Dapper ns2.0+System.Data.Client instead of net451) for the reference as the sdk does for project.assets.json.

Tried set CopyLocalLockFileAssemblies=true, but

  1. Still get an System.MissingMethodException if use an public API of Lib2 which export System.Data.SqlClient.SqlConnection.
  2. It use System.Data.SqlClient.dll(netstandard2.0) which is unusable that it throw PlatformNotSupportedException for every methods in the case.
  3. Unable to use it for nuget packages.
Skleni commented 4 years ago

I just had the same problem. Unfortunately @bigbangtheorem's workaround did not work for me for some reason, but this one by @duanenewman did. Maybe it helps someone else.

hotchkj commented 3 years ago

I concur that this is a feature that is needed because you end up having to fight MSBuild/NuGet and the default way of selecting packages when you are solving more esoteric problems. For example, we're consuming an open source package that has net45;netstandard2.0 targets available in NuGet. The NET 4.5 code is ancient and exhibits buggy behaviour. It's all very well saying 'fix the package' but we don't own it, the owners have gone, and it's a critical part of current consumed graph (again, none of which we own but have to consume to work with other people's services).

It's exceptionally frustrating to have no control over this as well as NET changing its mind in each subsequent layer of the build.

We have:

So to make matters worse, we test the first project and everything looks fine. I'm sure this behaviour is correct and normal for the vast majority of cases, but once tracked down, actually fixing it should be easier than the workarounds listed. (Though I'd be intrigued to understand why net45 is considered higher in precedence than netstandard2.0 as a match for net472 ...)

I notice this also got made a NuGet feature request over in NuGet/Home#7416 and am curious as to which component actually needs the feature adding. It feels like the problem is the selection by PackageReference.

duanenewman commented 3 years ago

Thanks @Skleni, glad that has helped you. I have a followup post that removes breakage due to version # updates here

It would be great to be able to specify a TargetFramework on a PackageReference like suggested above by @bigbangtheorem

this one by @duanenewman did.

dobsa commented 3 years ago

+1 for adding some feature to resolve this. Today I have spend 1/2 day looking for workarounds. So far no luck. I would like to make all our binaries to use netstandard2.0 version of a dependent library. ATM those binaries compiled for netcoreap3.1 target just do not work, because those compiled for net472 bring in the dependent library for net472. While all binaries happily work with netstanderd2.0 version of the dependent library.

LittleLittleCloud commented 3 years ago

+1 want to use a package target to both net45 and netstandard2.0. my app is net472 the API I want to use is only available in netstandard2.0 because of nuget automatically choose net45 for me thoughtfully I can't use the API I want, which should be available...

raymondbrink commented 3 years ago

want to use a package target to both net45 and netstandard2.0. my app is net472 the API I want to use is only available in netstandard2.0 because of nuget automatically choose net45 for me thoughtfully I can't use the API I want, which should be available...

For clarification and to hopefully help others: The work-around for this issue (.NET Framework application picking the net45 or net47 version of a lib over the netstandard20 version from the same NuGet package) is to change the HintPath in your project file:

From: `

..\packages\MyPackage.1.0.0.0\lib\net45\MyPackage.dll

To:

..\packages\MyPackage.1.0.0.0\lib\netstandard2.0\MyPackage.dll

`

Only downside is you have to do this after every package upgrade.

duanenewman commented 3 years ago

@brinkie2004

Only downside is you have to do this after every package upgrade.

if you use the GeneratePathProperty attribute on the package reference and use the generated variable $PkgPackage_Name in your hint path the version # is not an issue. My blog post (referenced above) talks about it: https://duanenewman.net/blog/post/a-better-way-to-override-references-with-packagereference/

jmeijrink commented 1 year ago

I had this same issue and solved it with the article mentioned by @Skleni in his comment above (https://github.com/dotnet/sdk/issues/1791#issuecomment-540985034). In our case System.Configuration.ConfigurationManager.6.0.1 package contains a different assembly version for net461 (6.0.0.1) than for netstandard2.0 (6.0.0.0) which caused build warnings. I solved it with below xml snippet:

<PackageReference Include="System.Configuration.ConfigurationManager" GeneratePathProperty="true" ExcludeAssets="Compile;Runtime;Build" />
<Reference Include="System.Configuration.ConfigurationManager">
 <HintPath>$(PkgSystem_Configuration_ConfigurationManager)\lib\netstandard2.0\System.Configuration.ConfigurationManager.dll</HintPath>
</Reference>

It's slightly optimized in comparison with the snippet mentioned in the article by generating a path property that can then be used in the reference. Also more assets were excluded, as only excluding Compile did not work for my case.