Creating F# solutions in VSCode from scratch
Now that we have set up our development environment, it’s about time to get started with the coding. In this post we are going to get to know different ways of creating a new F# solution from scratch.
Tools for working with .NET projects:
We’re going to do the following things using those different tools:
- Create a console app
- Create a class library
- Adding a .fs file
- Adding project reference from class lib to console app
- Building
- Running the console app
- Adding NuGet packages
- Adding test project
- Running tests
- Debugging
This will be a 2 part tutorial where we will look at how to do all these steps using two different toolsets, first using the combo Ionide, Forge and Paket and after we do the same thing using dotnet CLI and by hand.
Scaffolding using Ionide, Forge and Paket
1 Create a console app
The first thing you’ll have to do is to create a root folder for your new project and open VSCode in that folder:
$ cd ~/src
$ mkdir MyForgeProject
$ code MyForgeProject
Now you’re going to see a very emtpy VSCode instance without anything in it. Now, to create your project, hit Ctrl+Shift+P and start typing new project.

Choose F#: New Project and choose console from the menu that appears.

First, Forge will ask you which folder to put your new project folder in and next the name of your project:


Your project tree will now look like this:

Alternatively use Ionide's F# Project Explorer and press the green plus sign for creating a new project.

The forge template uses Paket for package management and FAKE for build scripting. It also targets .NET Full framework, but we’re going to change that to .NET Core by editing the MyForgeConsoleApp.fsproj file and replacing
<TargetFramework>net461</TargetFramework>
with
<TargetFramework>netcoreapp2.0</TargetFramework>
Paket is also restricted to resolving >= net461 dependencies, but we’ll have to remove that, since we’re now on .NET Core. Open the paket.dependencies file and remove the line:
framework: >= net461
Your paket.dependencies file should now look like this:
source https://www.nuget.org/api/v2
nuget FSharp.Core
group Build
source https://www.nuget.org/api/v2
nuget FAKE
Since the framework restriction is now removed, you have to update the paket.lock file by calling Paket install via the Ctrl+Shift+P menu in VSCode.

2 Create a class library
To create a class library, open the VSCode Command palette (Ctrl+Shift+P) and choose F#: New Project or press the green plus, but this time choose classlib in the menu. Choose src as Project folder and MyForgeClasslib as Project name. Again, it targets net461, so go to src/MyForgeClasslib/MyForgeClasslib.fsproj and replace the full framework target with a netstandard target, since this is a class lib and not a console app:
<TargetFramework>net461</TargetFramework>
with
<TargetFramework>netstandard2.0</TargetFramework>
3 Adding a .fs file
Ionide has a menu for manipulating projects. Simply right-click the project you want to add a file to and select Add file

and choose a name

Since F# cares about the order of the files in your project, Ionide supports moving files up or down by right-clicking the file in the F# project explorer:

Please note that F# script files (.fsx) are not shown in the F# project explorer, but still might influence this command by standing “in the way” if they are included in the .fsproj file. Have the .fsproj file open while doing this and see what happens.
4 Adding a project reference
Now we want to add a project reference to the classlib in our console app. Open the VSCode Command palette (Ctrl+Shift+P) and write project reference and choose F#: Add Project Reference.

This will first ask you which project to edit and next the which project to reference. Choose the console app and classlib respectively.
Alternatively, right click Project References under MyForgeConsoleApp in the F# Project Explorer pane.

and you will be asked which project to reference.
Forge will now update your MyForgeConsoleApp.fsproj with a <ProjectReference>. Now you can go to your MyForgeConsoleApp.fs and replace it with the following contnet:
module MyForgeConsoleApp
open MyForgeClasslib
[<EntryPoint>]
let main argv =
let x = MyForgeClasslib()
printfn "%A" x.X
0 // return an integer exit code
5 Building
There might be some red errors under the MyForgeClasslib entries, which means that we’ll have to build our project to get everything working. Since this template uses FAKE to build, run it by opening the VSCode Command palette (Ctrl+Shift+P) and writing build and selecting FAKE: Build Default or just pressing Ctrl+F5 as you can see here:

