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 newcommand 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
codecommand. 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 runcommand will run thedotnet buildcommand which will in turn run thedotnet restorecommand, 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
IncludeAllContentForSelfExtractinstead ofIncludeNativeLibrariesForSelfExtractto make a the resulting executable extract files to disk as was done in .NET 3.1. However in .NET 5 we have theIncludeNativeLibrariesForSelfExtractoption 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