An F# eXchange 2018 workshop
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.
A simple project to convert (console app with a library)
https://github.com/enricosada/fsharpx-2018-workshop-sample-app/
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
sln
file in root directorydotnet 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 .
We want to use the original files in the new project.
delete useless source file Library.fs
generated by template
Now copy the <Compile
list to new lib from source library
fix relative path of Compile
,None
items
replace
Include="
with
Include="..\MyLib\
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
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:
#if NETSTANDARD2_0
return wc.DownloadString (Uri(url))
#else
return! wc.AsyncDownloadString (Uri(url))
#endif
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
<DefineConstants>USE_HTTPCLIENT;NO_CONFIGURATIONMANAGER;CUSTOM_WEBPROXY;$(DefineConstants)</DefineConstants>
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
We need to use <TargetFrameworks
(plural, really, plural. I said plural already?) instead of TargetFramework
and add net452
.
So like
<TargetFrameworks>netstandard2.0;net452</TargetFrameworks>
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.
Run
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.
Now you can just remove the old projects, move the new fsproj and update relative paths inside fsprojs
dotnet pack
Directory.Build.props
filedotnet
commands in the build script
dotnet restore
the solution, not each project :D. Same for build
, pack
, etcdotnet test
command instead of specific tools (but depends, like dotnet xunit
)#if NETSTANDARD2_0