How do you multi-target a .NET Core class library with csproj?

53,207

Solution 1

You need to manually edit the project file and add s to the default TargetFramework and basically change it to TargetFrameworks. Then you mention the Moniker with a ; separator.

Also you can put the Nuget package references in a conditional ItemGroup manually or using VS Nuget Package Manager.

Here is what your .csproj should look like:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard1.6;net452</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net452'">
    <PackageReference Include="Microsoft.Azure.DocumentDB">
      <Version>1.12.0</Version>
    </PackageReference>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
    <PackageReference Include="Microsoft.Azure.DocumentDB.Core">
    <Version>1.1.0</Version>
    </PackageReference>
  </ItemGroup>
</Project>

Another workaround I do these days because of missing documentation is that I create a project in VS2015 and form the project.json using the available documentation and intellisense, then open the solution in VS2017 and use the built-in upgrade. I will then look at the csproj file to figure out how to make that configuration happen.

Multi-targeting more esoteric targets without a Moniker:

Microsoft:

PCLs are not recommended+

Although PCLs are supported, package authors should support netstandard instead. The .NET Platform Standard is an evolution of PCLs and represents binary portability across platforms using a single moniker that isn't tied to a static like like portable-a+b+c monikers.

If you want to target a Portable Profile it doesn't have a predefined moniker so Portable Profiles also can't infer TargetFrameworkIdentifier, TargetFrameworkVersion, and TargetFrameworkProfile. Also a compiler constant isn't defined automatically. Finally you have to add all assembly references none are provided by default.

This Example below is taken from a project that used the dynamic keyword so it additionally needed the Microsoft.CSharp assembly, thus you can see how it's references for different targets.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard1.5;net40;portable40-net45+sl5+win8+wp8</TargetFrameworks>
  </PropertyGroup>

  <PropertyGroup Condition="'$(TargetFramework)'=='portable40-net45+sl5+win8+wp8'">
    <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <TargetFrameworkProfile>Profile158</TargetFrameworkProfile>
    <DefineConstants>$(DefineConstants);PORTABLE158</DefineConstants>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.5'">
    <PackageReference Include="Microsoft.CSharp" Version="4.3.0" />
    <PackageReference Include="System.ComponentModel" Version="4.3.0" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='net40'">
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='portable40-net45+sl5+win8+wp8'">
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Windows" />
  </ItemGroup>
</Project>

Solution 2

You can manually edit .csproj file for this and set TargetFrameworks (not TargetFramework) property.

<TargetFrameworks>net451;netstandard1.4</TargetFrameworks>

For example see EFCore.csproj: https://github.com/aspnet/EntityFrameworkCore/blob/951e4826a38ad5499b9b3ec6645e47c825fa842a/src/EFCore/EFCore.csproj

Solution 3

I actually selected Class Library (.NET Core).

That is not the project template you want if your library needs to work on multiple platform targets. With this project template, your library can only ever be used in a project that targets .NETCore. The PCL library approach was retired, you now have to pick a .NETStandard.

You do so by starting the project with the "Class Library (.NET Standard)" project template. You now have the option of picking the .NETStandard version. The current compatibility grid is here.

Hopefully they'll keep that linked article updated. This is in flux, .NETStandard 2.0 was nailed down but does not ship yet. Targeted for Q2 of 2017, end of spring probably, it currently shows as 97% done. I overheard the designers saying that using 1.5 or 1.6 is not recommended, not compatible enough with 2.0

Solution 4

I did a simple guide to multi-targeting net framework and netcore which starts with the minimum 15 second fix but then walks you through each of the complications.

The very simplest approach is:

  1. First, get a netcore or netstandard target working.

Then

  1. Edit the .csproj project file and work through these steps for the other targets.

    1. Change the <TargetFramework> tag to <TargetFrameworks> and add your next target to the list, delimited by ;
    2. Learn about conditional sections in your csproj file. Create one for each target. Use them to declare dependencies for each target.
    3. Add <Reference />s for System.* dlls for any netframework targets just by reading what the build error messages say is missing.
    4. Deal with NuGet <PackageReference />s dependencies in the cases where they are not the same for each target. (The easiest trick here is to temporarily revert to single targetting so that the GUI will just handle the Nuget references correctly for you).
    5. If you must: learn a creative variety of techniques, workarounds and timesavers to deal with code that doesn’t compile on all targets.
    6. Know when to cut your losses when the cost of adding more targets is too high.
Share:
53,207

Related videos on Youtube

Gigi
Author by

Gigi

I run Gigi Labs and Dino's Ultima Page.

Updated on June 05, 2021

