Building .NET Standard Libraries with Visual Studio 2017

Thanks to the Visual Studio 2017, creating .NET Standard Class Libraries is easier than ever. While it is easy, there are few tips and tricks to help make the process as smooth as possible.

This is the first in a series of posts to generate a class library for a .NET Standard Class Library. This post will walk through:
1. How to create a .NET Standard class library
2. Show how to target multiple frameworks
3. Add references to NuGet packages
4. Add references to .NET Framework Libraries
5. A note on preprocessor directives
6. Add a reference to .NET Standard class library to a .NET 4.6.2 project

Ready? Let's get started!

Creating .NET Standard Library Project

In Visual Studio 2017 go to File -> New -> Project

When the New Project modal appears select Visual C# in the left menu. Then select Class Library (.NET Standard) and enter a location for the project.

After Visual Studio finishes the initial setup the project will load and the Class1 file will appear. Do not be surprised if at first, the file says it cannot compile and the code appears as red for a brief moment. Visual Studio is doing some work behind the scenes. Wait a minute or two and it goes away.

A new menu option has been added to Visual Studio 2017 for .NET Standard Class Libraries. It is now possible to right click on the project and select Edit [Project Name]

This is because .NET Standard Class Libraries follow the new .NET Core csproj format.. Looking at the initial project file created for the sample project it clearly shows it is a lot cleaner than the csproj files we were used to dealing with in the past.

Compare that with an older csproj file and it is not hard to see the difference.

Select Frameworks

Since we are in the project file, now is a good time to talk about targeting multiple frameworks in a single project file. As you can see, the project is targeting .NET Standard 1.4.

Reading the documentation (No!!!! Not reading!) it shows right at the top all it takes is a simple addition of an "s" to TargetFramework (to make it TargetFrameworks) enables the project to target multiple frameworks.

Saving the project will cause a warning message to appear. I recommend clicking on Reload All. In some cases, third-party tools such as Resharper tend to have a hard time with this change. Restarting Visual Studio tends to resolve the issue. Not the greatest experience, but this only needs to be done once.

The question really becomes, which frameworks should be targeted. To make life easier, the recommendation is to target two frameworks. A .NET Standard Framework and the corresponding .NET Framework. The table below provides a somewhat handy reference.

Basically, it works out like this. The idea being if the project can work in .NET Standard 1.1 then it sure as heck can work in .NET 4.5. It doesn't work the other way around, writing a library in .NET 4.5 does not mean it will work with .NET Standard 1.1.

  • netstandard1.1;net45
  • netstandard1.2;net451
  • netstandard1.3;net46
  • netstandard1.4;net461
  • netstandard1.5;net462

Important Note: the class library can just target net45 or net462. If the library targets only .NET Framework it is treated very similarly to a .NET Framework Class Library. The main difference being the class library will have the new csproj format.

Which one to target then? A couple of deciding factors.
1. What NuGet packages are you referencing and what .NET Standard version are they targeting?
2. What features are needed? When was that feature introduced into .NET Standard?
3. What is the minimum framework supported in the enterprise?

Why target multiple frameworks? It is a matter of what will be brought in. This library is going to be a small wrapper around Dapper. If the library just targeted .NET Standard 1.4 and I wanted to use library as a NuGet package in a console application written in .NET 4.6.2 then that console application would have to reference the .NETStandard.Library Nuget package. That, in turn, brings in what feels like half the world of NuGet references.
Having a .NET framework version helps keep that down. Because targeting multiple frameworks will generate two sets of .dlls, one for each target.

When this library is packaged into a NuGet package both instances will be included. Visual Studio and MSBuild will automatically reference the correct one depending on the application's .NET version. If the libary and the project are in the same solution, and the project is using .NET 4.6.1 (or higher) Visual Studio and MSBuild will automatically pick .NET 4.6.1. Pretty slick!

Reference NuGet Packages

For this purposes of this demo, the project is going to be a small wrapper around Dapper. Dapper is a great micro-ORM, but I want to wrap interfaces with simpler methods to make it easier to test. And to abstract away the ORM technology. If I were to ever go away from Dapper I would only have to change the concrete instance of the class, not the interface. All the code using the interface wouldn't care. Good ol' SOLID programming principles.

Anyway, open up the NuGet package manager and enter in Dapper. Notice the right side of the screen. This tells us that Dapper can be used in both .NET Framework and .NET Standard libraries. It also shows us the minimum version for this class libary must be .NET Standard 1.3 The library can then target any .NET Standard version greater than or equal to .NET Standard 1.3.

Another interesting note is there is no more packages.config. That has now been rolled into the new csproj file format.

Add References

It is possible to add normal .NET Framework references just like regular, old, .NET Framework Class Libraries. There is a small quirk to it. Right now the TargetFrameworks for this library is netstandard1.4;net461. Add a reference just like before.

The modal window which appears shows no references can be added because the project is currently targeting .NET Standard 1.4.

To resolve this issue change the target framework order to net461;netstandard1.4

Now the list of available .NET Framework assemblies are populated.

In a perfect world, all required references would be added via NuGet packages. But we all know the differences between a perfect world and real life when it comes to software development.

In some cases, the .dll referenced will only work with .NET Framework and not .NET Standard. These should be few and far between, but in the rare case when that does happen you will need to make use of preprocessor directives.

Preprocessor Directives

Preprocessor directives can be very powerful. Used correctly they allow code which works for different platforms to be on the same code base. Used incorrectly and the result is a mess of spaghetti code. I try to avoid them at all costs because results can be unexpected without proper testing. But they do pop up from time to time and can be helpful when developing a library designed to work with as many versions of .NET as possible. Most recently I saw them added to a library where it needed low-level OS access specifically for Windows. The preprocessor directive was #if NET461. The developer who wrote that section knew .NET 4.6.1 would only work on Windows.

Below is an example of preprocessor directive usage.

using System;

namespace DemoProject  
{
    public class DemoClass
    {
        public string GetResult(int someValue)
        {
#if NET461
            return ".NET 4.6.1";
#else
            return ".NET Standard 1.4";
#endif

        }
    }
}

The interesting thing is both lines are compiled into both .dlls. Opening the .NET 4.6.1 .dll using Jetbrain's Dotpeek shows this:

Adding a reference to .NET Standard Library to a .NET 4.6.2 Project

In the demo solution I added a console application targeting .NET 4.6.2. Adding a reference to this demo library is the same as any other class library reference. Right click on the project, select add reference.

The console is not going to do a whole lot, just write the result of the class (still with the preprocessor directive).

And the result shows that the pre-processor directive chose .NET 4.6.1.

But what if the project was not targeting .NET 4.6.1? Well, time to test that out. A couple of items to note. Change the target frameworks in the demo project back to just .NET Standard 1.4.

After a clean and rebuild the result has been changed to .NET Standard 1.4.

Reverting it back caused .NET 4.6.1 to appear again.

Note: Again, this is why it is recommended to avoid using preprocessor directives whenever possible. The results are too unexpected. Keeping the target frameworks paired (.NET Standard 1.4 and .NET Framework 4.6.1 for example) help keep the need for preprocessor directives down.

The main takeaway is adding a .NET Standard class library in a solution is the same process as adding a regular .NET Framework class library.

Conclusion

Writing .NET Standard class libraries are very similar to writing .NET Framework class libraries. Especially when using Visual Studio 2017 (or later, this was written in 2017). When it is all said and done, it is still C# (or VB.NET if that is your jam). The major headache is determining which .NET standard to target when the intention of the library is to package it up in a NuGet package for other applications to use. But I suppose that is the same for any .NET Framework class libraries designed to be packaged into NuGet packages.

The next part of this series will focus on wrapping the demo library into a NuGet package and having the CI process automatically build it.

Author image
About Bob Walker
Omaha, NE
Founder of CodeAperture.io. Principal Software Architect in Omaha, Nebraska. Friend of Redgate. Working as a Full Stack Developer since 2004.