Skip to content

Instantly share code, notes, and snippets.

@iamarcel
Last active November 28, 2023 10:41
Show Gist options
  • Save iamarcel/8047384bfbe9941e52817cf14a79dc34 to your computer and use it in GitHub Desktop.
Save iamarcel/8047384bfbe9941e52817cf14a79dc34 to your computer and use it in GitHub Desktop.
Creating Neat .NET Core Command Line Apps

Creating Neat .NET Core Command Line Apps

You can now read this on my (pretty) website! Check it out here.

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";
app.HelpOption("-?|-h|--help");

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;
    });

app.Execute(args);

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.";
        command.HelpOption("-?|-h|--help");

        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]

Options:
  -?|-h|--help  Show help information

Commands:
  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]

Arguments:
  [location]  Where the ninja should hide.

Options:
  -?|-h|--help  Show help information

Neat!

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!";
        command.HelpOption("-?|-h|--help");

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

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

        command.OnExecute(() =>
            {
                var exclusions = excludeOption.Values;
                var attacking = (new List<string>
                {
                    "dragons",
                    "badguys",
                    "civilians",
                    "animals"
                }).Where(x => !exclusions.Contains(x));

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

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

                Console.WriteLine();

                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

Neat!

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

@NightOwl888
Copy link

NightOwl888 commented Jul 2, 2017

Quote from aspnet Common GitHub repo:

Stop producing Microsoft.Extensions.CommandLineUtils

This library was only intended for usage with ASP.NET Core's tools, and not a general purpose library. After receiving bugs and questions about this, we realized that we didn't really intend to produce and support a command-line parsing library.

We will still leave the 1.0.0 and 1.1.0 versions of this library on NuGet.org, but the current plan is to stop producing new versions of this package.

The source code is still available for internal use via Microsoft.Extensions.CommandLineUtils.Sources, however we will not be publishing this library again as a general purpose library.

Unfortunately, as nice as this seems to work, it is no longer supported. Strange, since it was less than a year ago when it appeared on MSDN magazine.

It would be nice if someone would take the initiative and create a new project to extend this to get rid of the rest of the boilerplate garbage that everyone has to deal with when writing a command line util. Specifically, it would be nice if once you have all of the command options configured that you could plugin a "Markdown formatter" and run a command to generate all of the documentation in markdown (or another) format.

It would also be far better if this could be loosely coupled, support dependency injection, parameter validation (parameter x cannot be used in conjunction with parameter y, etc.), and localization.

@D-Bullock
Copy link

Adding support for .MVC's style of dependency injection is actually really trivial
Here's an example of adding logging

static void Main(string[] args) {
	//setup our DI
	var serviceProvider = new ServiceCollection()
		AddLogging()
              .	.BuildServiceProvider();

	// Consume logger
	var logger = serviceProvider.GetService<ILoggerFactory>()
		.CreateLogger<Program>();
	logger.LogDebug("Starting application");

To have things injected into constructors as MVC does, just add the object to the service provider. Eg
.AddScoped<RunTaskWithDependencies>()
You could even use reflection if you felt adding each item is too verbose

@Daniel15
Copy link

@rychlym
Copy link

rychlym commented Dec 13, 2017

Hi, all looks very nice, but is there any code example of a nested command?

@rychlym
Copy link

rychlym commented Dec 13, 2017

@rychlym I can perhaps answer it my self,
I was curious e.g. ninja weapon list - I tried playing with it a little (using also your nice starter)
It end up with CommandParsigException: "Unrecognized command or argument 'list'" ?? - looks like it can't recognize the nested command....
After a wile the error message actually lead me to check the nested "list" command is really "registered" in the weapon and that was the case... :)

    public static class WeaponCommandConfiguration
    {
        public static void Configure(CommandLineApplication command, CommandLineOptions options)
        {
            command.Description = "ninja weapon";
            
            // originally forgotten !!!
           command.Command("list", c => ListCommandConfiguration.Configure(c, options));

            command.HelpOption(RootCommandConfiguration.HelpOption);
            command.OnExecute(() =>
            {
                options.Command = new WeaponCommand(options);
                return 0;
            });
        }
    }

@bilal-fazlani
Copy link

bilal-fazlani commented Dec 14, 2017

I have build a library on top of Microsoft.Extensions.CommandLineUtils
https://github.com/bilal-fazlani/CommandDotNet and published it on nuget https://www.nuget.org/packages/CommandDotNet

Will try to improve this further by adding validations, localisations, etc

@natemcmaster
Copy link

FYI my community fork of CommandLineUtils includes validation along with other enhancements, like the ability to use C# attributes. I'd welcome further PRs to add things like localization.

https://github.com/natemcmaster/CommandLineUtils

@saviour123
Copy link

Hi, I am on linux with dotnet core, how can i get the boiler to work?

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