September 2017

Volume 32 Number 9

[.NET Standard]

Demystifying .NET Core and .NET Standard

By Immo Landwerth | September 2017

As the newest members of the .NET family, there’s much confusion about .NET Core and .NET Standard and how they differ from the .NET Framework. In this article, I’ll explain exactly what each of these are and look at when you should choose each one.

Before going into detail, it’s helpful to look at the larger picture of .NET to see where .NET Core and .NET Standard fit in. When.NET Framework first shipped 15 years ago, it had a single .NET stack that you could use for building Windows desktop and Web applications. Since then, other .NET implementations have emerged, such as Xamarin, which you can use for building mobile apps for iOS and Android, as well as macOS desktop applications, as shown in Figure 1.

The .NET Landscape

Figure 1 The .NET Landscape

Here’s how .NET Core and .NET Standard fit into this:

  • .NET Core: This is the latest .NET implementation. It’s open source and available for multiple OSes. With .NET Core, you can build cross-platform console apps and ASP.NET Core Web applications and cloud services.
  • .NET Standard: This is the set of fundamental APIs (commonly referred to as base class library or BCL) that all .NET implementations must implement. By targeting .NET Standard, you can build libraries that you can share across all your .NET apps, no matter on which .NET implementation or OS they run.

Introduction to .NET Core

.NET Core is a new cross-platform and fully open source .NET implementation that was forked from .NET Framework and Silverlight. It’s optimized for mobile and server workloads by enabling self-contained XCOPY deployments.

To get a better feel for .NET Core, let’s take a closer look at what developing for .NET Core looks like. And let’s do this while exploring the new command line-based tooling. You can also use Visual Studio 2017 for your .NET Core development, but because you’re reading this article, chances are you’re quite familiar with Visual Studio already, so I’ll focus on the new experience.

When .NET was created, it was heavily optimized for rapid appli­cation development on Windows. In practice, this means that .NET development and Visual Studio were inseparable friends. And sure thing: Using Visual Studio for development is a blast. It’s super productive and the debugger is hands down the best I’ve ever used.

However, there are cases where using Visual Studio isn’t the most convenient option. Let’s say you want to just play with .NET to learn C#: You shouldn’t have to download and install a multi-gigabyte IDE. Or, say you’re accessing a Linux machine over SSH where using an IDE is simply not an option. Or, say you’re simply someone who prefers using a command-line interface (CLI).

That’s why a first-class CLI was created, called .NET Core CLI. The .NET Core CLI’s main driver is called “dotnet.” You can use it for virtually all aspects of your development, including creating, building, testing and packaging projects. Let’s see what this looks like.

Start by creating and running a Hello World console app (I use PowerShell on Windows, but this will work equally well with Bash on macOS or Linux):

$ dotnet new console -o hello
$ cd hello
$ dotnet run
Hello World!

The “dotnet new” command is the CLI equivalent of File | New Project in Visual Studio. You can create a variety of different project types. Type “dotnet new” to see the different templates that come pre-installed.

Now, let’s extract some of the logic into a class library. To do this, first create a class library project that’s parallel to your hello project:

$ cd ..
$ dotnet new library -o logic
$ cd logic

The logic you want to encapsulate is the construction of a Hello World message, so change the contents of Class1.cs to the following code:

namespace logic
{
  public static class HelloWorld
  {
      public static string GetMessage(string name) => $"Hello {name}!";
  }
}

At this point, you should also rename Class1.cs to HelloWorld.cs:

$ mv Class1.cs HelloWorld.cs

Note that you don’t have to update the project file for this change. The new project files used in .NET Core simply include all source files from the project’s directory. Thus, adding, removing and renaming files doesn’t require modifying the project anymore. This makes file operations smoother from the command line.

To use the HelloWorld class, you need to update the hello app to reference the logic library. You can do this by editing the project file or by using the dotnet add reference command:

$ cd ../hello
$ dotnet add reference ../logic/logic.csproj

Now, update the Program.cs file to use the HelloWorld class, as shown in Figure 2.

Figure 2 Updating the Program.cs File to Use HelloWorld Class

using System;
using logic;
namespace hello
{
class Program
{
static void Main(string[] args)
{
Console.Write("What's your name: ");
var name = Console.ReadLine();
var message = HelloWorld.GetMessage(name);
Console.WriteLine(message);
}
}
}

To build and run your app, just type dotnet run:

$ dotnet run
What's your name: Immo
Hello Immo!

You can also create tests from the command line. The CLI supports MSTest, as well as the popular xUnit framework. Let’s use xUnit in this example:

$ cd ..
$ dotnet new xunit -o tests
$ cd tests
$ dotnet add reference ../logic/logic.csproj

