Which .NET Standard Version To Target

When I migrated Dandago.Finance to .NET Core yesterday, there was something I overlooked. I realised this when I tried to install the resulting package, targeting .NET Standard 1.6, in a new project. It worked fine in a .NET Core console application, but not in one targeting the full .NET Framework:

In fact, even referencing Dandago.Finance directly results in weird stuff going on:

The problem is immediately evident if we take a look at the compatibility grid for .NET Standard, a relevant excerpt of which at the time of writing this article is the following:

Targeting each version of .NET Standard means supporting the corresponding versions of .NET Core and .NET Framework upwards. For instance, if we target .NET Standard 1.4, then we support .NET Framework 4.6.1 and up, and .NET Core 1.0 and up.

But since Dandago.Finance was built to target .NET Standard 1.6, then .NET Framework 4.6.2 and earlier could not use it (since the first version it supports is “vNext”, whatever that means in this context).

So in practice, in order to maximise a library’s compatibility, you will want to target the lowest possible version of .NET Standard. You can do this by changing the target framework from the project settings:

In the case of Dandago.Finance, .NET Standard 1.1 provided insufficient API coverage to make it work:

Targeting .NET Standard 1.2 made Dandago.Finance compile just fine, and I verified that the resulting package installs fine for console applications targeting .NET Framework 4.5.1 and up (as per compatibility chart), and .NET Core 1.0 and up.

However, this means we have had to sacrifice support for .NET Framework 4.5. This is no big deal since .NET Framework versions 4, 4.5 and 4.5.1 have been dead for over a year now. So technically we could have targeted .NET Standard 1.3 (.NET Framework 4.6 and upwards), but it’s good to give extra backwards compatibility for legacy code where we can.

Migrating Dandago.Finance to .NET Core

Microsoft has recently been heavily investing in .NET Core, which you can think of as the next generation of the .NET Framework. There are various benefits to .NET Core, the biggest one being that it is cross-platform; thus compliant code can run on Windows, Linux and Mac (and probably others in future).

In this article, we’re going to take one of my smaller projects – Dandago.Finance – and port it to .NET Core. Dandago.Finance is ideal to demonstrate a first migration because it is very small, consisting of a main project (3 classes) and a unit test project (2 classes) – both class libraries.

Before we start, make sure you are using the latest tools (such as the recently released Visual Studio 2017). .NET Core tools have undergone a lot of radical changes (e.g. project.json is dead) so you don’t want to be learning based on something that’s already obsolete. If you’re using VS2017, make sure you have the .NET Core cross-platform development workload installed.

Migrating the main library

We’re going to start a fresh new class library targeting .NET Core and move our code there. Actually, that statement is not entirely correct: if you open Visual Studio 2017, you’ll see that there are at least 3 different kinds of class library you can create (or more depending on additional tooling you may have installed):

  • Class Library (.NET Framework)
  • Class Library (.NET Core)
  • Class Library (.NET Standard)

This is very confusing and I’ve asked a question about this on Stack Overflow yesterday that attracted some pretty detailed answers. In short, if you want your class libraries to be as portable as possible, you need to target .NET Standard. .NET Standard is a specification detailing APIs that need to be available in compatible frameworks. .NET Core, and certain versions of the full .NET Framework, implement .NET Standard. However, they each also incorporate a lot of other runtime-related stuff, so targeting .NET Core specifically means you can’t use your code under the full .NET Framework.

So let’s create a project of type Class Library (.NET Standard). As always, this will create a solution with the same name as the project.

Next, we’ll delete the automatically created Class1 class, and copy the class files from the old Dandago.Finance library to the new project folder. You’ll notice that Visual Studio automatically notices the new files and includes them in the project, without you needing to explicitly add them:

 

Migrating the test project

Let’s add a new class library for the unit tests, but this time it needs to be a Class Library (.NET Core). If you get this wrong and choose Class Library (.NET Standard) instead, Visual Studio won’t find your tests and the dotnet test command will refuse to run it (as per this Stack Overflow question). The reason why .NET Standard won’t work for unit tests is detailed in the corresponding answer: in short, we need to specify a target framework that will be responsible for running the tests; .NET Standard on its own is not enough.

Next, we need to add a reference to the Dandago.Finance project.

Now, we can repeat the procedure we did for the main library, and delete Class1.cs and copy over the test classes.

However, this isn’t going to be as smooth as with the main library. The original test project uses NUnit, and at the time of writing, that isn’t fully supported by .NET Core. Fortunately, however, it’s easy to change to xUnit, which does already boast .NET Core support.

First, we need to install the following packages:

Install-Package Microsoft.NET.Test.Sdk
Install-Package xunit
Install-Package xunit.runner.visualstudio

Then, we need to make the following substitutions:

  1. using NUnit.Framework; becomes using Xunit;
  2. [TestFixture] goes away
  3. [Test] becomes [Fact]
  4. Assert.IsTrue(...) becomes Assert.True(...)
  5. Assert.IsFalse(...) becomes Assert.False(...)

The solution should now build, and the unit tests should run successfully:

Summary

Migrating Dandago.Finance to .NET Core has taught us a few things:

  1. Visual Studio can automatically detect new files for .NET Core / .NET Standard projects.
  2. Portable class libraries should target .NET Standard.
  3. Unit test projects should target .NET Core.
  4. Use xUnit for .NET Core unit tests.