I’ve been using the .NET Core CLI for a while now, and lurk on the GitHub issues. I’ve seen that some of the aspects of it are a little difficult to understand, especially if you want to contribute to it.

What .NET Version Am I Using?

There’s been a number of issues filed where people are trying to interpret the output of dotnet --version, which today looks something like “1.0.0-preview2-1-003177”. Quite often, the user just installed .NET Core 1.1, then did --version to see that the update took, but then still noticed that it said something like “1.0.0-preview2-1-003177”. What gives?

The first thing to point out is that the Tooling and the Runtime are two different versions. The Tooling has not yet release in 1.0.0 form. The Runtime however, is at 1.1.0 as of writing.

In short, dotnet --version is the version of the tooling. If you want the version of the Core Host, then dotnet is the correct option. It will print something like this:

Microsoft .NET Core Shared Framework Host

Version : 1.1.0

Finally, there is the --info option. This prints some additional information about the runtime environment it thinks you are running, such as the RID, and OS info.

There is an issue on GitHub to make --info better. I would encourage feedback on that issue if all of this seems confusing to you.

SDK versions and the Muxer

.NET CLI allows installing multiple versions. In macOS, you can list them in the directory /usr/local/share/dotnet/sdk/. Which version is used currently depends on your global.json for your project.

global.json allows specifying an SDK version. If global.json doesn’t declare what SDK version it should use, the maximum, non-preview version will be used. Today, we don’t have any versions that aren’t preview, so it’s whatever the maximum version you have installed is.

If you do specify a version, like this:

    "sdk": {
        "version": "1.0.0-preview2-1-003177"

Then that version of the SDK will be used, even if I have 1.0.0-preview4-004130 installed.

This process is handled by the muxer. The muxer’s responsibility is to bootstrap the SDK and tooling version. The first thing the muxer does is walk down the directory structure looking for a global.json and an “sdk” to use. If it finds one and the version is valid, the muxer loads that SDK’s path and tooling.

It’s worth pointing out that global.json affects everything. If you’re in a directory that has a global.json, then everything respects that version of the SDK. If I run dotnet --info or dotnet in a directory that has an SDK, it will behave exactly as that version of the SDK.

This makes it easy to have projects use different SDKs by specifying the global.json at the project root. This means I can have the preview4 nightly toolings installed, all of which use csproj for projects, but also continue to build project.json style projects.

The last thing to remember is that the muxer looks for global.json down the directory structure. So if a parent directory, or parent’s parent directory has one of these files, it will be respected. The “nearest” global.json is honored.

An icky quirk of the muxer is that it silently fails. If you ask to use an SDK version that doesn’t exist, it will just behave as if you didn’t specify one in the first place.

Running Applications

You’ve probably noticed that when you compile and publish an application, it does not include a native executable (ready-to-run). It produces a DLL.

If you want to run a project, use dotnet run.

If you want to run a compiled DLL, use dotnet myapp.dll.

Doing dotnet run myapp.dll looks right, and it might work, but it might not do what you expect. It runs a project, and passes myapp.dll as an argument to Main. If you happen to have a project.json in your working directory, then it is running that.