Microsoft released .NET Core 1.0 and I want to port some of my .NET libraries to .NET Core. This article describes how to migrate the existing library TinyCsvParser to .NET Core and shows the changes necessary to make it work.
Visual Studio 2015 RC3, .NET Core Tools
The best way to start with .NET Core in Windows is to use Visual Studio and the additional .NET Core 1.0 Tools:
I had the problem, that the intallation of the .NET Core 1.0 for Visual Studio failed due to an errorneous Visual Studio 2015 version check.
You can skip the version check and finish the installation by running the Setup with the SKIP_VSU_CHECK
parameter:
DotNetCore.1.0.0-VS2015Tools.Preview2.exe SKIP_VSU_CHECK=1
The .xproj / .project.json Situation
One of the key goals of .NET Core was to build a platform, which allows to develop applications with Windows, Mac and Linux. MSBuild wasn't Open Source at the time and so Microsoft has developed a new build system. The current build system is based on project.json files.
So the first step for migrating to the current .NET Core version is to migrate the .csproj projects into the new format. I have decided to have projects for both .csproj and project.json, so you can still use Visual Studio 2013 and your existing MSBuild environment to build the library.
Please keep in mind, that Microsoft has recently decided to step back from project.json and intends to move back to a .csproj / MSBuild build system (see the Microsoft announcement on Changes to Project.json). I really, really hope, that some of the very nice features of project.json survive the change.
global.json
The global.json file specifies what folders the build system should search, when resolving dependencies.
TinyCsvParser currently consists of two projects named TinyCsvParser
, which is the library and TinyCsvParser.Test
,
which is is the test project. In the global.json file you can either specify the projects explicitly or let the .NET Core
tooling try to resolve them automatically.
The global.json for TinyCsvParser looks like this:
{
"projects": [ "TinyCsvParser", "TinyCsvParser.Test" ]
}
project.json
The projects in .NET Core are specified in a project.json file. The project.json file specifies how the project should be built, it includes the dependencies and specifies the frameworks it works with.
Migrating the TinyCsvParser Project
project.json
In the frameworks
section of the file the target .NET frameworks are specified. I want my library to target .NET 4.5 and .NET Core 1.0.
See the .NET Platform Standard for most recent informations on the .NET platform and informations like Target Framework monikers.
- .NET Documentation: .NET Standard Library
- Andrew Lock: Understanding .NET Core, NETStandard, .NET Standard applications and ASP.NET Core
.NET Core is intended to be a very modular platform, so we also need to include the dependencies necessary to build the project. The packages will be resolved from NuGet.
In the scripts
section, we can define various pre-build and post-build events. In the postCompile
event I have instructed
the build system to pack a NuGet Packages and store it in the current Configuration folder (e.g. Debug / Release). This is a great
feature and makes it very simple to distribute the library.
{
"version": "1.5.0",
"title": "TinyCsvParser",
"description": "An easy to use and high-performance library for CSV parsing.",
"copyright": "Copyright 2016 Philipp Wagner",
"authors": [
"Philipp Wagner"
],
"packOptions": {
"owners": [
"Philipp Wagner"
],
"authors": [
"Philipp Wagner"
],
"tags": [ "csv", "csv parser" ],
"requireLicenseAcceptance": false,
"projectUrl": "https://github.com/bytefish/TinyCsvParser",
"summary": "An easy to use and high-performance library for CSV parsing.",
"licenseUrl": "https://opensource.org/licenses/MIT",
"repository": {
"type": "git",
"url": "git://github.com/bytefish/TinyCsvParser"
}
},
"frameworks": {
"net45": {},
"netstandard1.3": {
"dependencies": {
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Runtime.InteropServices": "4.1.0",
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
"System.Globalization": "4.0.11",
"System.Linq": "4.1.0",
"System.Linq.Expressions": "4.1.0",
"System.Linq.Parallel": "4.0.1",
"System.Text.RegularExpressions": "4.1.0",
"System.IO.FileSystem": "4.0.1",
"System.Reflection": "4.1.0",
"System.Reflection.Extensions": "4.0.1",
"System.Reflection.TypeExtensions": "4.1.0"
}
}
},
"scripts": {
"postcompile": [
"dotnet pack --no-build --configuration %compile:Configuration%"
]
}
}
Conditional Compilation for the Reflection API
There were slight changes to the Reflection API in recent .NET Standard Framework versions. An additional call to the
GetTypeInfo
method is necessary to access the property informations of a type. I want the library to have a single
code base, so I have used a preprocessor directive to allow conditional compilation.
I target the .NET Standard 1.3 framework with the library, so the #if
directive looks like this:
public static bool IsEnum(Type type)
{
#if NETSTANDARD1_3
return typeof(Enum).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());
#else
return typeof(Enum).IsAssignableFrom(type);
#endif
}
Migrating the TinyCsvParser.Test Project
project.json
The NUnit Folks have done an amazing job and provide full support for .NET Core 1.0. In the project.json for the project
we simply need to set the testRunner
to nunit
and include the necessary NUnit dependencies. We reference the
TinyCsvParser
as a project dependency.
{
"version": "0.0.0",
"testRunner": "nunit",
"dependencies": {
"dotnet-test-nunit": "3.4.0-beta-1",
"NUnit": "3.4.0",
"TinyCsvParser": {
"target": "project"
}
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"netcoreapp1.0",
"portable-net45+win8"
],
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
}
},
"buildOptions": {
"define": [ "NETCOREAPP" ]
}
}
}
}
Conditional Compilation
One of the Unit Tests used the AppDomain
to obtain the current working directory, and write a file to disk. The AppDomain
is
not available in .NET Core for various valid reasons, but it can be replaced with the AppContext
. So for the .NET Core build
the buildOptions
have been used to define a preprocessor symbol for conditional compilation.
The NETCOREAPP
symbol can now be used in the unit test to obtain the current base directory either from the AppContext
or the
AppDomain
, depending on the target framework.
#if NETCOREAPP
var basePath = AppContext.BaseDirectory;
#else
var basePath = AppDomain.CurrentDomain.BaseDirectory;
#endif
Conclusion
And that's it!
The library is now built for .NET 45 and .NET Core, and the NuGet package is automatically created in a post-build event. Only minimal changes had to be made to build the library against the .NET Core framework. All unit tests went green on first run, and even the NUnit Test Runner in Visual Studio worked without problems.
So migrating an existing project to .NET Core was really easy. Microsoft is currently making hard changes to the .NET ecosystem. And to me it's natural, that a lot of things are still in flux. It was fun to work the project.json based build system and the .NET Core integration in Visual Studio is great.