Change the UnitTest1.cs contents, as shown in Figure 3, to add a test.

Figure 3 Changing the UnitTest1.cs Contents to Add a Test

using System;
using Xunit;
using logic;
namespace tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
var expectedMessage = "Hello Immo!";
var actualMessage = HelloWorld.GetMessage("Immo");
Assert.Equal(expectedMessage, actualMessage);
}
}
}

Now you can run the tests by invoking dotnet test:

$ dotnet test
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.

To make things a bit more interesting, let’s create a simple ASP.NET Core Web site:

$ cd ..
$ dotnet new web -o web
$ cd web
$ dotnet add reference ../logic/logic.csproj

Edit the Startup.cs file and change the invocation of app.Run to use the HelloWorld class as follows:

app.Run(async (context) =>
{
  var name = Environment.UserName;
  var message = logic.HelloWorld.GetMessage(name);
  await context.Response.WriteAsync(message);
});

To start the development Web server, just use dotnet run again:

$ dotnet run
Hosting environment: Production
Now listening on: https://localhost:5000
Application started. Press Ctrl+C to shut down.

Browse to the displayed URL, which should be https://localhost:5000.

At this point, your project structure should look like Figure 4.

Figure 4 The Project Structure You Created

$ tree /f
│
├───hello
│ hello.csproj
│ Program.cs
│
├───logic
│ HelloWorld.cs
│ logic.csproj
│
├───tests
│ tests.csproj
│ UnitTest1.cs
│
└───web
Program.cs
Startup.cs
web.csproj

To make it easier to edit the files using Visual Studio, let’s also create a solution file and add all the projects to the solution:

$ cd ..
$ dotnet new sln -n HelloWorld
$ ls -fi *.csproj -rec | % { dotnet sln add $_.FullName }

As you can see, the .NET Core CLI is powerful and results in a lean experience that developers from other backgrounds should find quite familiar. And while you used dotnet with PowerShell on Windows, the experience would look quite similar if you were on Linux or macOS.

Another huge benefit of .NET Core is that it supports self-­contained deployments. You could containerize your application using Docker in such a way that it has its own copy of the .NET Core runtime. This lets you run different applications on the same machine using different .NET Core versions without them interfering with each other. Because .NET Core is open source, you can also include nightly builds or even versions you’ve modified or built yourself, potentially including modifications you made. However, that’s beyond the scope of this article.

Introduction to .NET Standard

When you’re building modern experiences, your app often spans multiple form factors and, therefore, multiple .NET implementations. In this day and age, customers pretty much expect that they can use your Web app from their mobile phone and that data can be shared via a cloud-based back end. When using a laptop, they also want to get access via a Web site. And for your own infrastructure, you likely want to use command-line tools and potentially even desktop apps for letting your staff manage the system. See Figure 5 for how the different .NET implementations play into this.

Figure 5 Descriptions of .NET Implementations

  OS Open Source Purpose
.NET Framework Windows No Used for building Windows desktop applications and ASP.NET Web apps running on IIS.
.NET Core Windows, Linux, macOS Yes Used for building cross-platform console apps and ASP.NET Core Web apps and cloud services.
Xamarin iOS, Android, macOS Yes Used for building mobile applications for iOS and Android, as well as desktop apps for macOS.
.NET Standard N/A Yes Used for building libraries that can be referenced from all .NET implementations, such as .NET Framework, .NET Core and Xamarin.

In such an environment, code sharing becomes a major challenge. You need to understand where APIs are available and make sure that shared components only use APIs that are available across all .NET implementations you’re using.

And that’s where .NET Standard comes in. .NET Standard is a specification. Each .NET Standard version defines the set of APIs that all .NET implementations must provide to conform to that version. You can think of it as yet-another .NET stack, except that you can’t build apps for it, only libraries. It’s the .NET implementation you should use for libraries that you want to reference from everywhere.

You’re probably wondering which APIs .NET Standard covers. If you’re familiar with .NET Framework, then you should be famil­iar with the BCL, which I mentioned earlier. The BCL is the set of fundamental APIs that are independent of UI frameworks and application models. It includes the primitive types, file I/O, networking, reflection, serialization, XML and more.

All .NET stacks implement some version of .NET Standard. The rule of thumb is that when a new version of a .NET implemen­tation is produced, it will usually implement the latest available version of the .NET Standard.

A good analogy is HTML and browsers: Think of the HTML specification as the .NET Standard and the different browsers as the .NET implementations, such as .NET Framework, .NET Core and Xamarin.

At this point, you’re probably curious how you can use .NET Standard. In fact, you already have. Remember when we created the logic class library earlier? Let’s take a closer look at the project file:

$ cd logic
$ cat logic.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

