In this article, we will learn more about how to use the CLI to build and run .NET applications.
Whether taking our first steps in cross-platform development based on the .NET common language runtime (CLR), or brushing up on some basics to explore a different way to work, we are in the right place. For an overview of the basics of C# and .NET, be sure to check out our C# Back to Basics series and Intro to .NET and C#.
Let’s start!
Create the First Project
Before we can run the command line interface (CLI), we need to make sure we have all the required files. The CLI is included in the .NET SDK. For more installation information, refer to Install .NET SDK. To verify that we can run the CLI, we can open the command line terminal and run the dotnet
command. If it works, we should see the output:
Usage: dotnet [options] Usage: dotnet [path-to-application] Options: -h|--help Display help. --info Display .NET information. --list-sdks Display the installed SDKs. --list-runtimes Display the installed runtimes. path-to-application: The path to an application .dll file to execute.
Create the Project
Those who have used Visual Studio to develop C# applications may remember that it offers many file and project templates that save us significant time and effort when creating new projects and files. The same templates are available for .NET cross-platform development with the dotnet new
command. The basic structure of the command is:
dotnet new <template> -o <output directory> -n <name>
If we omit the name argument it will default to using the last part of the output directory, which is usually a reasonable default. Let’s use this command to create a console application:
dotnet new console -o HelloWorld
This produces output like the following:
The template "Console App" was created successfully. Processing post-creation actions... Restoring C:\Workspace\CodeMaze\HelloWorld\HelloWorld.csproj: Determining projects to restore... Restored C:\Workspace\CodeMaze\HelloWorld\HelloWorld.csproj (in 68 ms). Restore succeeded.
As we can see, it created a new folder HelloWorld
and inside it a C# project named HelloWorld.csproj
. If we look inside the folder we will find a familiar project structure.
For more details about the dotnet new
command and its argument please refer to the dotnet new command documentation.
Explore the Templates
For information about the installed templates we use the list
subcommand:
dotnet new list
Which produces an output similar to the following:
These templates matched your input: Template Name Short Name Language Tags -------------------------------------------- ------------------- ---------- -------------------------------- ASP.NET Core Empty web [C#],F# Web/Empty Class Library classlib [C#],F#,VB Common/Library Console App console [C#],F#,VB Common/Console dotnet gitignore file gitignore Config Dotnet local tool manifest file tool-manifest Config MSTest Test Project mstest [C#],F#,VB Test/MSTest NuGet Config nugetconfig Config NUnit 3 Test Item nunit-test [C#],F#,VB Test/NUnit NUnit 3 Test Project nunit [C#],F#,VB Test/NUnit Protocol Buffer File proto Web/gRPC Solution File sln,solution Solution Web Config webconfig Config xUnit Test Project xunit [C#],F#,VB Test/xUnit ... (output truncated)
We can see that even though C# is the default language, we can also use F# and VB (--language
argument). This list can also be expanded by installing new templates with the install subcommand.
Using CLI to Run .NET Applications
Now that we have created the project, we can use the .NET CLI to run it. We will first position ourselves in the project folder:
cd HelloWorld
And then run
the project:
dotnet run
We should see the output:
Hello, World!
If we inspect the project folder now, we will see that dotnet
compiled the project into an executable file, and then executed it from the output directory.
Additional arguments allow us to specify a different architecture or configuration, or even pass arguments to the console application that we want to execute. For more details, refer to the dotnet run command documentation.
This command is intended for fast, iterative development. We can combine it with the watch command for hot reloads and faster iterations.
The run command needs the source files and NuGet package cache, which will be compiled and restored by the build command that is implicitly executed. That’s why this is not a recommended way to run applications in a production environment. Instead, we generally use the publish
command to create a deployment. More about that in a minute.
Using the CLI to Build .NET Applications
When we run the .NET application using CLI, the build command is executed implicitly. However, dotnet
is also used for scripting custom release pipelines. In case we need the explicit build step, we can do it like this:
dotnet build
We can expect output similar to this:
MSBuild version 17.6.1+8ffc3fe3d for .NET Determining projects to restore... All projects are up-to-date for restore. HelloWorld -> C:\Workspace\CodeMaze\HelloWorld\bin\Debug\net7.0\HelloWorld.dll Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:01.30
In this example, we see the use of MSBuild, the output path for the project, the outcome, and the total time it took to build this project. Also, there were no packages to restore. We’ll fix that in the next section.
There are more options for building a project than we can cover in the scope of this article. For more details, see the dotnet build command documentation.
Add the Package Reference
Assuming that our project requires interaction with some other web resources and that we found a library to make this job easier. We can add a reference to this library with the following command:
C:\Workspace\CodeMaze\HelloWorld>dotnet add package Flurl
The output will look similar to this:
Determining projects to restore... Writing C:\Users\LENOVO\AppData\Local\Temp\tmp2F17.tmp info : X.509 certificate chain validation will use the default trust store selected by .NET for code signing. info : X.509 certificate chain validation will use the default trust store selected by .NET for timestamping. info : Adding PackageReference for package 'Flurl' into project 'C:\Workspace\CodeMaze\HelloWorld\HelloWorld.csproj'. info : GET https://api.nuget.org/v3/registration5-gz-semver2/flurl/index.json info : OK https://api.nuget.org/v3/registration5-gz-semver2/flurl/index.json 360ms info : Restoring packages for C:\Workspace\CodeMaze\HelloWorld\HelloWorld.csproj... info : GET https://api.nuget.org/v3-flatcontainer/flurl/index.json info : OK https://api.nuget.org/v3-flatcontainer/flurl/index.json 242ms info : GET https://api.nuget.org/v3-flatcontainer/flurl/3.0.7/flurl.3.0.7.nupkg info : OK https://api.nuget.org/v3-flatcontainer/flurl/3.0.7/flurl.3.0.7.nupkg 53ms info : Installed Flurl 3.0.7 from https://api.nuget.org/v3/index.json with content hash ... info : Package 'Flurl' is compatible with all the specified frameworks in project 'C:\Workspace\... info : PackageReference for package 'Flurl' version '3.0.7' added to file 'C:\Workspace\CodeMaze\... info : Writing assets file to disk. Path: C:\Workspace\CodeMaze\HelloWorld\obj\project.assets.json log : Restored C:\Workspace\CodeMaze\HelloWorld\HelloWorld.csproj (in 642 ms).
As we can see, dotnet
checked the version compatibility, added the reference to the .csproj file, and downloaded the required package files (in a slightly different order). We can inspect the project file:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Flurl" Version="3.0.7" /> </ItemGroup> </Project>
Note the highlighted line that references the Flurl library, along with a specific version.
If we inspect the project folder we won’t find Flurl.dll or Flurl.nupkg. The reason is that referenced packages will be downloaded to the package cache which, on a development computer, is located in the user folder under .nuget
.
We can now use the features offered by the library that we added. Let’s edit the Program.cs
file:
using Flurl; Console.WriteLine("Hello, World!"); Console.WriteLine("https://api.site.com".AppendPathSegments("area", "class"));
If we now run the run command:
dotnet run
We will see the output:
Hello, World! https://api.site.com/area/class
As expected, the Flurl library correctly appended two path segments to the base URL.
Publish the Project
We have already covered how to build and run .NET applications using CLI. However, whether we are scripting the continuous integration/deployment (CI/CD) pipeline or we want to publish the project manually, we will need to make sure that we package all the required artifacts so that our project can execute properly in the target environment.
Let’s run the publish
command:
dotnet publish
The output shows us the most important information:
MSBuild version 17.6.1+8ffc3fe3d for .NET Determining projects to restore... All projects are up-to-date for restore. HelloWorld -> C:\Workspace\CodeMaze\HelloWorld\bin\Debug\net7.0\HelloWorld.dll HelloWorld -> C:\Workspace\CodeMaze\HelloWorld\bin\Debug\net7.0\publish\
The publish
command implicitly executed the build command and packaged all files needed to run the HelloWorld program in the publish folder mentioned in the last line. Let’s inspect the contents of that folder:
C:\Workspace\CodeMaze\HelloWorld>dir bin\Debug\net7.0\publish /B Flurl.dll HelloWorld.deps.json HelloWorld.dll HelloWorld.exe HelloWorld.pdb HelloWorld.runtimeconfig.json
Apart from the expected binary and symbolic files, we also see HelloWorld.deps.json and HelloWorld.runtimeconfig.json. As their extensions suggest, we use them for further automated dependency processing (optional) and runtime configuration, such as garbage collection mode (also optional). Note the Flurl.dll
in the publish folder. The publish command will copy all dependencies into the output folder. This way, we don’t have to install the dependencies separately on the target machine.
We can use several arguments for the publish
command to fine-tune the output for the specific environment to which we want to publish. For example, we might want to have different configurations for testing and for the production environment, or we might want to prepare deployments for multiple frameworks or architectures. For more information check out the dotnet publish command documentation.
Create the First Solution
The CLI allows us to create solution files that group multiple projects. First, let’s add one more project:
dotnet new classlib -o HelloWorldCore
Now we can create a new solution:
dotnet new sln -n HelloWorldSolution
The output is not very verbose, but it informs us that the solution file is created successfully:
The template "Solution File" was created successfully.
We don’t have much use for an empty solution so we should add the projects we created earlier:
C:\Workspace\CodeMaze>dotnet sln add .\HelloWorld\HelloWorld.csproj Project `HelloWorld\HelloWorld.csproj` added to the solution. C:\Workspace\CodeMaze>dotnet sln add .\HelloWorldCore\HelloWorldCore.csproj Project `HelloWorldCore\HelloWorldCore.csproj` added to the solution.
Now, when we run the build
command in the same directory:
dotnet build
We get some different output than we did with just one project:
MSBuild version 17.6.1+8ffc3fe3d for .NET Determining projects to restore... All projects are up-to-date for restore. HelloWorld -> C:\Workspace\CodeMaze\HelloWorld\bin\Debug\net7.0\HelloWorld.dll HelloWorldCore -> C:\Workspace\CodeMaze\HelloWorldCore\bin\Debug\net7.0\HelloWorldCore.dll Build succeeded. 0 Warning(s) 0 Error(s)
Note that it now builds both projects, each in its respective output directory.
Add the Project Reference
Most of the time, projects in a solution tend to have interdependencies. Let’s see how we can reference one project from another in the same solution.
First, we need to position ourselves in the referencing project folder. Then we can use the add reference
command to add the dependency project:
C:\Workspace\CodeMaze>cd HelloWorld C:\Workspace\CodeMaze\HelloWorld>dotnet add reference ..\HelloWorldCore\HelloWorldCore.csproj
When the command completes, we will see the output:
Reference `..\HelloWorldCore\HelloWorldCore.csproj` added to the project.
We can now use any classes defined in the HelloWorldCore
project from the HelloWorld
console application.
If we now run the publish
command in the solution folder:
C:\Workspace\CodeMaze>dotnet publish
We can see that MSBuild built the projects from the solution and copied the publish output folder:
MSBuild version 17.6.1+8ffc3fe3d for .NET Determining projects to restore... Restored C:\Workspace\CodeMaze\HelloWorld\HelloWorld.csproj (in 238 ms). 1 of 2 projects are up-to-date for restore. HelloWorldCore -> C:\Workspace\CodeMaze\HelloWorldCore\bin\Debug\net7.0\HelloWorldCore.dll HelloWorldCore -> C:\Workspace\CodeMaze\HelloWorldCore\bin\Debug\net7.0\publish\ HelloWorld -> C:\Workspace\CodeMaze\HelloWorld\bin\Debug\net7.0\HelloWorld.dll HelloWorld -> C:\Workspace\CodeMaze\HelloWorld\bin\Debug\net7.0\publish\
Let’s inspect the publish output folder for the HelloWorld
console application:
C:\Workspace\CodeMaze>dir HelloWorld\bin\Debug\net7.0\publish /B Flurl.dll HelloWorld.deps.json HelloWorld.dll HelloWorld.exe HelloWorld.pdb HelloWorld.runtimeconfig.json HelloWorldCore.dll HelloWorldCore.pdb
It contains all the dependencies needed to run the application, including the one we added.
Test the Solution From the CLI
The last (but not least) command that we will cover in this article is the test
command. To test the solution we will first need to add a test project. We can use the new
command to create one:
C:\Workspace\CodeMaze>dotnet new nunit -o HelloWorldTests The template "NUnit 3 Test Project" was created successfully. Processing post-creation actions... Restoring C:\Workspace\CodeMaze\HelloWorldTests\HelloWorldTests.csproj: Determining projects to restore... Restored C:\Workspace\CodeMaze\HelloWorldTests\HelloWorldTests.csproj (in 4,8 sec). Restore succeeded.
Then we will add it to the solution:
C:\Workspace\CodeMaze>dotnet sln add HelloWorldTests\HelloWorldTests.csproj Project `HelloWorldTests\HelloWorldTests.csproj` added to the solution.
Because tests typically reference the library they are testing, let’s add the reference to HelloWorldCore
project:
C:\Workspace\CodeMaze>CD HelloWorldTests C:\Workspace\CodeMaze\HelloWorldTests>dotnet add reference ..\HelloWorldCore\HelloWorldCore.csproj Reference `..\HelloWorldCore\HelloWorldCore.csproj` added to the project.
Now (from the solution directory) we are ready to test the solution:
dotnet test
We can expect an output similar to this:
Determining projects to restore... Restored C:\Workspace\CodeMaze\HelloWorldTests\HelloWorldTests.csproj (in 205 ms). 2 of 3 projects are up-to-date for restore. HelloWorldCore -> C:\Workspace\CodeMaze\HelloWorldCore\bin\Debug\net7.0\HelloWorldCore.dll HelloWorldTests -> C:\Workspace\CodeMaze\HelloWorldTests\bin\Debug\net7.0\HelloWorldTests.dll Test run for C:\Workspace\CodeMaze\HelloWorldTests\bin\Debug\net7.0\HelloWorldTests.dll (.NETCoreApp,Version=v7.0) Microsoft (R) Test Execution Command Line Tool Version 17.6.0 (x64) Copyright (c) Microsoft Corporation. All rights reserved. Starting test execution, please wait... A total of 1 test files matched the specified pattern. Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: 23 ms - HelloWorldTests.dll (net7.0)
The first part builds the test project along with all the references. The second part informs us of the directory from which the tests run, along with their outcomes. As we can see, the default test project template comes with one passing test. This can be very useful to verify that we have set up the environment correctly before we start writing actual unit tests.
In this example, we used an NUnit test project. We could also use the xUnit or MSTest template and we would see very similar output.
Similar to its sibling commands, the test
command can also take a wide variety of arguments to further configure its execution, from test filtering to crash dump creation. For more details be sure to visit the dotnet test command documentation.
Conclusion
We’ve covered some basics about the main commands used to build and run .NET applications using only the CLI. Some of them, like new
, run
, sln
, and add
, are used in development loops, while creating the solution structure and writing the code. Others, like build
, test
, and publish
, can be used in development loops, but also in scripts on the build server to automate publishing cycles or continuous integration. They can be very useful regardless of the IDE being used.
As we’ve seen from this brief introduction, the .NET CLI allows us to further enhance our development workflow and embrace the full potential of .NET. Let’s experiment with the .NET CLI in our projects, explore its extensive capabilities, and begin our journey towards more streamlined and effective .NET development.