This should eventually give you a Build Time Report and Status: Ok. You might still see some red error markers under open MyForgeConsoleApp. If don’t dissapear by themselves after a little while, try opening your VSCode Command palette (Ctrl+Shift+P) and choose Reload window.
6 Running
Ionide can run the project for you. First you have to set your startup project by right clicking the project you want to debug. Set the MyForgeConsoleApp project as startup project and press the round green icon with a play sign inside. This will actually run it in debug mode.

This should open a terminal and show the output of the applicaion.

7 Adding NuGet packages
Now lets add the console app arguments parser Argu to our console app. First, open the project file to add it to by right clicking MyForgeConsoleApp in F# Project Explorer and choose Open project file from the menu. Then open the VSCode Command palette (Ctrl+Shift+P) and write add nuget package and choose Paket: Add NuGet Package (to current project) and write Argu in the box that appears. Tada! You can now add open Argu to the MyForgeConsoleApp.fs file and start parsing arguments.
8 Adding a test project
In the F# world, Expecto is the testing lib to use and Forge has a project template for that. Press the green plus in F# Project Explorer and choose expecto. Put it in tests folder and call it MyForgeExpecto. Change the <TargetFramework> to netcoreapp2.0 in the MyForgeExpecto.fsproj file. If you get any weird issues, try running Paket install again to update your paket.lock file.
9 Running tests
Ionide has buit in support for Expecto, so simply open VSCode Command palette (Ctrl+Shift+P) and write expecto and choose Expecto: Run or press Ctrl+F6. If you open the Tests.fs file you will now see a green and a red chemistry bottle type thing next to the passing and failing test respectively.

10 Debugging
Ionide has build in support for debugging. First you have to set your startup project by right clicking the project you want to debug. Set the MyForgeExpecto project as startup project, add a breakpoint on line 14 in Tests.fs (by clicking right below the red bottle) and press the round green icon with a play sign inside:

If you also press the debug icon in VSCode's sidebar, you should see the following:

