.Net Standard and .NET Core accept been on my heed for a long time now - years really, merely the reality is while I've been using the technology quite a bit, I've not jumped in with both feet. In fact, to date I accept still to build anything 'real' for customers beyond a few internal infrastructure projects and quite a few sample applications.

For me personally, .NET Standard ii.0 and .NET Core 2.0 with their much bigger base library foot impress and the real possibility of porting the bulk of existing library code over to .NET Core really has been a deciding factor for me to offset moving some of my existing full framework libraries that I've been using for equally long every bit I have been using .Cyberspace to .Internet Core 2.0. Being able to bring some of the tools I utilize to exist productive over to .Internet Core is really pretty important gene to overcoming my reluctance to move into .NET Core. Nobody wants to rewrite lawmaking they already have just to get dorsum to square ane, merely with .NET Core 2.0 information technology really looks like almost lawmaking volition migrate pretty easily.

This isn't just important to me personally, but I call up this is a vital requirement for moving much of the support libraries that exist for .Net into .Internet Core and providing the full featured eco-organization that we've come to look from .Net applications. Currently with .Cyberspace Core i.10 it'south been hit or miss feature wise to feel confident you can actually brand it through a projection without getting stuck with some missing core feature you can't easily notice and have to build from scratch. My feeling is that .Net Core 2.0 will change all that by making it possible for most libraries to be ported with minimal effort.

In this postal service I describe porting an existing full framework library to .NET Core 2.0 and multi-targeting the project to support .Internet 4.5, four.0 and .Cyberspace Standard 2.0. The porting process was fifty-fifty easier than I expected, although the tooling required a fleck of patience to get on with.

What y'all demand to follow forth:

  • Visual Studio 2017 Update 3 Preview 2 or later
  • .Net Core 2.0 SDK Preview

Notation that currently at that place's no back up for .Net Cadre 2.0/.Net Standard ii.0 in the release version of Visual Studio, and that's why the Preview install is required. Y'all tin install the minimal .Net and .NET Core payload for a lightish install, and the install is next with Visual Studio 2017 RTM then both work.

.NET Standard?

A key concept to the porting procedure is .Net Standard 2.0 and how it relates to .NET Cadre 2.0.

For those of you that don't know, .NET Standard is a specification that serves as a blueish print for .NET runtime implementations. The standard specifies what base features the runtime has to implement to support information technology. .Internet Standard describes the base of operations API library - what we used to think of equally the Base Course Library (BCL) in full framework that make upwards the core features of the platform.

.NET Standard is a standard not an implementation and it's up to the runtime to implement the features set forth in the standard. The logistics of this involve some runtime magic where each runtime provides a set of .NET Standard forwarding assemblies that map the .NET Standard APIs to the actual underlying APIs on the specific runtime.

For the purposes of this word, the salient point is that .NET Core 2.0 is an implementation of .Internet Standard 2.0 which ways that if I implement a .Internet Standard 2.0 compliant DLL it will run on .NET Core 2.0. And whatsoever other platform like .NET iv.6.1, Xamarin, Mono, UWP and Unity all of which will somewhen support .Net Standard ii.0. By targeting .NET Standard 2.0 I tin insure that my DLL volition run on any of the target platforms that .Cyberspace Standard supports.

The big win with .Cyberspace Standard is that it provides a common interface to consumers of a library, every bit well as an official guideline to the actual runtime implementers.

For Visual Studio purposes targeting .NET Standard for a class library is also what gives the new SDK projection type that is required to make multi-targeting work.

I don't want to rehash all the details near how .Cyberspace Standard works hither, simply yous can read my earlier blog post .Cyberspace Standard 2.0 - Making Sense of .Net Again for a more than detailed discussion on simply how that works.

The key takeaway for this post is that your .Internet Applications tin at present target .NET Standard 2.0 in your form libraries (or applications) and tin have a very reasonable expectation of interoperability for a number of platforms. In this post I'll talk well-nigh full framework .NET 4.5, four.0 and .Cyberspace Core 2.

Putting it to a Test: Porting a .Cyberspace 4.v/4.0 Library

To really put this all into perspective I decided to move one of my libraries - Westwind.Utilities - to .NET Cadre 2.0 and in the process target .Net Standard 2.0, .NET 4.5 and .Net 4.0 all in a single project. Multi-targeting from a unmarried project is an crawly feature that makes it possible to create a unmarried .NET library projection that can target multiple .Internet Framework targets. Using a single project I can create binaries - and a NuGet parcel if desired - for multiple platforms.

Westwind.Utilities is a actually sometime project that I've been using since the very early years of .NET and it'south interesting in this context because it contains a large hodge-podge of functionality that touches a lot of unlike framework features in a single library. If I built this today I would probably take broken most of the features out into separate projects, simply at that place's a lot of convenience in having these features I utilise in almost every project provided in a single package. Anyway, the point is this is very typical total framework legacy .Net code that was designed with no concept of .NET Core and makes a for a good example of what you're probable to observe when yous beginning porting full framework code to .NET Core two and later.

