104

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

jbtule
  • 31,383
  • 12
  • 95
  • 128
Gigi
  • 28,163
  • 29
  • 106
  • 188
  • 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. – Hans Passant Mar 12 '17 at 14:35
  • I actually selected Class Library (.NET Core). – Gigi Mar 12 '17 at 14:40
  • 2
    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. – Hans Passant Mar 12 '17 at 14:44
  • That clears it. You can write up an answer from your comments if you like. – Gigi Mar 12 '17 at 14:45

4 Answers4

137

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>
Chris F Carroll
  • 11,146
  • 3
  • 53
  • 61
Aboo
  • 2,314
  • 1
  • 18
  • 23
  • 1
    Do you have to do this by editing the csproj manually, or can it be done via VS? – Gigi Mar 14 '17 at 17:14
  • 1
    @Gigi multi targeting needs to be done manually. Nuget packages can be done via VS2017 or manually. – Aboo Mar 15 '17 at 03:08
  • 2
    I was having the same issue, and everything worked fine after I added the s to . Really wish these things were better documented. – Negorath Mar 17 '17 at 11:02
  • 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. – Asad Saeeduddin Mar 22 '17 at 00:25
  • 2
    @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. – Aboo Mar 22 '17 at 00:44
  • Just tested it, you're right. Thanks! For posterity, I found a good explanation of all this stuff here: https://oren.codes/2017/01/04/multi-targeting-the-world-a-single-project-to-rule-them-all/ – Asad Saeeduddin Mar 22 '17 at 01:29
  • @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! – jbtule Mar 24 '17 at 15:21
  • "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. – O. R. Mapper Oct 08 '17 at 13:24
  • 2
    @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). – Aboo Oct 09 '17 at 00:34
27

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

Sergey Vasiliev
  • 803
  • 8
  • 19
  • 15
    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". – howardlo Apr 26 '17 at 22:40
  • 1
    The example you point at does not include a property. – RenniePet Mar 05 '18 at 06:42
  • 1
    @RenniePet, thank you! The project file has changed in time. I changed the link to a concrete commit, where there is . – Sergey Vasiliev Mar 05 '18 at 07:15
12

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

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 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. – Gigi Mar 12 '17 at 15:09
  • 1
    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. – Hans Passant Mar 12 '17 at 15:20
  • I suspected as much. Thanks a lot for clarifying! – Gigi Mar 12 '17 at 15:25
  • @HansPassant — "...you need to forget that this ever existed." Are you referring here to `project.json`? – InteXX Nov 30 '17 at 07:13
  • 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. – Hans Passant Nov 30 '17 at 08:14
  • 4
    @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. – Stefano Ricciardi Dec 05 '17 at 11:31
  • 1
    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. – McGuireV10 Dec 19 '17 at 19:13
  • 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. – Lunyx Jul 18 '18 at 12:24
8

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.
Chris F Carroll
  • 11,146
  • 3
  • 53
  • 61