Getting Started With .NET 5
December 14, 2020 by Areg Sarkissian
In this post I will show how to create a basic .NET 5 console application project using the dotnet
command line interface and demonstrate a few new C# 9 features in the process.
I will also show how to add the Entity Framework 5 tooling and how to run and debug your application. I will close out the post by showing how to create a single file executable from your project.
As a bonus, I will also show how you can use git and the Github cli to create local and remote Git repositories for your project, all from the command line.
Most of the content here applies to Windows installations as well, however since I use a Mac for development, the instructions will be based on using the Mac terminal and the command line.
.NET 5 installation on MacOS
Download and install .NET 5 SDK from https://dotnet.microsoft.com/download
After downloading and running the SDK installer, we can verify the version of the dotnet
executable that was placed in the /usr/local/share/dotnet
directory:
cd /usr/local/share/dotnet
./dotnet --version
We should see the displayed .NET SDK version: 5.0.100
To be able to run the dotnet
command globally we need to create a symlink to it in the /usr/local/bin/
binaries directory that is on the system path:
ln -s /usr/local/share/dotnet/dotnet /usr/local/bin/
Now go to your home directory and verify that the dotnet
command is available globally:
cd ~
dotnet --version
We should see the displayed the SDK version:
5.0.100
Creating a .NET project
Within a .NET project, the SDK version that the dotnet
command uses can be specified by a global.json
file.
So before we use the dotnet
command to create a new project, we want to create the project directory and add a global.json
file to specify the .NET SDK version:
mkdir ConsoleApp && cd ConsoleApp
dotnet new globaljson --sdk-version 5.0.100
cat global.json
We should see a new global.json file generated with the following content:
{
"sdk": {
"version": "5.0.100"
}
}
Open the file and add the rollForward
strategy for rolling forward with future versions of the .NET framework SDK:
{
"sdk": {
"version": "5.0.100",
"rollForward": "minor"
}
}
I chose the strategy, to roll forward only with minor versions.
See details for the different rollForward strategies at https://docs.microsoft.com/en-us/dotnet/core/tools/global-json
Save the file and verify the project SDK version again:
dotnet --version
It should still display the SDK version unless you have upgraded to a newer minor version of the SDK.
5.0.100
Before creating the project we can also create a .NET .gitignore
file:
dotnet new gitignore
cat .gitignore
Now create the console app and print out the created project file:
dotnet new console
cat ConsoleApp.csproj
We can see the .NET 5 framework is specified as the target framework:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
To see all available
dotnet new
command project type templates, rundotnet new -l
.
Open the project in VSCode:
# open the current directoy in the VSCode editor
code .
Note: I have configured my VSCode installation to launch the editor from the command line using the
code
command. VSCode is a free editor from Microsoft for cross platform development.
The first time we open a .NET project in VSCode, it will automatically start downloading and installing the editor extension packages it needs for IntelliSense and building and debugging .NET applications.
Installing C# dependencies...
Platform: darwin, x86_64
Downloading package 'OmniSharp for OSX' (49870 KB).................... Done!
Validating download...
Integrity Check succeeded.
Installing package 'OmniSharp for OSX'
Downloading package '.NET Core Debugger (macOS / x64)' (43578 KB).................... Done!
Validating download...
Integrity Check succeeded.
Installing package '.NET Core Debugger (macOS / x64)'
Downloading package 'Razor Language Server (macOS / x64)' (52935 KB).................... Done!
Installing package 'Razor Language Server (macOS / x64)'
Finished
The installed Omnisharp
package will automatically run dotnet build
command which will generate the bin
directory that contains the project binaries.
The dotnet build
command also runs the dotnet restore
command which will try to install any packages in the csproj
file if it finds any.
The package will then pop up Required assets to build and debug are missing from 'ConsoleApp'. Add them?
notification.
This happens every time you open a new project in VSCode where the project does not contain the .vscode
directory and files.
Clicking the yes
button will add the .vscode
directory which contains the launch.json
launch settings file for running and debugging the application from the VSCode GUI.
The .vscode
directory will also contain the tasks.json
tasks settings file for running build tasks from VSCode GUI.
You can find the code for the OmniSharp extension at https://github.com/OmniSharp/omnisharp-vscode
Running the program from the command line
We can run the code as is by using the following command:
dotnet run
The
dotnet run
command will run thedotnet build
command which will in turn run thedotnet restore
command, so there is no need to run each command individually.
The response should be:
Hello World!
Modifying the code
The first thing I will show you is how to use the C# 9 top level statements to eliminate the boilerplate in the program.cs
file.
Open program.cs
in your editor:
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
We can remove much of this boilerplate using the C# 9 top level statements feature:
using System;
Console.WriteLine("Hello World!");
Run the program again
dotnet run
The response should still be:
Hello World!
Now lets refactor to add top level functions:
using System;
Print("Hello World!");
void Print(string outputText) =>
Console.WriteLine(outputText);
Here I have added a Print
function which is called from the Print
statement above it.
Run the program and the see that the output is unchanged.
You can also add top level classes to the file and then instantiate the class and call class methods in the top level statements.
How about adding command line arguments to our program to change the output text or print multiple text lines?
using System;
Print("Hello World!");
foreach (var arg in args)
Print(arg);
void Print(string outputText) =>
Console.WriteLine(outputText);
As you can see now the code loops through and prints any arguments that are passed to the program from the command line.
Try it out:
# run without arguments
dotnet run
# run with one argument
dotnet run -- one
# run with two arguments
dotnet run -- one two
Finally lets add code to interactively read and echo text input lines:
using System;
Print("Hello World!");
foreach (var arg in args)
Print(arg);
Console.WriteLine("Type text to print or hit enter to exit");
while (true)
{
var lineText = Console.ReadLine();
if (lineText.Length == 0)
break;
Print(lineText);
}
void Print(string outputText) =>
Console.WriteLine(outputText);
Run the code again:
dotnet run
After it prints its output, it waits for user input.
Typing any text and hitting the enter key, echos the text to the output.
Hitting enter key alone will exit the program.
Debugging using VSCode
To run this program using the VSCode debugger, use the cmd+shift+d
keyboard shortcut to open the debugger panel and start the debugger.
Before you start the debugger, replace the "console": "internalConsole"
setting in the "name": ".NET Core Launch (console)"
configuration section of the .vscode/launch.json
file to "console": "integratedTerminal"
.
This will allow the Console.ReadLine()
line in the program to except input from the VSCode integrated terminal.
Make sure that you have opened the VSCode terminal tab to interact with the program. By default when you run the debugger the debug tab is in focus showing the debug output of the program.
Another option is to use "console": "externalTerminal"
setting instead of the "console": "internalConsole"
setting. This will launch an external MacOS terminal when you start debugging, instead of using the integrated terminal, just as when you ran the program in the terminal before.
Installing Entity Framework 5
One other thing we may want to do is install the Entity Framework
command line tooling.
This will give us an opportunity to see how the dotnet tools
command line extensions can be installed to add more command line capabilities.
Install the EF Core tool:
dotnet tool install --global dotnet-ef --version 5.0.0
In order to be able to access the tools we need to add the tools directory to our system path.
Add the following to your shell profile file:
export PATH="$PATH:~/.dotnet/tools"
Verify the installed EF Core version:
dotnet ef --version
You should see the response
Entity Framework Core .NET Command-line Tools
5.0.0
If you check the ~/.dotnet/tools/
directory you should see the dotnet-ef
binary file.
To see the available entity framework commands, use the help flag with the command:
dotnet ef --help
To uninstall a global tool we can run the dotnet tool uninstall
command with the same tool name.
dotnet tool uninstall ef
Installing other useful tools
Other tools can be added using the same dotnet tool install
command.
Here is a tool that is a REPL for making HTTP calls to web APIs:
dotnet tool install -g Microsoft.dotnet-httprepl
You run this command directly without prefixing it with the dotnet
command:
httprepl
You can use Ctrl-c to exit the tool
We can also list all installed tools:
dotnet tool list --global
The output lists the two tools we have installed so far.
Package Id Version Commands
-----------------------------------------------------
dotnet-ef 5.0.0 dotnet-ef
microsoft.dotnet-httprepl 5.0.0 httprepl
Building a self contained executable
To create a single file self contained executable from our project, run the dotnet publish
command from the project directory:
dotnet publish -c Release -r osx-x64 -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
The -c flag specifies a Release version of the app and the -r flag specifies the target platform.
The command will produce the following files relative to your project directory.
#binary executable
bin/Release/net5.0/osx-x64/publish/ConsoleApp
#symbol file
bin/Release/net5.0/osx-x64/publish/ConsoleApp.pdb
We can run the program directly from the project directory by executing:
bin/Release/net5.0/osx-x64/publish/ConsoleApp
You should see the app running.
Note we could have used
IncludeAllContentForSelfExtract
instead ofIncludeNativeLibrariesForSelfExtract
to make a the resulting executable extract files to disk as was done in .NET 3.1. However in .NET 5 we have theIncludeNativeLibrariesForSelfExtract
option which will only extract some native libraries to the disk and load the rest of the program directly into memory. Consult the documentation as there are some issues with some of the single file executable options when your program uses reflection.
One More Thing
I like to create a new Github repository anytime I create a new project so I can keep track of my changes from day one.
I prefer the Github command line client to create my remote repository and then using the standard Git command line client to create my local tracking repository.
You can install the Github command line tool using the Homebrew MacOS package manager:
brew install gh
gh --version
The manual for the gh
cli is located at Github cli manual.
Macs come with a preinstalled version of the Git client as part of the XCode development tools. If you don’t want to use your native MacOS git client you can install git using brew as well:
brew install git
git --version
By default the gh
client and the remote Github git
repository are configured to communicate using the HTTP protocol.
I like to configure the gh
client and the Github repository to use the SSH protocol:
#set local git client protocol
gh config set git_protocol ssh
#set remote git repo protocol
gh config set -h github.com git_protocol ssh
#verify local setting
gh config get git_protocol
#verify remote setting
gh config get -h github.com git_protocol
As configured above, the gh config get
commands should respond with ssh
. You can run the commands anytime to change or check the git protocol setting.
Now we are ready to use the gh
client. But before using it we need to first authenticate with Github:
Note: In order to be able to use the SSH protocol with Github, we have to generate and upload SSH keys to Github. Instructions for generating a local SSH key pair and uploading the public key to Github.com can be found in the resources section at the end of this post.
#login to github using the web browser
gh auth login -h github.com --web
This command will print a verification code that you need to copy then paste in the browser window that the command launches to authenticate with Github.
Tip: After you copy the code put your cursor in the leftmost edit box on the web page and paste to fill in the entire code in one shot across all the edit boxes in the web page.
Create the remote repository on Github
Now that the client is authenticated, Create the local and remote repository from within the project directory:
#need to first initialize a local git repository before using the gh client 'repo create' command so the command can add the remote to the local repository
git init
# create a public repo (use --private instead to make the repo private)
# the command will add the remote repo to our remotes so we wont need to add it manually like we normally need to do when using the git client
gh repo create ConsoleApp -d "First .NET 5 App" --public
The command will ask to create the repo named ConsoleApp in the current directory.
Type y
and hit enter to continue to create the remote repo.
After the remote repo is created, it is added to the list of remotes for your local repo, as seen in the output below:
? This will create 'ConsoleApp' in your current directory. Continue? Yes
✓ Created repository aregsar/ConsoleApp on GitHub
✓ Added remote git@github.com:aregsar/ConsoleApp.git
Now that the local and remote repos are created, we can make our first commit and push, creating a tracking branch in the process.
# track all files in the directory
git add -A
# the first commit
git commit -m "first commit"
# push changes to remote
# the -u flag creates a local tracking branch to our upstream remote branch
git push -u origin master
git status
Finally, we can navigate to our repo from the command line:
# inspect the new repository in the web browser
gh repo view --web
When you are done creating the repository, you can logout:
gh auth logout
You can still use the standard git
commands to commit and push changes after logging out with gh
.
And with that we are done!
Resources
ssh-essentials-working-with-ssh-servers-clients-and-keys