Comments

  • Gigi
    Gigi almost 3 years

    When .NET Core still used the project.json format, you could build a class library targeting multiple frameworks (e.g. net451, netcoreapp1.0).

    Now that the official project format is csproj using MSBuild, how do you specify multiple frameworks to target? I am trying to look for this from the project settings in VS2017, but I am able to only target a single framework from the .NET Core frameworks (it doesn't even list the other full .NET Framework versions which I do have installed):

    enter image description here

    • user1703401
      user1703401 about 7 years
      That is not what it is supposed to look like, you ought to get the .NETStandard 1.x choices listed in the dropdown. It is not very clear how this happened, be sure to pick the right project template to get started. Should be "Class Library (.NET Standard)". Looks like you picked the Console App template and then started changing properties, not the correct way. If you in fact used the Class Library template then the install did not go well.
    • Gigi
      Gigi about 7 years
      I actually selected Class Library (.NET Core).
    • user1703401
      user1703401 about 7 years
      Right, so that is the wrong one if you want to multi-target. You have to pick a .NETStandard to make the library usable on more than one platform.
    • Gigi
      Gigi about 7 years
      That clears it. You can write up an answer from your comments if you like.
  • Gigi
    Gigi about 7 years
    How does it work if you have different dependencies for different target frameworks? I mean in project.json you could specify specific dependencies for a target framework.
  • user1703401
    user1703401 about 7 years
    I'm 90% sure that you need to forget that this ever existed. It was a confusing mess that was only a stop-gap measure to bootstrap .NETCore. Use the compatibility grid I linked to.
  • Gigi
    Gigi about 7 years
    I suspected as much. Thanks a lot for clarifying!
  • Gigi
    Gigi about 7 years
    Do you have to do this by editing the csproj manually, or can it be done via VS?
  • Aboo
    Aboo about 7 years
    @Gigi multi targeting needs to be done manually. Nuget packages can be done via VS2017 or manually.
  • Negorath
    Negorath about 7 years
    I was having the same issue, and everything worked fine after I added the s to <TargetFramework>. Really wish these things were better documented.
  • Asad Saeeduddin
    Asad Saeeduddin about 7 years
    I don't get it. You just removed the TargetFramework property, so why would you be able to use it in the ItemGroup condition attributes? When I try doing this in VS2017 I get squiggles everywhere $(TargetFramework) is referenced.
  • Aboo
    Aboo about 7 years
    @AsadSaeeduddin chances are that Resharper is showing you the squiggles and not Visual Studio. The $(TargetFramework) is just used to make an ItemGroup available to a particular framework/Moniker.
  • Asad Saeeduddin
    Asad Saeeduddin about 7 years
    Just tested it, you're right. Thanks! For posterity, I found a good explanation of all this stuff here: oren.codes/2017/01/04/…
  • jbtule
    jbtule about 7 years
    @Aboo I'd like to clarify that PCLs are not recommended going forward instead of .NETStandard, but multi-targeting a PCL in additional to .NETStandard is very necessary to move all of these nuget libraries over to .NETStandard. Nuget libraries depend on any number of hierarchy of nuget libraries, and while .NET Standrad works like a portable library, the API surface is much smaller and started out way smaller. The example project used Profile158 that allows .net4.5, same project could only move to .net standard 1.5 for it's api use, which only works on .net 4.6.2 or greater!
  • howardlo
    howardlo about 7 years
    Thanks! It kills me. In Brian Kernigan's "The Elements of Programming Style" written four decades ago, he talks about the mistakes of using variables that differ by a single letter at the end. It would have been a lot clearer if the name was "TargetFrameworkList".
  • O. R. Mapper
    O. R. Mapper over 6 years
    "you can put the Nuget package references in a conditional ItemGroup (...) using VS Nuget Package Manager" - how? I don't see any option for that when I add a NuGet package reference.
  • Aboo
    Aboo over 6 years
    @O.R.Mapper if the NuGet package is constructed correctly this should happen automatically when you import. Meaning if NuGetPackageA supports multiple frameworks already, you don't need to put it in a conditioned item group. Now if you need to reference PackageA for .net framework and PackageB for .net core then that needs to be put in conditioned item group. There is no option in the interface as of today (Oct 2017).
  • InteXX
    InteXX over 6 years
    @HansPassant — "...you need to forget that this ever existed." Are you referring here to project.json?
  • user1703401
    user1703401 over 6 years
    That too. The recent updates for the full framework, Xamarin and UWP removed the last few obstacles to adopting .NETStandard 2.0. I am now 98.23% sure that multi-targeting is no longer useful. So forget this ever existed.
  • Stefano Ricciardi
    Stefano Ricciardi over 6 years
    @HansPassant multi-target is still the best option if you have legacy code and at the same time your greenfield development can be done in one of the recent full framework flavours or dotnetcore.
  • McGuireV10
    McGuireV10 over 6 years
    Not even greenfield is necessarily .NET Core friendly yet. I'm using Azure Function Apps but their .NET Core version only supports a couple of triggers. (I need Service Bus triggers, so I'm stuck with .NET Framework, and a very large business-functionality library may wind up as multi-target.) Microsoft's platform is a tangled mess right now. It's the modern equivalent of DLL Hell.
  • RenniePet
    RenniePet about 6 years
    The example you point at does not include a <TargetFrameworks> property.
  • Night walker
    Night walker about 6 years
    @RenniePet, thank you! The project file has changed in time. I changed the link to a concrete commit, where there is <TargetFrameworks>.
  • Lunyx
    Lunyx almost 6 years
    This should be much higher up. Everything else I had seen was talking about targeting multiple framework versions and having multiple versions of the library compiled for each framework.