Creating a new .NET Standard Project

The outset step for moving this library is to create a new .NET Standard Class Library:

Creating a .NET Standard Class library

This creates a new SDK style project using a csproj file. This is the new, more streamlined, MSBUILD based projection format that uses the underlying dotnet command line tooling to build, examination and publish your library. These projects tin too target multiple runtime versions, and when compiled, output multiple versions of your associates for each runtime. You lot tin can as well optionally publish and create a Nuget bundle that includes all runtime versions. The new SDK format lets you configure NuGet attributes directly in the csproj file configuration.

I also ready a test projection at the aforementioned fourth dimension to motility over the existing tests for the old project.

Multi-Target Projects in Visual Studio

When it is all said and washed, here'southward the what the final ported project ends up looking similar in Visual Studio:

A multi-targeted .NET project in Visual Studio

Notice the iii targets for .Internet iv.v, 4.0 and .NET Standard 2.0 all living in the same projection. You tin also see the dependencies that each of the different runtime implementations are pulling in. .NET Core only shows the two packages (Json.internet and SqlClient) I pulled in, while .NET 4.5 shows the specific assembly reference - both explicit assemblies and dependent assemblies (the ones with the lock in Solution Explorer).

The skilful news is that y'all tin now have a unmarried project with multiple targets with one single build footstep. Yay!

The bad news is that there's currently no visual tooling back up for managing multi-target projects in Visual Studio and you have to deal with the .csproj file directly to modify targets or apply special target configuration settings.

To ram that point home, when I get to the project properties for the my class library projection here's what I see:

TargetFramework is missing in Visual Studio

Yup - no runtime target shows considering the UI can't handle multiple frameworks (information technology only looks at <TargetFramework> not <TargetFrameworks>). In social club to manage multiple frameworks you currently have to piece of work directly with the .csproj file.

Luckily that is now a lot easier for a couple of reasons:

  • Implicit File Inclusion
    The new .csproj format no longer explicitly needs to add together every file to the project. Code files are now implicitly considered function of the project and so no longer need to exist explicitly included in the project which drastically reduces the size and complexity of the project file every bit well as reducing the alter churn in the file which is better for source control management. There are still overrides that let you specify custom behaviors for specific files or add files that need to be explicitly included or pushed out equally content into the build folder. But for your base lawmaking files, they are considered included by default unless you tell the project otherwise.

  • Side by Side Editing
    You tin can at present easily edit the .csproj file from Visual Studio while the project is still active. Nigh changes are immediately reflected in Visual Studio although in the current preview that behavior is however a fiddling spotty and some things crave an explicit project/solution reload.

You can now edit CsProj files while the project is open

Editing .csproj for Multi Targeting

In order to target multiple platforms with a single projection you have to brand at least one change in your project, by irresolute the <TargetFramework> element (which is created when you create a new .Cyberspace Standard grade library project) to <TargetFrameworks> and providing a listing of semicolon separated targets:

          <TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>                  

Et voila: I now have project that compiles for three split targets!

You tin can find a list of target frameworks available in the .NET Platform Guide. Here I'chiliad targeting .NET Standard 2.0 for my .Cyberspace Cadre 2.0 applications and standard .Net iv.5 and 4.0 for the total framework libraries. Note that if your library can work entirely with .Internet Standard and doesn't need any additional features, you tin potentially only target a version .NET Standard, only if you're migrating from total framework you're probably better off merely creating split up full framework targets alongside the .NET Standard target.

As shown in the project above Visual Studio automatically breaks out the different runtime dependencies and yous can manage those in Visual Studio, simply they are also referenced in the .csproj file. Information technology's relatively like shooting fish in a barrel to set target specific build and configuration options.

The following shows some of the settings I use for the .NET Standard two.0 and .NET 4.5 targets (omitting the .NET 4.0 ones which are the aforementioned as iv.5 except for the name).

          <!-- common NuGet package refs that affect all projects --> <ItemGroup> 	<PackageReference Include="Newtonsoft.Json" Version="x.0.2" /> </ItemGroup>   <!-- .Internet Standard 2.0 references, compilation flags and build options --> <PropertyGroup Status=" '$(TargetFramework)' == 'netstandard2.0'"> 	<DefineConstants>NETCORE;NETSTANDARD;NETSTANDARD2_0</DefineConstants> </PropertyGroup> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'"> 	<PackageReference Include="Arrangement.Data.SqlClient" Version="iv.4.0-preview1-25305-02" /> </ItemGroup>   <!-- .Cyberspace 4.five references, compilation flags and build options --> <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">		 	<Reference Include="mscorlib" /> 	<Reference Include="System" /> 	<Reference Include="System.Core" /> 	<Reference Include="Microsoft.CSharp" />		 	<Reference Include="System.Information" /> 	<Reference Include="Organisation.Web" /> 	<Reference Include="System.Cartoon" /> 	<Reference Include="System.Security" /> 	<Reference Include="System.Xml" /> 	<Reference Include="System.Configuration" /> </ItemGroup> <PropertyGroup Status=" '$(TargetFramework)' == 'net45'"> 	<DefineConstants>NET45;NETFULL</DefineConstants> </PropertyGroup>                  

You can look at the consummate .csproj file on GitHub

The key items here are the runtime dependencies which are NuGet packages for .NET Standard and explicit assemblies and Nuget packages for the full framework versions. In that location are also custom compiler flags that are set up, which I utilize in the project's code to differentiate betwixt .NET Standard and Total Framework features so I can conditionally subclass code. Typically I utilise NETFULL and NETSTANDARD to differentiate betwixt the two different paradigms and the specific version specifiers similar NET45 and NETSTANDARD_20 which coincide with the standard .NET Framework monikers.

Unlike in older versions of .csproj files the above is like shooting fish in a barrel to read and understand, so modifying the .csproj file manually shouldn't exist a large bargain. I also presume that at some point Visual Studio will support setting up configuration for multiple framework targets interactively probably with a frameworks selection dropdown instead of the unmarried value.

Notation that although you take to deal with framework specific settings using the .csproj file, all project wide features tin can still exist ready through Visual Studio's IDE. So if you add special attributes to files (similar content files to copy in a exam project for case) those features still work from Visual Studio and update the .csproj file for you. It's just the tiptop level target features that are not available in VS right now.

Moving Projection Files

Let's get dorsum to the actual migration of my project.

Because I am essentially creating a new project for this library, I have to move the old files into the new projection. The process is to simply move the files/folders from the old projection into the new. Because you no longer have to explicitly include files into the new SDK project, there's no need to perform an explict Include File step. I can simply re-create files from the quondam project and they will just bear witness upwardly in the new projection.

Because this library is not very characteristic focused, I decided to motility small, logically related chunks of the project at a time in order to not get overwhelmed by the migration errors I was likely to run into.

Depression Level Features: Information technology simply works

In this example I started with several of the the contained utility functions which are freestanding. I used the StringUtils class and it just ported without any issues. Because the features used in these utilities are based on core runtime features no changes are required and they just compile and work. Starting with these allowed me to get the projection upward and compiling for all runtimes, making sure that the cantankerous project compilation works and that the NuGet package generation works.

The skilful news is that a big swath of the library falls into this category. As I pulled in new pieces of the library, about 85% of the files imported required no attention at all - .Internet Standard's larger foot print lets me reuse the majority of my lawmaking as is. The rest required some provisional logic that either removes functionality or uses different logic to implement the aforementioned functionality. More on that in a minute.

Examination Project: NETCOREAPP

At the same time I also brought over the related tests for those initially imported classes. The Test project also has to get through the same framework configuration steps I went over earlier as it too needs to back up all the different target frameworks. The process is pretty much the same, but the test projection (and all other .NET Core non-classlibrary projects) has to target netcoreapp2.0 rather then netstandard2.0:

          <TargetFrameworks>netcoreapp2.0;net45;net40</TargetFrameworks>                  

netcoreapp2.0 targets a specific version of the framework rather than .NET Standard which is currently necessary for top level execution frameworks (console apps and test runners).

Framework Specific Differences

In one case I got through the plain bones files that I knew would port, I started importing some of the more involved components, knowing full well that I was going to run into compatibility problems. This include those that use System.Configuration (which isn't support in .Net Cadre and which is the biggest hurting point for me), a number of System.Data and System.Data.SqlClient issues, and a few odds and ends here and there.

When porting code from total framework .Net to .Cyberspace Core y'all are likely to find a APIs that aren't available or behave differently, then there volition exist some provisional code you demand to write to ensure that code is handled properly.

There are a couple of obvious ways to handle differences:

  • Block out the lawmaking that won't work on .Internet Core 2
  • Utilize conditional code to run lawmaking differently for each framework

Either way this takes the form of using a compile time constant to bracket code or completely removing code that simply isn't going to exist available for .NET Core (or full framework in the reverse case which is probable rare).

To deal with this I utilise custom compiler constants that are declared in the .csproj file for each platform:

          <PropertyGroup Status=" '$(TargetFramework)' == 'netstandard2.0'"> 	<DefineConstants>NETCORE;NETSTANDARD;NETSTANDARD2_0</DefineConstants> </PropertyGroup>  <PropertyGroup Condition=" '$(TargetFramework)' == 'net45'"> 	<DefineConstants>NET45;NETFULL</DefineConstants> </PropertyGroup>  <PropertyGroup Condition=" '$(TargetFramework)' == 'net40'"> 	<DefineConstants>NET40;NETFULL</DefineConstants> </PropertyGroup>                  

In code you can then do things like this:

          #if NETFULL     Console.WriteLine("NETFULL"); #else     Console.WriteLine("NETCORE");