Let’s contrast this with the “hello” console application project file:

$ cd ..\hello
$ cat hello.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\logic\logic.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

</Project>

As you can see, the logic library has a value for TargetFramework of netstandard2.0, while the console app has a value of netcoreapp2.0. The TargetFramework property indicates which .NET implementation you’re targeting. So, the console app is targeting .NET Core 2.0, while the library is targeting .NET Standard 2.0. That means you can reference the logic library not only from a .NET Core app, but also from an app built for .NET Framework or Xamarin.

Unfortunately, most of the libraries available today aren’t targeting .NET Standard, yet. Most of them are targeting the .NET Framework. Of course, not all libraries can (or even should) target .NET Standard. For instance, a library containing Windows Presentation Foundation (WPF) controls needs to target .NET Framework because UI isn’t part of the standard. However, many of the general-purpose libraries only target .NET Framework because they were created when .NET Standard simply didn’t exist yet.

With .NET Standard 2.0, the API set is large enough so that most, if not all, of the general-purpose libraries can target .NET Standard. As a result, 70 percent of all the libraries that exist on NuGet today only use APIs that are now part of .NET Standard. Still, only a fraction of them are explicitly marked as being compatible with .NET Standard.

To unblock developers from using them, a compatibility mode has been added. If you install a NuGet package that doesn’t offer a library for your target framework, nor provides one for .NET Standard, NuGet will try to fall back to .NET Framework. In other words, you can reference .NET Framework libraries as if they were targeting .NET Standard.

I’ll show you what this looks like. In my example, I’ll use a popular collection library called PowerCollections, which was written in 2007. It wasn’t updated in a while and still targets .NET Framework 2.0. I’ll install this from NuGet into the hello app:

$ dotnet add package Huitian.PowerCollections

This library provides additional collection types that the BCL doesn’t provide, such as a bag, which makes no ordering guarantees. Let’s change the hello app to make use of it, as shown in Figure 6.

Figure 6 Sample Application Using PowerCollections

using System;
using Wintellect.PowerCollections;
namespace hello
{
class Program
{
static void Main(string[] args)
{
var data = new Bag<int>() { 1, 2, 3 };
foreach (var element in data)
Console.WriteLine(element);
}
}
}

If you run the program, you’ll see the following:

$ dotnet run
hello.csproj : warning NU1701: Package 'Huitian.PowerCollections 1.0.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v2.0'. This may cause compatibility problems.
1
3
2

So, what just happened? The hello app is targeting .NET Core 2.0. Because .NET Core 2.0 implements .NET Standard 2.0, it also has the compatibility mode for referencing .NET Framework libraries. However, not all .NET Framework libraries will work on all .NET implementations. For example, they might use Windows Forms or WPF APIs. NuGet has no way of knowing that, so it gives you a warning message so you’re aware of this situation and don’t waste your time troubleshooting issues that might result from this.

Note that you’ll get this warning each time you build. This avoids the problem where you simply didn’t see the warning during package installation, or forgot about it.

Of course, there’s nothing worse than unactionable warnings that you need to overlook every time you build. So, the idea here is that after you validate your app, you can then disable the warning for that package. Because the app is running fine (it correctly printed the contents of the bag you created) you can now suppress the warning. To do that, edit the hello.csproj file and add the NoWarn attribute to the package reference:

<PackageReference Include="Huitian.PowerCollections" Version="1.0.0" 
  NoWarn="NU1701" />

If you now run the app again, the warning should be gone. Should you install another package that uses the compatibility mode, you’ll get the warning for that package for which you can suppress, as well.

The new tooling also lets class library projects produce NuGet packages as part of the build. This makes it much simpler to share your libraries with the world (by pushing to nuget.org) or just within your organization (by pushing to your own package feed on Visual Studio Team Services or MyGet). The new projects also support multi-targeting, which lets you build a single project for multiple .NET implementations. This means you can use conditional compilation (#if) to adapt the library to specific .NET implementations. It also lets you build .NET Standard wrappers for platform-specific APIs. However, all of that is beyond the scope of this article.

Wrapping Up

.NET Standard is a specification of APIs that all .NET implementations must provide. It brings consistency to the .NET family and enables you to build libraries you can use from any .NET implementation. It replaces PCLs for building shared components.

.NET Core is an implementation of the .NET Standard that’s optimized for building console applications, Web apps and cloud services using ASP.NET Core. Its SDK comes with a powerful tooling that in addition to Visual Studio development supports a full command line-based development workflow. You can learn more about them at aka.ms/netstandardfaq and aka.ms/netcore.


Immo Landwerth is a program manager at Microsoft, working on .NET. He focuses on .NET Standard, the BCL and API design.


Discuss this article in the MSDN Magazine forum