When developing with .NET Core, you have two different workflows to choose from: manage projects from Visual Studio or work from a command-line using dotnet commands. New projects can be created this way, and after the recent updates the project templates system became extensible, allowing to install additional templates or even create your own.

“dotnet new” and friends

For example, this is how the output of dotnet new looks like by default with the latest RC installed (.NET Core 1.0 SDK RC4, to be specific):

> dotnet new
Template Instantiation Commands for .NET Core CLI.

Usage: dotnet new [arguments] [options]

Arguments:
  template  The template to instantiate.

Options:
  -l|--list         List templates containing the specified name.
  -lang|--language  Specifies the language of the template to create
  -n|--name         The name for the output being created. If no name is specified, the name of the current directory is used.
  -o|--output       Location to place the generated output.
  -h|--help         Displays help for this command.
  -all|--show-all   Shows all templates


Templates                                 Short Name      Language      Tags
--------------------------------------------------------------------------------------
Console Application                       console         [C#], F#      Common/Console
Class library                             classlib        [C#], F#      Common/Library
Unit Test Project                         mstest          [C#], F#      Test/MSTest
xUnit Test Project                        xunit           [C#], F#      Test/xUnit
Empty ASP.NET Core Web Application        web             [C#]          Web/Empty
MVC ASP.NET Core Web Application          mvc             [C#], F#      Web/MVC
Web API ASP.NET Core Web Application      webapi          [C#]          Web/WebAPI
Solution File                             sln                           Solution

Examples:
    dotnet new mvc --auth None --framework netcoreapp1.0
    dotnet new classlib --framework netstandard1.4
    dotnet new --help

We can additionally install JavaScriptServices templates like this:

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

As mentioned in the recent ASP.NET Community Standup, the asterisk at the end means “any version” and is required, because --install argument needs a version to be specified.

Now, when we execute dotnet new once again, the list will have more items and we can use these nicely preconfigured .NET Core + React/Angular/etc. projects:

Templates                                  Short Name      Language      Tags
---------------------------------------------------------------------------------------
Console Application                        console         [C#], F#      Common/Console
Class library                              classlib        [C#], F#      Common/Library
Unit Test Project                          mstest          [C#], F#      Test/MSTest
xUnit Test Project                         xunit           [C#], F#      Test/xUnit
Empty ASP.NET Core Web Application         web             [C#]          Web/Empty
MVC ASP.NET Core Web Application           mvc             [C#], F#      Web/MVC
MVC ASP.NET Core with Angular              angular         [C#]          Web/MVC/SPA
MVC ASP.NET Core with Aurelia              aurelia         [C#]          Web/MVC/SPA
MVC ASP.NET Core with Knockout.js          knockout        [C#]          Web/MVC/SPA
MVC ASP.NET Core with React.js             react           [C#]          Web/MVC/SPA
MVC ASP.NET Core with React.js and Redux   reactredux      [C#]          Web/MVC/SPA
Web API ASP.NET Core Web Application       webapi          [C#]          Web/WebAPI
Solution File                              sln                           Solution

These new SPA templates are built by Steve Sanderson (the author of knockout.js) together with the ASP.NET team and deserve a separate post, so I’m not going to discuss them now. What I want to talk about instead is the extensibility of dotnet command itself.

How “dotnet” command works

Obviously, there is not too much magic: the .NET Core installer copies dotnet.exe to C:\Program Files\dotnet and adds this directory to PATH. When in doubt, remember about where command, which can help identify the location of an executable:

> where dotnet
C:\Program Files\dotnet\dotnet.exe

In most cases dotnet command will be executed with an additonal argument, which is the actual action we want to perform, like new, build, or run. The list of supported ones can be displayed by running dotnet help:

Commands:
  new           Initialize .NET projects.
  restore       Restore dependencies specified in the .NET project.
  build         Builds a .NET project.
  publish       Publishes a .NET project for deployment (including the runtime).
  run           Compiles and immediately executes a .NET project.
  test          Runs unit tests using the test runner specified in the project.
  pack          Creates a NuGet package.
  migrate       Migrates a project.json based project to a msbuild based project.
  clean         Clean build output(s).
  sln           Modify solution (SLN) files.

All of them are the built-in commands, which can be seen in the source code on Github:

public class Program
{
    private static Dictionary<string, Func<string[], int>> s_builtIns = new Dictionary<string, Func<string[], int>>
    {
        ["add"] = AddCommand.Run,
        ["build"] = BuildCommand.Run,
        ["clean"] = CleanCommand.Run,
        ["help"] = HelpCommand.Run,
        ["list"] = ListCommand.Run,
        ["migrate"] = MigrateCommand.Run,
        ["msbuild"] = MSBuildCommand.Run,
        ["new"] = NewCommandShim.Run,
        ["nuget"] = NuGetCommand.Run,
        ["pack"] = PackCommand.Run,
        ["publish"] = PublishCommand.Run,
        ["remove"] = RemoveCommand.Run,
        ["restore"] = RestoreCommand.Run,
        ["restore-projectjson"] = RestoreProjectJsonCommand.Run,
        ["run"] = RunCommand.Run,
        ["sln"] = SlnCommand.Run,
        ["test"] = TestCommand.Run,
        ["vstest"] = VSTestCommand.Run,
    };

...

However, if you tried using Entity Framework Core CLI, you know that it is managed using the dotnet ef command installed with a separate NuGet package. And there is definitely no “ef” in the list of built-in commands above. How does this actually work? What happens when we execute dotnet COMMANDNAME?

The answer is again in the source code, same Program.cs file:

int exitCode;
Func<string[], int> builtIn;
if (s_builtIns.TryGetValue(command, out builtIn))
{
    exitCode = builtIn(appArgs.ToArray());
}
else
{
    CommandResult result = Command.Create(
            "dotnet-" + command,
            appArgs,
            FrameworkConstants.CommonFrameworks.NetStandardApp15)
        .Execute();
    exitCode = result.ExitCode;
}

return exitCode;

So dotnet.exe will look for COMMANDNAME in the predefined list, and if it isn’t found, will try to run an executable with the filename dotnet-COMMANDNAME, passing down the rest of the original arguments to it. This only works if a file dotnet-COMMANDNAME.exe can be found in at least one of the places from the PATH variable.

In fact, this is almost true: it actually doesn’t have to be an *.exe file, as you can see in the snippet above. Anything that is on PATH, can be executed and has the name dotnet-COMMANDNAME will do. Which opens some creative possibilities…

Batch file

Let’s start from the simplest example. I created a batch file dotnet-hi.bat in C:\tools\go\bin (which happens to be in my computer’s PATH) with the following content:

@echo off
echo Hi there!

And now I can do dotnet hi from anywhere:

> dotnet hi
Hi there!

Ok, it works, but not soo exciting. Let’s add some interaction and create the following dotnet-flickr.bat file:

@echo off
set "url=https://www.flickr.com/photos/tags/%1"
start %url%

Now, when you are feeling sad, just run dotnet flickr cats and be happy!

We can also do something at least remotely useful, for example, create a dotnet-code.bat, which will start Visual Studio Code in the current directory:

@echo off
code .

dotnet rocks!

You can only do much with scripting, so there will be a point when a full-blown executable makes more sense. Plus, so far the commands have been still pretty simple and boring. And since I am a big fan of .NET Rocks! show, I decided to pay a tribute to Carl and Richard: the greatest podcast about .NET absolutely deserves its own dotnet command.

So let me introduce you to dotnet rocks!

> dotnet rocks
Pick one of 10 last episodes on .NET Rocks! to play:
 -16 Feb 2017: Fusion Power Update Geek Out
 -15 Feb 2017: Virtual, Augmented and Mixed Realities with Jessica Engstrom
 -14 Feb 2017: Machine Learning Panel at NDC London
 -09 Feb 2017: Ops and Operability with Dan North
 -08 Feb 2017: Xamarin MVVM apps with Gill Cleeren
 -07 Feb 2017: Chatbots with Galiya Warrier
 -02 Feb 2017: IdentityServer4 with Brock Allen and Dominick Baier
 -01 Feb 2017: Data and Docker with Stephanie Locke
 -31 Jan 2017: Nodatime, Google Cloud and More with Jon Skeet
 -26 Jan 2017: Punishment Driven Development with Louise Elliott

(Disclaimer: only freely available data is used here and I am not affiliated with .NET Rocks!, although I can’t recommend it enough! When asked for permission to use “dotnet rocks” as the tool name, Carl and Richard kindly allowed me to “go for it”.)

The tool itself is very simple: it is built as a .NET Core console app, which will display a menu of N (10 by default) last episodes from .NET Rocks! and allow to pick one. Once selected, the URL of episode’s MP3 file will be opened by the default OS program (most likely - will open in your default browser and start playing). The source code is on https://github.com/atsvetkov/dotnet-rocks and the packaged version is available in a NuGet format at https://www.nuget.org/packages/dotnet-rocks. This last one is actually important: having an executable somewhere on your PATH is not exactly the most robust way of installing .NET CLI extensions, so there is a better option: adding it as a .NET CLI tool reference to a project file. So, since it is already on nuget.org, you can just create a new .NET Core app and add a tool reference to “dotnet-rocks” in a .csproj file:

<ItemGroup>
    <DotNetCliToolReference Include="dotnet-rocks" Version="0.0.1" />
</ItemGroup>

Then, after performing dotnet restore, you should be able to run dotnet rocks in this project’s folder. This time there is no executable with the name dotnet-rocks anywhere on the PATH, but there is a package folder with dotnet-rocks.dll in the system-wide NuGet packages directory, which will also be probed by .NET CLI.

This way of installing custom extensions seems much cleaner and more under control than scripts with magic names residing in unpredictable corners of the filesystem. And now your dream of checking out the latest .NET Rocks episodes from the command-line has finally come true! OK, maybe it wasn’t anyone’s dream, but I just felt it had to be done.

Summary

Hopefully this post demonstrates what kind of funny and not completely useless things can be done using the .NET CLI extension mechanism. I see this as yet another way of building the convenience tools for making your dev team’s life easier. The probing logic in dotnet.exe is flexible enough to allow hooking up tools or scripts from different places, so go ahead and come up with your own dotnet something-cool extension!