This article describes the process that is necessary to enable multi-platform, multi-target
builds; specifically, it's designed for users who are attempting to enable multi-targeting
of .NET Framework when running on Linux or macOS. It leverages the .NET SDK command line
dotnet) to do builds and test execution.
This documentation will include links and version numbers that were valid at the time it was created. Newer package versions and links may become available in the future, so your results may be slightly different.
The requirements to support multi-targeting on Windows and non-Windows OSes are different.
For Windows OSes, it is assumed that the (one and only one version) of .NET Framework will
be installed as part of your Windows installation, so the only required step is to enable
it. The build system for the .NET SDK already understands how to build .NET Framework
applications on Windows, and
dotnet test already understands how to run them.
On non-Windows OSes, you're missing two key components: the libraries that are required to compile your applications, and the .NET Framework runtime. The former are added (in a somewhat convoluted way) through NuGet packages, and the latter comes from Mono: an open source implementation of the .NET Framework that's available for Linux and macOS (the other two officially supported platforms for the .NET SDK).
Unlike most NuGet packages, the process of enabling .NET Framework support for your multi-targeted
projects is not just a matter of adding a single package. Instead, the process is split up into
two steps: identifying the package with your libraries, and updating the
to include the directives to allow the C# compiler to find the .NET Framework reference libraries
when building your executable.
As of the writing of this article, these libraries (created by the .NET team at Microsoft) are housed on MyGet, rather than the more traditional NuGet repository. In order to find the package you want, you need to visit the .NET Core Gallery on MyGet and find your framework's package version.
On the gallery page, go to the Search box and type
This will limit the package list to those packages that provide the reference libraries for the
.NET Framework. Find the one that matches the version of the .NET Framework that you're targeting,
and note it's version number. (For our example, we'll be using package
Microsoft.TargetingPack.NETFramework.v4.5.2 which is currently version
There are three (or four) changes to make to your
.csproj file to enable multi-targeting,
supporting both Windows and non-Windows machines.
Make sure you've updated your
<TargetFrameworks> element to include the .NET
Framework version you're planning to target. Note that if your project was previously single-targeting
.NET Core, you will need to change
<TargetFramework> (singular) to
<TargetFrameworks> (plural). In our example, we're adding
netcoreapp2.1 test project:
You will need a NuGet package reference for the .NET Framework reference libraries. You will add
this to a conditional
<ItemGroup> inside your
.csproj file. For
our example, the item group looks like this:
You will replace the exact package name and version with the one you found in the step above. Since the condition is gated on both non-Windows OSes as well as the specific target framework, you can add more of these as needed for each .NET Framework target your project includes.
Once we have the package downloaded, we need to inform the C# compiler where the framework
reference assemblies live. We also need to let NuGet know where the NuGet package lives, since
it's currently only available on MyGet. For our sample, this means adding a new
<PropertyGroup> element with these two values:
Once again, you will replace your package name and version number as needed. Note that the NuGet package cache always stores package names in lowercase, and since non-Windows OS file systems can be case-sensitive, you too must use fully lowercased names.
Normally there is a batch of system libraries that automatically get referenced when building .NET Framework applications. You may find as you build on non-Windows OSes that you're missing some of these references that might otherwise have been included automatically on Windows. In our example, we were missing one reference:
$ dotnet build Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core Copyright (C) Microsoft Corporation. All rights reserved. Restore completed in 30.82 ms for ~/src/MyFirstUnitTests/MyFirstUnitTests.csproj. MyFirstUnitTests -> ~/src/MyFirstUnitTests/bin/Debug/netcoreapp2.1/MyFirstUnitTests.dll Class1.cs(7,10): error CS0012: The type 'Attribute' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=18.104.22.168, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. [~/src/MyFirstUnitTests/MyFirstUnitTests.csproj]
You can add them as
<Reference> elements in the
you created when adding your NuGet packages, since it already contains the conditional statement. To fix our
error above, we needed to add one reference:
Note that non-Windows OSes, file systems are often case sensitive, so when adding references, make sure the
names exactly match the casing of the files on your file system (without the
You can find those files in your NuGet cache folder. For our example, we found the files here:
$ ls ~/.nuget/packages/microsoft.targetingpack.netframework.v4.5.2/1.0.1/lib/net452 Accessibility.dll System.Net.NetworkInformation.dll CustomMarshalers.dll System.Net.Primitives.dll ISymWrapper.dll System.Net.Requests.dll ... System.Net.dll WindowsBase.dll System.Net.Http.dll WindowsFormsIntegration.dll System.Net.Http.WebRequest.dll XamlBuildTask.dll
One file that constantly causes trouble is System.Xml. The automatically generated reference is
System.Xml, but the file on disk is named
System.XML.dll. When you add your
<Reference>, make sure you use the properly cased name. This is one reason why verifying
the filename on disk is critical when adding any references. It's also valuable to make sure you've tried
your build on a case-sensitive file system (for example, using the default ext4 on Linux).
If all of these steps have been performed successfully, then your normal command line tools should all
dotnet build, and
Give them a try! Here are a few tips:
/) and line ending differences (CRLF on Windows vs. LF on Linux and macOS). Make sure to use the
Pathclass when creating and manipulating file paths, and use
Environment.NewLineto know which line endings are in use. You can also pass a flag to
Assert.Equalwhen comparing strings to tell it to ignore line ending differences (
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);).
As previously mentioned, when running on non-Windows OSes,
dotnet test knows how to launch
your .NET Framework tests with Mono. If all goes to plan, when you run your tests, you should be able to
successfully run your .NET Framework tests. You may even see signs of Mono in your stack traces:
Assert.Equal() Failure Expected: 5 Actual: 4 Stack Trace: at MyFirstUnitTests.Class1.FailingTest () [0x0000a] in <ea38f081094e407290795149a3e20d66>:0 at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object,System.Exception&) at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object parameters, System.Globalization.CultureInfo culture) [0x0003b] in <7b0d87324cab49bf96eac679025e77d1>:0