.Cyberspace Standard and .NET Core have been on my mind for a long fourth dimension now - years really, just the reality is while I've been using the technology quite a chip, I've non jumped in with both feet. In fact, to date I have yet 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 .Net Cadre ii.0 with their much bigger base of operations library foot print and the real possibility of porting the majority of existing library code over to .Net Core actually has been a deciding factor for me to start moving some of my existing full framework libraries that I've been using for equally long as I have been using .NET to .Internet Cadre two.0. Existence able to bring some of the tools I use to be productive over to .NET Core is actually pretty important factor to overcoming my reluctance to move into .Internet Cadre. Nobody wants to rewrite code they already have merely to get back to square 1, but with .Net Core 2.0 it really looks like virtually code will migrate pretty hands.

This isn't just of import to me personally, but I think this is a vital requirement for moving much of the support libraries that exist for .Cyberspace into .Net Cadre and providing the full featured eco-system that we've come to expect from .Cyberspace applications. Currently with .Cyberspace Cadre 1.x it'due south been hit or miss feature wise to feel confident you can actually brand information technology through a project without getting stuck with some missing core feature y'all tin can't easily find and accept to build from scratch. My feeling is that .Internet Core ii.0 will change all that past making information technology possible for about libraries to be ported with minimal effort.

In this post I describe porting an existing total framework library to .NET Cadre ii.0 and multi-targeting the project to back up .NET 4.5, four.0 and .Net Standard 2.0. The porting procedure was even easier than I expected, although the tooling required a bit of patience to get on with.

What y'all need to follow along:

  • Visual Studio 2017 Update iii Preview ii or afterward
  • .Internet Core 2.0 SDK Preview

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

.Net Standard?

A key concept to the porting procedure is .Cyberspace Standard 2.0 and how information technology relates to .Internet Core ii.0.

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

.NET Standard is a standard not an implementation and it'due south 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 .Cyberspace 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 point is that .NET Cadre 2.0 is an implementation of .Net Standard two.0 which means that if I implement a .NET Standard 2.0 compliant DLL it will run on .NET Core 2.0. And any other platform like .Net 4.6.1, Xamarin, Mono, UWP and Unity all of which will eventually support .Internet Standard two.0. By targeting .NET Standard 2.0 I can insure that my DLL will 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 every bit an official guideline to the actual runtime implementers.

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

I don't want to rehash all the details about how .Internet Standard works here, but yous tin can read my earlier blog post .NET Standard 2.0 - Making Sense of .Net Again for a more detailed word on but how that works.

The fundamental takeaway for this post is that your .Cyberspace Applications can now target .Internet 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 post I'll talk about full framework .NET 4.five, 4.0 and .Cyberspace Core 2.

Putting it to a Examination: Porting a .Internet iv.five/iv.0 Library

To actually put this all into perspective I decided to motion i of my libraries - Westwind.Utilities - to .NET Core 2.0 and in the procedure target .Cyberspace Standard two.0, .NET 4.5 and .Cyberspace 4.0 all in a single project. Multi-targeting from a unmarried projection is an awesome feature that makes it possible to create a single .Internet library project that tin target multiple .Cyberspace Framework targets. Using a single project I 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 on years of .NET and it'south 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 most of the features out into split projects, but there's a lot of convenience in having these features I utilize in almost every project provided in a single packet. Anyway, the point is this is very typical full framework legacy .Cyberspace code that was designed with no concept of .Net Cadre and makes a for a good example of what you're likely to detect when you kickoff porting total framework code to .Net Cadre 2 and later.

Creating a new .Net Standard Project

The kickoff footstep 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, 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. You tin also optionally publish and create a Nuget package that includes all runtime versions. The new SDK format lets y'all configure NuGet attributes directly in the csproj file configuration.

I also set upwardly a test projection 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, here'due south the what the final ported project ends upwards looking like in Visual Studio:

A multi-targeted .NET project in Visual Studio

Notice the 3 targets for .NET 4.5, 4.0 and .Net Standard 2.0 all living in the same project. You can also see the dependencies that each of the different runtime implementations are pulling in. .NET Cadre 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 good news is that you can now have a single projection with multiple targets with one single build footstep. 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 directly to modify targets or employ special target configuration settings.

To ram that indicate home, when I become to the project properties for the my class library projection here'due south 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> not <TargetFrameworks>). In society to manage multiple frameworks yous 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 every file to the project. Code files are now implicitly considered part of the project and then no longer need to be explicitly included in the project which drastically reduces the size and complexity of the projection file as well as reducing the change churn in the file which is better for source command direction. At that place are still overrides that let you specify custom behaviors for specific files or add files that need to be explicitly included or pushed out as content into the build binder. But for your base lawmaking files, they are considered included by default unless you tell the project otherwise.

  • Next Editing
    You lot can at present hands edit the .csproj file from Visual Studio while the projection is still active. Most changes are immediately reflected in Visual Studio although in the electric current preview that beliefs is yet a footling spotty and some things require 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 project y'all accept to make at least one alter in your project, by changing the <TargetFramework> element (which is created when you create a new .NET Standard class library projection) to <TargetFrameworks> and providing a listing of semicolon separated targets:

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

