Multi-Targeting .NET Standard Class Libraries

The .NET family has grown quite a bit, and to be honest, it’s a bit of a confusing mess. As I already explained in “Migrating Dandago.Finance to .NET Core“, there are now several different types of class library you can choose from (including different kinds of portable class libraries), different ideas of cross-platform (.NET Core vs Universal Windows Platform), different frameworks, and .NET Standard.

Let’s consider the following image, which shows three different .NET-based frameworks in relation to .NET Standard:

Image credit: .NET Core, .NET Framework, Xamarin – The “WHAT and WHEN to use it”

Your applications will typically be built for one of the frameworks on top (e.g. .NET Framework). But when you create a class library, you can choose to have it target a specific framework, or .NET Standard. Just as a quick recap from “Migrating Dandago.Finance to .NET Core“, targeting a particular framework (e.g. .NET Core) will not let you use the class library on others (e.g. .NET Framework), but targeting .NET Standard keeps it compatible with all of them.

This means that class libraries that target .NET Standard have maximum compatibility, but there’s a cost: not all APIs are available for .NET Standard, and targeting .NET Standard is only compatible with certain recent versions of those frameworks. If you take a look at the compatibility chart, you’ll notice for instance that .NET Standard 1.3  supports .NET Framework 4.6 and onwards.

For the most part, this is okay. But sometimes, you may want to combine .NET Standard compatibility with specific features in a particular framework, or perhaps target .NET Standard but still support older versions of a framework because you have some legacy code you can’t upgrade yet.

Multi-Targeting

When you create a new .NET Standard Class Library, its .csproj file will look something like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
  </PropertyGroup>

</Project>

It is now actually possible to target multiple frameworks, by changing the <TargetFramework> element to <TargetFrameworks> (just add an ‘s’) and putting in different target framework monikers separated by semicolons. For instance, the .csproj file for my .NET Settings Framework looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard1.2;net452</TargetFrameworks>
    <!-- ... -->
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net452'">
    <Reference Include="System.Configuration" />
  </ItemGroup>

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

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="1.1.1" />
  </ItemGroup>

</Project>

In this project, I’m targeting .NET Standard to provide as much compatibility as possible, but I’m also using framework-specific assets from the .NET Framework (namely System.Configuration, which is used to work with App.config/Web.config files). Thus, I am targeting both .NET Standard 1.2+ and .NET 4.5.2+. This has the effect of creating separate builds for each framework.

I have dependencies in there that are applicable for both targets (the section at the end with no conditions), but I also have specific dependencies needed for each framework. For instance, the functionality that needs System.Configuration will only be available for the build that targets the full .NET Framework, and will not be usable in the .NET Standard build. Such functionality will have to be written within preprocessor directives to prevent them from breaking the other builds:

#if NET452

using System.Configuration;

// ...

#endif

Unfortunately, as of now, you have to edit the .csproj by hand if you want to do multi-targeting, because the Visual Studio tooling hasn’t quite caught up with it yet. In fact, the project settings will only allow you to target a specific version of .NET Standard from a dropdown:

And after you’ve edited the .csproj by hand, it won’t let you change it from the GUI:

Summary

  • Targeting .NET Core lets you go cross platform, but you can’t use that functionality with other frameworks.
  • Targeting .NET Standard is compatible with any framework that supports that version of .NET Standard.
  • You can multi-target a .NET Standard library to include framework-specific functionality (guarded with preprocessor directives) while keeping the rest of the library compatible with all the relevant frameworks.