An F# eXchange 2018 workshop

How to migrate an existing project to .NET Standard or .NET Core

If you have an existing project and want to add support for .NET Standard or .NET Core, there are three parts to consider

NOTE The .NET Sdk doesnt support just .NET Core/Standard, but also normal .NET Framework.

That mean you can create a console app/lib to just target net461 (.NET Framework 4.6.1). Or both, so .net core (netcoreapp2.0) and net461

Easier incremental way is:

NOTE After you are comfortable with the process, you can do migration of projects in place directly.

Example of migration

A simple project to convert (console app with a library)


Create a basic scaffolding, in parallel to existing

cd src
dotnet new lib -lang F# -n MyLib -o MyLib.NetStandard
cd src
dotnet new console -lang F# -n MyApp -o MyApp.NetCore
dotnet add MyApp.NetCore reference MyLib.NetStandard/MyLib.fsproj
dotnet new sln -n MyApp.NetCore
dotnet sln MyApp.NetCore.sln add src/MyApp.NetCore/MyApp.fsproj
dotnet sln MyApp.NetCore.sln add src/MyLib.NetStandard/MyLib.fsproj

Now you can open it with VSCode, just code .

Convert the library, target NETStandard

We want to use the original files in the new project.




cd src\MyLib.NetStandard
dotnet build

And now we see there is an error, because an api is missing in netstandard2.0

error FS0039: The field, constructor or member 'AsyncDownloadString' is not defined. Maybe you want one of the following:   DownloadString

Fix the api differences of library

By default, targeting any framework, a compiler define is added.

In this project targeting netstandard2.0, the NETSTANDARD2_0 compiler define is added.

So is possibile to #if def the api differences. An easy way is for this is:

    return wc.DownloadString (Uri(url))
    return! wc.AsyncDownloadString (Uri(url))

Is not optimal, but first priority is make it work. after that, each difference can be refactored as needed.

Now, dotnet build should pass.

You can also use custom compiler defines, if the difference is more specific (like NUNIT3, USE_HTTPCLIENT etc) because help reason about features used insted of just api, and make it easier to maintain the code.

Just add that in the fsproj like


Now convert the app too

As before, just copy the Compile, None and fix relative paths

Because .NET Core doesnt support app.config, you can leave it there for later using a conditional check

    <None Include="..\MyApp\App.config" Condition=" '$(TargetFramework)' != 'netcoreapp2.0' " />

Now, let’s try to run it

dotnet run

Adding Multi targeting, to build .NET Framework like the original project.

We need to use <TargetFrameworks (plural, really, plural. I said plural already?) instead of TargetFramework and add net452.

So like


There you should also add back custom compiler defines, special configuration of the original project. All can be used with a Condition=" '$(TargetFramework)' == 'net452' " or any other property

After that, we need to redo restore.

dotnet restore MyApp.NetCore.sln

and build it (this will build all target frameworks).

cd src\MyApp.NetCore
dotnet build

and run a specific framework, like

dotnet run -f net452
dotnet run -f netcoreapp2.0

NOTE the run command call build if needed. the build command call restore if needed. So most of the time, is ok just run the command you want, the dotnet cli should do what is needed.

package it


cd src\MyLib.NetStandard
dotnet pack -c Release

or dotnet pack -c Release /p:Version=1.2.3 to generate a specific version

The -c Release is used to build it in Release configuration.

Remove old project

Now you can just remove the old projects, move the new fsproj and update relative paths inside fsprojs

Additional work usually needed in real world projects