Create a gist now

Instantly share code, notes, and snippets.

Creating Neat .NET Core Command Line Apps

Creating Neat .NET Core Command Line Apps

Every reason to get more HackerPoints™ is a good one, so today we're going to write a neat command line app in .NET Core! The Common library has a really cool package Microsoft.Extensions.CommandlineUtils to help us parse command line arguments and structure our app, but sadly it's undocumented.

No more! In this guide, we'll explore the package and write a really neat console app. We'll get good practices, a help system and argument parsing for free. Oh, it also involves ninjas. Insta-win.

The code is all up on GitHub so you can look at it there, clone the project and play around for yourself. I've also created a little framework you can use for your own projects, as a starting point for creating neatly structured neat console apps.

Ready? Cool, me too.

Introduction to the package

Basically, the CommandLineUtils package parses your arguments and helps you structure the different parts of your application. It makes it very easy to allow your app to accept all kinds of parameters, according to the conventions you probably know and love.

What can we do?

Let's assume we're creating an app called ninja. I'll list a couple of possible commands while I introduce you to the terminology used in the package. Whenever a word like Command, Option or Argument is capitalized, that means I'm talking about its specific definition regarding to the CommandLineUtils package.

  • ninja hide (simple Command)
  • ninja weapon list (nested Command)
  • ninja attack --exclude civilians (simple Command with Option)
  • ninja weapon add {weaponType} (nested Command with Argument)

Of course, we can start mixing and matching to make really powerful types of commands. But once you've got the basics, everything else is easy-peasy.

Structure of your application

So if we look at what we have above, the structure of our application will look like this:

  • Root Command (ninja)
    • Argument(s) (positional parameters, like weaponType)
    • Option(s) (parameters that have a name, specified like --exclude civilians)
    • More commands like hide and weapon, with their own Arguments and Options

Getting help

Help is essential to any application, especially if it's a command line application. The package is super-helpful here: you just specify some explanation with yout Commands, Arguments and Options, and it'll spit out neatly formatted and organized help when your user needs it.

Creating/Becoming a Console Ninja

Let's dive in and start doing some coding.

The Application

We create our application by creating a CommandLineApplication.

var app = new CommandLineApplication();
app.Name = "ninja";

The second line is the name of our application. It'll be used in the help output.

The third line specifies which options trigger the help output. The syntax of the string is self-explanatory: use either "-?", "-h" or "–help" as a parameter for the app to open up the help.

To make it actually do something, we define a function in onExecute(Func<int>). Note that it expects an int as return value (the error code). To run the actual CommandLineApplication, call app.Execute(string[] args). You can just pass along the arguments you got from the Main entry point of your console app.

app.OnExecute(() => {
        Console.WriteLine("Hello World!");
        return 0;


Now, "Hello World!" will be printed when you run the app. If you specify, for example --help, an (empty) help thing will show up. Neat uh?

A Command

This is how we create a Command:

app.Command("hide", (command) =>
        command.Description = "Instruct the ninja to hide in a specific location.";

        var locationArgument = command.Argument("[location]",
                                   "Where the ninja should hide.");

        command.OnExecute(() =>
                var location = locationArgument.HasValue()
              ? locationArgument.Value()
              : "under a turtle";
                Console.WriteLine("Ninja is hidden " + location);

app.Command takes a command name and a configuration function as arguments. Note that the type of command in the configuration function is CommandLineApplication. Yup, that's how easy it is to start nesting commands.

The Description will be used in the help output and the call to HelpOption is required in order to enable those arguments.

Let's see what happens now:

$ ninja hide
Ninja is hidden under a turtle

$ ninja hide "on top of a street lamp"
Ninja is hidden on top of a street lamp

$ ninja -?
Usage: ninja [options] [command]

  -?|-h|--help  Show help information

  hide  Instruct the ninja to hide in a specific location.

Use "ninja [command] --help" for more information about a command.

$ ninja hide --help
Usage: ninja hide [arguments] [options]

  [location]  Where the ninja should hide.

  -?|-h|--help  Show help information


Arguments and Options

You saw that with the command.Argument function we, well, created an argument! Just pass on the name and description (again, for the help output) and CommandLineUtils will do its magic. Make sure you store that variable somewhere; when executing you'll need it to get the value of your arguments.

Our ninja needs more powers. Powers with Options.

app.Command("attack", (command) =>
        command.Description = "Instruct the ninja to go and attack!";

        var excludeOption = command.Option("-e|--exclude <exclusions>",
                                "Things to exclude while attacking.",

        var screamOption = command.Option("-s|--scream",
                               "Scream while attacking",

        command.OnExecute(() =>
                var exclusions = excludeOption.Values;
                var attacking = (new List<string>
                }).Where(x => !exclusions.Contains(x));

                Console.Write("Ninja is attacking " + string.Join(", ", attacking));

                if (screamOption.HasValue())
                    Console.Write(" while screaming");


                return 0;


Recognize the syntax? The first argument in command.Option defines the template, the second is a description and the third defines the type of argument. This can be SingleValue, MultipleValue or NoValue (that's for toggles, use myOption.HasValue() to check if it has been set).

The user can specify a value for options using either a space or an equals sign between the option name and its value: --exclude civilians -e=animals works perfectly (yes, also in a single command since the option type is MultipleValue).

Note that an Option is Option-al so be sure handle the case where it's not given. Also, only the first word is parsed as part of the option. If you type -e civilians animals, the parser wil throw an exception.

The following commands will all work as you'd expect:

$ ninja attack -?
$ ninja attack --scream
$ ninja attack -e dragons -s --exclude=animals


That's it! (for now)

Well done! You got the basics down and are now ready to create really cool console applications. All hail the command line! Now go out and impress your friends because you've accumulated quite some HackerPoints™ now. (just don't forget change your color scheme so there's green text on a black background)

But soon you'll start creating really big, useful impressive console apps and it could get a bit messy to keep this all in your Main function. So what's the best way to neatly organize our neat app? I got ya covered. The guys at Entity Framework did a really good job in their tooling; I analyzed their sources with my lazer eyes and wrote you a simple guide (and a little framework!).

Read on about structuring neat console apps neatly.

Shoutout to 4D Vision for letting me write this while working as a student for them! They build custom software for complex processes of companies and organizations; not to mention they're really cool people. This article is also part of their internal documentation now.

Thanks for reading! If you wanna keep up with my work, follow me on twitter because that's where the cool guys are at, right?

Further Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment