.NET Standard and .Net Core have been on my heed for a long fourth dimension at present - years really, but the reality is while I've been using the technology quite a flake, I've non jumped in with both feet. In fact, to date I take withal to build anything 'real' for customers beyond a few internal infrastructure projects and quite a few sample applications.

For me personally, .NET Standard 2.0 and .Internet Core 2.0 with their much bigger base library foot print and the existent possibility of porting the majority of existing library lawmaking over to .Cyberspace Core really has been a deciding cistron for me to start moving some of my existing full framework libraries that I've been using for as long equally I accept been using .Cyberspace to .Net Core 2.0. Being able to bring some of the tools I utilize to be productive over to .NET Cadre is actually pretty important factor to overcoming my reluctance to move into .NET Cadre. Nobody wants to rewrite code they already have just to go back to square one, but with .NET Core 2.0 it really looks similar most lawmaking will migrate pretty easily.

This isn't just important to me personally, simply I think this is a vital requirement for moving much of the support libraries that be for .NET into .NET Core and providing the full featured eco-system that nosotros've come up to look from .Internet applications. Currently with .NET Cadre ane.x it's been hit or miss feature wise to experience confident yous tin can actually make it through a project without getting stuck with some missing core feature you can't hands find and accept to build from scratch. My feeling is that .Internet Core 2.0 volition change all that past making it possible for most libraries to exist ported with minimal effort.

In this post I describe porting an existing full framework library to .NET Cadre two.0 and multi-targeting the projection to support .NET 4.5, 4.0 and .NET Standard 2.0. The porting process was fifty-fifty easier than I expected, although the tooling required a bit of patience to get on with.

What you need to follow forth:

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

Note that currently there'due south no support for .Internet Core 2.0/.NET Standard ii.0 in the release version of Visual Studio, and that's why the Preview install is required. You can install the minimal .Cyberspace and .NET Core payload for a lightish install, and the install is side by side with Visual Studio 2017 RTM so both piece of work.

.Internet Standard?

A primal concept to the porting process is .Net Standard 2.0 and how information technology relates to .Cyberspace Core ii.0.

For those of you that don't know, .Net Standard is a specification that serves as a blue impress for .NET runtime implementations. The standard specifies what base features the runtime has to implement to support information technology. .Cyberspace Standard describes the base API library - what we used to recall of as the Base of operations Grade Library (BCL) in full framework that brand upwards the cadre features of the platform.

.NET Standard is a standard non an implementation and it's upward to the runtime to implement the features fix forth in the standard. The logistics of this involve some runtime magic where each runtime provides a gear up 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 discussion, the salient betoken is that .Net Core ii.0 is an implementation of .Net Standard 2.0 which ways that if I implement a .Internet Standard 2.0 compliant DLL it volition run on .Net Core 2.0. And any other platform like .Internet iv.vi.1, Xamarin, Mono, UWP and Unity all of which will eventually support .Internet Standard 2.0. By targeting .NET Standard 2.0 I can insure that my DLL volition run on any of the target platforms that .NET Standard supports.

The big win with .NET Standard is that it provides a common interface to consumers of a library, as well equally 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 brand multi-targeting piece of work.

I don't want to rehash all the details nearly how .NET Standard works hither, but you can read my earlier weblog post .Cyberspace Standard 2.0 - Making Sense of .NET Over again for a more detailed word on just how that works.

The key takeaway for this mail is that your .NET Applications can now target .Cyberspace Standard 2.0 in your class libraries (or applications) and can have a very reasonable expectation of interoperability for a number of platforms. In this mail service I'll talk almost full framework .NET 4.5, 4.0 and .NET Cadre 2.

Putting information technology to a Examination: Porting a .NET 4.five/4.0 Library

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

Westwind.Utilities is a really old project that I've been using since the very early years of .NET and it's interesting in this context considering it contains a large hodge-podge of functionality that touches a lot of different framework features in a unmarried library. If I built this today I would probably have broken nigh of the features out into carve up projects, but there's a lot of convenience in having these features I use in almost every projection provided in a unmarried packet. Anyway, the indicate is this is very typical full framework legacy .Net lawmaking that was designed with no concept of .NET Cadre and makes a for a good example of what you're probable to find when y'all start porting total framework lawmaking to .NET Cadre 2 and later.

Creating a new .NET Standard Project

The commencement step for moving this library is to create a new .Net Standard Form Library:

Creating a .NET Standard Class library

This creates a new SDK way 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, test and publish your library. These projects can as well target multiple runtime versions, and when compiled, output multiple versions of your assembly for each runtime. Yous tin can also optionally publish and create a Nuget packet that includes all runtime versions. The new SDK format lets you configure NuGet attributes straight in the csproj file configuration.

I also prepare a test project at the aforementioned time to move over the existing tests for the old project.

Multi-Target Projects in Visual Studio

When it is all said and done, hither'due south the what the final ported project ends up looking like in Visual Studio:

A multi-targeted .NET project in Visual Studio

Notice the three targets for .NET iv.v, 4.0 and .Cyberspace Standard two.0 all living in the same project. You lot can too see the dependencies that each of the different runtime implementations are pulling in. .Cyberspace Core only shows the two packages (Json.cyberspace 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 good news is that you can at present take a single project with multiple targets with one unmarried build step. Yay!

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

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

TargetFramework is missing in Visual Studio

Yup - no runtime target shows because the UI can't handle multiple frameworks (it only looks at <TargetFramework> non <TargetFrameworks>). In order to manage multiple frameworks you currently take to 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 part of the project and so no longer demand to be explicitly included in the project which drastically reduces the size and complication of the projection file equally well equally reducing the change churn in the file which is better for source command management. There are even so overrides that let y'all specify custom behaviors for specific files or add together files that need to be explicitly included or pushed out as content into the build binder. Just for your base code files, they are considered included by default unless you tell the project otherwise.

  • Adjacent Editing
    You lot can at present easily edit the .csproj file from Visual Studio while the project is however active. Most changes are immediately reflected in Visual Studio although in the current preview that behavior is nevertheless a little 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 social club to target multiple platforms with a single projection you have to make at least one alter in your project, by changing the <TargetFramework> element (which is created when you create a new .Cyberspace Standard course 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 divide targets!

Y'all can observe a list of target frameworks bachelor in the .NET Platform Guide. Here I'm targeting .Internet Standard 2.0 for my .Internet Core ii.0 applications and standard .Cyberspace 4.5 and iv.0 for the full framework libraries. Note that if your library can work entirely with .NET Standard and doesn't need any additional features, you can potentially just target a version .Net Standard, but if y'all're migrating from full framework you're probably better off just creating split full framework targets alongside the .Internet Standard target.

As shown in the project above Visual Studio automatically breaks out the dissimilar runtime dependencies and you can manage those in Visual Studio, but they are also referenced in the .csproj file. It's relatively easy to set target specific build and configuration options.

The post-obit shows some of the settings I use for the .NET Standard two.0 and .NET 4.five targets (omitting the .Cyberspace four.0 ones which are the same as 4.5 except for the name).

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

Y'all can look at the complete .csproj file on GitHub

The key items hither are the runtime dependencies which are NuGet packages for .Internet Standard and explicit assemblies and Nuget packages for the full framework versions. There are too custom compiler flags that are set upward, which I employ in the project'southward code to differentiate between .Cyberspace Standard and Full Framework features so I can conditionally bracket code. Typically I use NETFULL and NETSTANDARD to differentiate between the two unlike paradigms and the specific version specifiers like NET45 and NETSTANDARD_20 which coincide with the standard .NET Framework monikers.

Different in older versions of .csproj files the higher up is easy to read and understand, then modifying the .csproj file manually shouldn't be a big deal. I as well assume that at some point Visual Studio will support setting upwards configuration for multiple framework targets interactively probably with a frameworks selection dropdown instead of the single value.

Note that although you have to deal with framework specific settings using the .csproj file, all project wide features can all the same be ready through Visual Studio's IDE. And then if y'all add together special attributes to files (like content files to copy in a test projection for example) those features even so work from Visual Studio and update the .csproj file for you. It's just the acme level target features that are not available in VS right at present.

Moving Project Files

Let's become back to the actual migration of my project.

Considering I am substantially creating a new projection for this library, I take to move the old files into the new projection. The process is to simply motility the files/folders from the former project into the new. Because you lot 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 simply show upward in the new projection.

Considering this library is non very characteristic focused, I decided to move small, logically related chunks of the projection at a time in order to non get overwhelmed by the migration errors I was likely to run into.

Low Level Features: It merely works

In this case I started with several of the the contained utility functions which are freestanding. I used the StringUtils form and it just ported without whatsoever problems. Because the features used in these utilities are based on core runtime features no changes are required and they only compile and work. Starting with these immune me to get the project up and compiling for all runtimes, making sure that the cross project compilation works and that the NuGet package generation works.

The good 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 attending at all - .Cyberspace Standard'southward larger foot print lets me reuse the majority of my code as is. The rest required some provisional logic that either removes functionality or uses different logic to implement the aforementioned functionality. More than on that in a infinitesimal.

Test Project: NETCOREAPP

At the same time I also brought over the related tests for those initially imported classes. The Exam project also has to go through the same framework configuration steps I went over earlier as it too needs to support all the different target frameworks. The process is pretty much the same, but the exam project (and all other .NET Core non-classlibrary projects) has to target netcoreapp2.0 rather so 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

One time I got through the obviously bones files that I knew would port, I started importing some of the more involved components, knowing total well that I was going to run into compatibility problems. This include those that use System.Configuration (which isn't support in .Internet Core and which is the biggest hurting indicate for me), a number of System.Data and System.Data.SqlClient issues, and a few odds and ends here and in that location.

When porting code from total framework .NET to .NET Core you are probable to find a APIs that aren't bachelor or behave differently, and so there will exist some conditional code you demand to write to ensure that code is handled properly.

At that place are a couple of obvious ways to handle differences:

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

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

To deal with this I use custom compiler constants that are alleged 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 Status=" '$(TargetFramework)' == 'net40'"> 	<DefineConstants>NET40;NETFULL</DefineConstants> </PropertyGroup>                  

In code you tin then do things like this:

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