Et voila: I now take project that compiles for iii separate targets!

You lot tin can find a list of target frameworks available in the .Cyberspace Platform Guide. Here I'm targeting .Cyberspace Standard ii.0 for my .Net Core 2.0 applications and standard .Internet four.5 and 4.0 for the full framework libraries. Note that if your library tin can work entirely with .Internet Standard and doesn't demand any boosted features, you can potentially but target a version .Net Standard, just if you lot're migrating from full framework yous're probably better off just creating separate full framework targets alongside the .Cyberspace Standard target.

As shown in the project above Visual Studio automatically breaks out the dissimilar runtime dependencies and you lot tin 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 following shows some of the settings I utilize for the .Cyberspace Standard ii.0 and .NET iv.5 targets (omitting the .NET 4.0 ones which are the same equally 4.5 except for the name).

          <!-- common NuGet package refs that affect all projects --> <ItemGroup> 	<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> </ItemGroup>   <!-- .NET Standard ii.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="Organization.Data.SqlClient" Version="four.4.0-preview1-25305-02" /> </ItemGroup>   <!-- .NET 4.5 references, compilation flags and build options --> <ItemGroup Status=" '$(TargetFramework)' == 'net45' ">		 	<Reference Include="mscorlib" /> 	<Reference Include="Organization" /> 	<Reference Include="System.Core" /> 	<Reference Include="Microsoft.CSharp" />		 	<Reference Include="System.Data" /> 	<Reference Include="System.Web" /> 	<Reference Include="System.Drawing" /> 	<Reference Include="Organization.Security" /> 	<Reference Include="System.Xml" /> 	<Reference Include="System.Configuration" /> </ItemGroup> <PropertyGroup Condition=" '$(TargetFramework)' == 'net45'"> 	<DefineConstants>NET45;NETFULL</DefineConstants> </PropertyGroup>                  

You lot can look at the complete .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. There are too custom compiler flags that are fix up, which I utilise in the project'due south code to differentiate between .NET Standard and Full Framework features so I can conditionally bracket lawmaking. Typically I use NETFULL and NETSTANDARD to differentiate between the 2 unlike paradigms and the specific version specifiers like NET45 and NETSTANDARD_20 which coincide with the standard .Cyberspace Framework monikers.

Dissimilar in older versions of .csproj files the higher up is piece of cake to read and understand, so modifying the .csproj file manually shouldn't be a large deal. I also presume that at some point Visual Studio will support setting up configuration for multiple framework targets interactively probably with a frameworks option 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 yet exist set up through Visual Studio's IDE. So if you add special attributes to files (like content files to copy in a test project for example) those features however piece of work from Visual Studio and update the .csproj file for you. It's just the top level target features that are not available in VS right now.

Moving Projection Files

Let'south get back to the bodily migration of my project.

Because I am essentially creating a new projection 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 project 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 copy files from the old project and they volition just show upwardly in the new projection.

Considering this library is not very characteristic focused, I decided to move pocket-size, logically related chunks of the project at a time in order to not get overwhelmed by the migration errors I was probable to see.

Low Level Features: It simply works

In this example I started with several of the the contained utility functions which are freestanding. I used the StringUtils grade and it just ported without whatsoever issues. Because the features used in these utilities are based on core runtime features no changes are required and they merely compile and work. Starting with these allowed 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 large 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 - .NET Standard'southward larger foot print lets me reuse the majority of my lawmaking as is. The rest required some conditional logic that either removes functionality or uses different logic to implement the aforementioned functionality. More on that in a infinitesimal.

Test Projection: NETCOREAPP

At the aforementioned fourth dimension I too 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 back up all the unlike target frameworks. The process is pretty much the aforementioned, but the exam project (and all other .NET Cadre non-classlibrary projects) has to target netcoreapp2.0 rather and 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 (panel apps and exam runners).

Framework Specific Differences

In one case I got through the plainly 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 across compatibility bug. This include those that utilize Organisation.Configuration (which isn't support in .Cyberspace Core and which is the biggest pain point for me), a number of Organization.Data and System.Data.SqlClient problems, and a few odds and ends here and there.

When porting lawmaking from total framework .Cyberspace to .NET Core you are likely to find a APIs that aren't bachelor or behave differently, and so there will be some conditional code you lot need to write to ensure that lawmaking is handled properly.

There are a couple of obvious means to handle differences:

  • Cake out the lawmaking that won't work on .Cyberspace Core 2
  • Apply conditional code to run code differently for each framework

Either way this takes the form of using a compile time abiding to subclass code or completely removing code that just isn't going to exist available for .Cyberspace Core (or full framework in the opposite instance which is probable rare).

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

          <PropertyGroup Condition=" '$(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");