Targeting Multiple Frameworks in Visual Studio 2017

Share on facebook
Share on twitter
Share on linkedin

In the early days of .NET you had limited options for what your applications targeted. If you were writing .NET applications then you were writing for Windows. As the years passed .NET grew more and more and now with Xamarin, Mono, and .NET Core that is no longer the case. We are now using .NET to write applications and libraries that run on Windows, iOS, Android, Linux, and even Macs. With the addition of the .NET Standard you can create libraries that will be used on any of these platforms. As a developer, I often want to take advantage of the latest and greatest features, but I realize that by doing that it limits the consumers of my code. In this article I will show you how you can use Visual Studio 2017 to create a package that targets multiple frameworks so you can take advantage of the most recent frameworks while also supporting older ones as well.

The first step to creating a package that targets multiple frameworks is to create a new project using Visual Studio 2017. In the New Project dialog you will want to choose “.NET Standard” in the sidebar and create a “Class Library (.NET Standard)”. If you can’t find it in the options you can use the search bar as well.

Creating the project will add a new class library targeting the .NET standard. The version it targets might depend on what you have installed on your machine, but in my instance it is targeting netstandard1.4. You can view which framework is being targeted by right clicking the project in Solution Explorer and choosing “Edit .csproj” where is whatever you named the project. You should see something similar to this:

<Project Sdk="Microsoft.NET.Sdk">    
    <PropertyGroup>
        <TargetFramework>netstandard1.4</TargetFramework>
    </PropertyGroup>
</Project>

In order to target multiple frameworks, we are going to change the <TargetFramework> tag to <TargetFrameworks> instead. Once we do that we can add whatever frameworks we wish to target inside the tag separated by a semicolon:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFrameworks>netstandard1.4;net20</TargetFrameworks>
    </PropertyGroup>
</Project>

In this example I am choosing the original .NET Standard 1.4 target and adding .NET Framework 2.0. The monikers for those are nestandard1.4 and net20 respectively. Once you save the project you might get prompted to reload the project. Choose the option to “Reload All”. If you were to build the project right now you would be producing two assemblies:

bin\Debug\netstandard1.4\Example.dll
bin\Debug\net20\Example.dll

Let’s add a bit of code that shows how you might take advantage of a newer feature using our netstandard1.4 target but fall back on different code for the net20 target.

// class Utilities.cs
#if NETSTANDARD1_4
using System.Threading.Tasks;
#endif

namespace Example
{
    #if NETSTANDARD1_4
    public Task UseTaskRun()
    {
        return Task.Run(() =>
        {
            // very complicated long running code goes here
        });
    }
    #elif NET20
    public void DontUseTaskRun()
    {
        // revert to old school code here
    }
    #endif
}

When the compiler encounters the #if NETSTANDARD1_4 / NET20 directives it will only compile that code if those conditional compilation symbols are defined (these ones are defined automatically for you). If you were to inspect the assemblies that are generated using a decompilation tool you would see different methods for each. In order to create a NuGet package all you need to do is right click your project and choose the “Pack” option. It will place your .nupkg file in the bin/Debug or bin/Release folder depending on which configuration you have selected. If you were to inspect the contents of the package with the tool you would see a very similar structure to physical folder structure. Both assemblies will be included in the package. If you were to publish the package to a NuGet server and install the package in a .NET 2.0 project then it would use the assembly that was targeting .NET Framework 2.0.

One last thing I would like to show is the ability to include different third party libraries based on the framework. Since you might be writing code for .NET 2.0 like in my example, you might encounter a library that does not support your framework. You need to be able to only include that library when targeting a more recent framework. In order to do that you need to edit your project file. Here is an example of what you might see when doing this:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFrameworks>netstandard1.4;net20</TargetFrameworks>
    </PropertyGroup>

    <ItemGroup Condition="'$(TargetFramework)' == 'net20'">
        <Reference Include="System.Data.Entity" />
    </ItemGroup>

    <ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.4'">
        <PackageReference Include="StackExchange.Redis" Version="1.2.1" />
    </ItemGroup>
</Project>

The above example shows how you might include the StackExchange.Redis NuGet package (which does not have support for .NET 2.0) in your netstandard1.4 library but include a traditional assembly reference to System.Data.Entity in your .NET 2.0 version. You would still need to write code using the conditional directives anywhere you use these libraries or else you would receive compilation errors.

In this article I showed how you a few tricks for how you might target multiple different frameworks in a single project. We looked at a simple example of how to write code using conditional compilation based on the framework and how you might include different third party libraries based on the framework. I hope you found this tutorial helpful.

More to explore