Now you should hopefully be able to scaffold your own projects using Ionide and VSCode and start hacking.
Scaffolding using dotnet CLI
1 Creating a console app
Again, create the root folder for your new project and open VSCode.
$ cd ~/src
$ mkdir MyProject
$ code MyProject
Now hit Ctrl+` to open the integrated terminal, which is a great way to keep your CLI and code in the same window. Now check out which templates you have installed by typing:
$ dotnet new
...
Templates Short Name Language Tags
----------------------------------------------------------------------------------------------------------------------------
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
SAFE-Stack Web App v0.4.0 SAFE F# F#/Web/Suave/Fable/Elmish/Giraffe/Bulma
Simple Fable App fable F# Fable
Unit Test Project mstest [C#], F#, VB Test/MSTest
xUnit Test Project xunit [C#], F#, VB Test/xUnit
ASP.NET Core Empty web [C#], F# Web/Empty
ASP.NET Core Web App (Model-View-Controller) mvc [C#], F# Web/MVC
ASP.NET Core Web App razor [C#] Web/MVC/Razor Pages
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
ASP.NET Core Web API webapi [C#], F# Web/WebAPI
global.json file globaljson Config
NuGet Config nugetconfig Config
Web Config webconfig Config
Solution File sln Solution
Razor Page page Web/ASP.NET
MVC ViewImports viewimports Web/ASP.NET
MVC ViewStart viewstart Web/ASP.NET
...
We’re going to create an F# Console Application by typing:
$ dotnet new console -lang F# -o src/MyConsoleApp
This will create the following tree of files:

2 Create a class library
Next we’re going to create a library project we can use from our awesome console app.
$ dotnet new classlib -lang F# -o src/MyLibrary
The template "Class library" was created successfully.
and your files tree should now look like this:

3 Adding a .fs file
Adding a new file to a project is as simple as right-clicking the folder you want to add it to and select New File.

Just call it NewFile.fs and we’ll have to manually add it to the .fsproj file above or below the existing file.
<ItemGroup>
<Compile Include="Library.fs" />
<Compile Include="NewFile.fs" />
</ItemGroup>
Now you’ll have to add some code to NewFile.fs to not break the build. E.g.:
module NewFile
let add2 x = 2 + x
Now, the VSCode explorer doesn’t take this file ordering into consideration. For that you’ll have to look at Ionide’s F# Project Exploerer.

4 Adding project reference
By hand
With the new MSBuild SDK, it’s actually possible to edit .*proj files by hand. Now add a <ProjectReference> to your MyLibrary.fsproj in your MyConsoleApp.fsproj, which will now look like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.fsproj" />
</ItemGroup>
</Project>
With dotnet CLI
If you’d rather use dotnet CLI, the command works like this:
$ dotnet add <ProjectToAddReferenceTo> reference <ProjectToReference>
So in our case we will run the following from the root directory:
$ dotnet add src/MyConsoleApp/MyConsoleApp.fsproj reference src/MyLibrary/MyLibrary.fsproj
Reference `..\MyLibrary\MyLibrary.fsproj` added to the project.
5 Building
The dotnet CLI build command supports .sln files (we will create one later) or individual .*proj files. To build our console app, type the following:
$ dotnet build src/MyConsoleApp/MyConsoleApp.fsproj
This will create a bunch of files in src/MyConsoleApp/bin/Debug/netcoreapp2.0/. As you can see, .NET Core console apps aren’t by default compiled to an executable, but rather a .dll you have to run using dotnet CLI.
Building as a self-contained executable
.NET Core also supports compiling self-contained executables for a given runtime. Change the <PropertyGroup> of your MyConsoleApp.fsproj file to this:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeIdentifiers>ubuntu.16.04-x64</RuntimeIdentifiers>
</PropertyGroup>
Now you can build your self-contained app by typing this:
$ dotnet publish -c Release -r ubuntu.16.04-x64
Now take a look in src/MyConsoleApp/bin/Release/netcoreapp2.0/ubuntu.16.04-x64/. There you have a MyConsoleApp executable for Ubuntu!
6 Running
Now that we have added a reference to the class library, we can try to use it. Open Program.fs and Library.fs and change the code to something like this and build it again:

With dotnet CLI
We now have three ways of running the console app using the command line:
dotnet run-p src/MyConsoleApp/MyConsoleApp.fsproj <args>dotnetsrc/MyConsoleApp/bin/Debug/netcoreapp2.0/MyConsoleApp.dll <args>- src/MyConsoleApp/bin/Release/netcoreapp2.0/ubuntu.16.04-x64/
MyConsoleApp<args>
So for example:
$ dotnet run -p src/MyConsoleApp/MyConsoleApp.fsproj Scott
Hello Scott
With VSCode
Press F5 and you will see the following menu popping up where you should select .NET Core:

This will create a folder called .vscode with a file called launch.json with the following content (and then some).
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/<insert-target-framework-here>/<insert-project-name-here>.dll",
"args": ["Scott"],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
You’ll have to add /src/MyConsoleApp and edit the <insert-target-framework-here> and <insert-project-name-here> to the target framework in the MyConsoleApp.fsproj file and the name of the console app dll itself. To get the same output as above, add Scott to the args entry :
"program": "${workspaceFolder}/src/MyConsoleApp/bin/Debug/netcoreapp2.0/MyConsoleApp.dll",
"args": ["Scott"],
Press F5 again and you will get the following popup:

Choose Configure Task and VSCode will show you the following:

Just press enter, since there is only one option, and in the following menu, choose .NET Core.

This will create a tasks.json file next to the launch.json file in the .vscode folder, looking like this:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet build",
"type": "shell",
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}
If you try to hit F5 again now, the build will fail, since there is no project defined. To fix this, add the MyConsoleApp.fsproj to the command:
"command": "dotnet build src/MyConsoleApp/MyConsoleApp.fsproj",
Now you should be able to press F5 and see the following in the built-in terminal:

If you like buttons, you could also go to the Debug pane in VSCode which now will list all configs in the launch.json file (which you can open by pressing that cogwheel) and press the green play button:

Now you can set breakpoints in your code and debug your (maybe first) .NET Core F# project in VSCode. How cool is that!

7 Adding NuGet reference
Now we’re going to add an arguments parser to our console app from NuGet. The goto arguments parser for F# is Argu.
By hand
Again, with the new MSBuild SDK, adding a NuGet package is as simple as adding a <PackageReference> to Argu in your MyConsoleApp.fsproj, which will now look like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeIdentifiers>ubuntu.16.04-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.fsproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Argu" Version="5.0.1" />
</ItemGroup>
</Project>
Now do a $ dotnet restore src/MyConsoleApp/MyConsoleApp.fsproj. If you are getting a warning like Detected package downgrade: FSharp.Core, add a <PackageReference> to FSharp.Core in the same <ItemGroup> as Argu, matching at least the minumum version required by Argu:
<ItemGroup>
<PackageReference Include="FSharp.Core" Version="4.3.2" />
<PackageReference Include="Argu" Version="5.0.1" />
</ItemGroup>
With dotnet CLI
dotnet CLI has a command for installing NuGet packages which looks like this:
$ dotnet add <ProjectToAddNuGetTo> package <NuGetPackageId>
So to add Argu to our console app we just type:
$ dotnet add src/MyConsoleApp/MyConsoleApp.fsproj package Argu
To fix the potential Detected package downgrade: FSharp.Core issue, you should in theory just run $ dotnet add src/MyConsoleApp/MyConsoleApp.fsproj package FSharp.Core, however, for me it fails with an error. So you’ll have to add FSharp.Core by hand in the MyConsoleApp.fsproj file as mentioned above or switch to Paket as explained below.
With Paket
Paket is an F# community open source package manager which fixes a lot of the issues NuGet has, e.g. global paket.dependencies and paket.lock files which globally defines which packages and versions are to be used and much more. First, download the paket.bootstrapper.exe and save it as <SolutionFolder>/.paket/paket.exe (yes, you are renaming it. See here for more info).
If you haven’t already added any packages, run the following to get started with Paket and add the Argu package to the MyConsoleApp project:
$ mono .paket/paket.exe init
$ mono .paket/paket.exe add --project src/MyConsoleApp/MyConsoleApp.fsproj Argu
If you’re already using NuGet and want to switch to Paket, run the following command and Paket will figure out which NuGet packages you are using and initialize itself accordingly:
$ mono .paket/paket.exe convert-from-nuget
Paket won’t give you the Detected package downgrade: FSharp.Core issue mentioned above, since it, by default, resolves the higest versions of transient dependencies, unlike NuGet which does the opposite. Paket also automatically hooks into dotnet restore, so you won’t have to do mono .paket/paket.exe restore to restore your packages when using Paket with dotnet CLI.
8 Adding a test project
In F#, Expecto is the goto project for unit-testing. There are two ways to set up a testing project using Expecto. By hand or by dotnet CLI template.
With dotnet CLI
First you need to create a console app, since Expecto is just a library that you can run from console. The second thing you’ll have to do is to add the Expecto NuGet package.
$ dotnet new console -lang F# -o tests/MyTests
$ dotnet add tests/MyTests/MyTests.fsproj package Expecto
$ dotnet restore tests/MyTests/MyTests.fsproj
Replace the contents of Program.fs with the following:
open Expecto
[<EntryPoint>]
let main argv =
Tests.runTestsInAssembly defaultConfig argv
With dotnet CLI template
As you saw earlier, dotnet new did not show any template for Expecto, however, someone has created this for us. To install it type the following:
$ dotnet new -i Expecto.Template::*
...
...
Templates Short Name Language Tags
----------------------------------------------------------------------------------------------------------------------------
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
SAFE-Stack Web App v0.4.0 SAFE F# F#/Web/Suave/Fable/Elmish/Giraffe/Bulma
Simple Fable App fable F# Fable
Expecto .net core Template expecto F# Test
Unit Test Project mstest [C#], F#, VB Test/MSTest
xUnit Test Project xunit [C#], F#, VB Test/xUnit
ASP.NET Core Empty web [C#], F# Web/Empty
ASP.NET Core Web App (Model-View-Controller) mvc [C#], F# Web/MVC
ASP.NET Core Web App razor [C#] Web/MVC/Razor Pages
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
ASP.NET Core Web API webapi [C#], F# Web/WebAPI
global.json file globaljson Config
NuGet Config nugetconfig Config
Web Config webconfig Config
Solution File sln Solution
Razor Page page Web/ASP.NET
MVC ViewImports viewimports Web/ASP.NET
MVC ViewStart
Now you’ll se a new template called Expecto .net core Template that we will create by typing:
$ dotnet new expecto -o tests/MyTests
Your tree will now look something like this:

9 Running tests and debugging
Using dotnet CLI
Running the Expecto tests is as simple as running the console app created by the dotnet CLI template, like this:
$ dotnet run -p tests/MyTests/MyTests.fsproj
[21:17:53 INF] EXPECTO? Running tests... <Expecto>
...
...
[21:18:06 INF] EXPECTO! 8 tests run in 00:00:00.2157219 – 2 passed, 1 ignored, 5 failed, 0 errored. ( ರ Ĺ̯ ರೃ ) <Expecto>
Using VSCode
Now you’ll have to configure a build task and launch parameters for the Expecto project by first pressing the cogwheel here:

which opens launch.json. Add the following to the list of configurations:
{
"name": ".NET Core Launch (tests)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-tests",
"program": "${workspaceFolder}/tests/MyTests/bin/Debug/netcoreapp2.0/MyTests.dll",
"args": [""],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
Here we have changed the name, preLaunchTask and the program entries. Now your list of debug targets should look like this:

Now select the .NET Core Lunch (tests) entry and press the green play button and you will get a popup saying Could not find the preLaunchTask 'build-tests'.
Just select Configure Task again and choose build in the next popup, which will open the tasks.json again. Now add the following task to the list of tasks:
{
"label": "build-tests",
"command": "dotnet build tests/MyTests/MyTests.fsproj",
"type": "shell",
"group": "test",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
As you can see, the label matches the preLaunchTask in launch.json. Now try running the .NET Core Lunch (tests) again and Expecto should now start in debug mode.
But where is the solution file?
With .NET Core and dotnet CLI the .sln file is not really necessary anymore, however, it is still supported and it can make the workflow a bit simpler by having dotnet CLI just run the .sln instead of having to point to the specific .*proj files for building. So, to create a solution file we must do the following:
- Create the solution file
- Add all projects to it
Both can be done using dotnet CLI like this:
$ dotnet new sln -n MyApp
$ dotnet sln add src/MyConsoleApp/MyConsoleApp.fsproj src/MyLibrary/MyLibrary.fsproj tests/MyTests/MyTests.fsproj
Project `src/MyConsoleApp/MyConsoleApp.fsproj` added to the solution.
Project `src/MyLibrary/MyLibrary.fsproj` added to the solution.
Project `tests/MyTests/MyTests.fsproj` added to the solution.
Now to build the application, simply type the following from the root folder of your solution (where the MyApp.sln file is located):
$ dotnet build
Now you can go into tasks.json and replace the two separate build and build-tests tasks with a single default one which looks like this:
{
"label": "build",
"command": "dotnet build",
"type": "shell",
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
See that the project path is removed from the command. Now you can open launch.json and change the preLaunchTasks for both configurations to the same build task.
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/MyConsoleApp/bin/Debug/netcoreapp2.0/MyConsoleApp.dll",
"args": ["Scott"],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": ".NET Core Launch (tests)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/tests/MyTests/bin/Debug/netcoreapp2.0/MyTests.dll",
"args": [""],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
}
So the .sln file can actually be quite useful.
This ended up being quite long, but I hope you learned something along the way. So now you should be able to create great F# apps using VSCode on any platform using dotnet CLI and Ionide. See, it even has menus and a Play button!