So you’ve created a really neat console app, but it’s growing and you need a way to keep it all neatly organized and preferrably with some Good Practices.
The guys at Entity Framework have thought about this and structured their console app really neatly. Today, we’ll take the ninja app we built previously and make it all look pretty and stuff.
I know what I’m doing, just gimme the damn framework!
We’ll start by breaking up our commands. A couple of requirements:
- Each Command should live in its own file/class
- Separate setting up options and arguments from the actual execution
- Let the main execution start at the root; Command classes shouldn’t do anything on their own
Every Command will have three methods:
- A static
Configure
method that sets up the Arguments, Options and nested Commands - A constructor to pass in properties (done in
OnExecute
, inConfigure
) - A
Run
method that actually executes the command
Like this:
// Commands/ICommand.cs
public interface and {
void Run();
}
// Commands/AttackCommand.cs
public class AttackCommand : ICommand {
public static void Configure(CommandLineApplication 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(() => {
(new AttackCommand(locationArgument.Value)).Run();
return 0;
});
}
private readonly string _location;
public AttackCommand(string location) {
_location = location;
}
public void Run() {
var location = _location != null
? _location
: "in a trash can";
Console.WriteLine("Ninja is hidden " + location);
}
}
Now we can clean up Main
quite a bit:
// Set up the app
var app = new CommandLineApplication();
app.Name = "ninja";
app.HelpOption("-?|-h|--help");
// Register commands
app.Command("hide", HideCommand.Configure);
app.Command("attack", AttackCommand.Configure);
app.OnExecute(() => {
(new CommandLineApplication(app)).Run();
return 0;
});
// Fire!
app.Execute(args);
It’s starting to look neat but we can do even better. First, since the app is just another command we can move it to its own class:
// Commands/RootCommand.cs
public class RootCommand : ICommand {
public static void Configure(CommandLineApplication app) {
app.Name = "ninja";
app.HelpOption("-?|-h|--help");
// Register commands
app.Command("hide", HideCommand.Configure);
app.Command("attack", AttackCommand.Configure);
app.OnExecute(() => {
(new RootCommand(app)).Run();
return 0;
});
}
private readonly CommandLineApplication _app;
public RootCommand(CommandLineApplication app) {
_app = app;
}
public void Run() {
_app.ShowHelp();
}
}
So now Main
is only three lines!
// Program.cs
var app = new CommandLineApplication();
RootCommand.Configure(app);
app.Execute(args);
One more feature: I’d like to have some global options that we can pass along and use in any child Command we’d like.
To do this, we’ll create a CommandLineOptions
class and basically move
everything there. And while we’re at it, store the Command to be executed as an
option. That fulfills the requirement of the Commands not doing anything by
themselves.
public class CommandLineOptions {
public static void Parse(string[] args) {
var options = new CommandLineOptions();
var app = new CommandLineApplication();
var isQuietOption = app.Option("--extra-quiet|-q",
"Instruct the ninja to do its best to be even more quiet",
CommandOptionType.NoValue);
RootCommand.Configure(app, options);
options.IsQuiet = isQuietOption.HasValue();
var result = app.Execute(args);
if (result != 0 || options.Command == null) {
Console.Error.WriteLine("Fail");
return null;
}
return options;
}
public CommandLineApplication Command;
public bool IsQuiet;
}
We’ll need to make three changes in every Command now:
- Change
OnExecute
so it changesoptions.Command
to the command itself - Update
Configure
to accept the new argument - Update any child
Command
calls in order ro be compatible with the newConfigure
API
For example, in RootCommand:
// Commands/RootCommand.cs
public class RootCommand : ICommand {
public static void Configure(CommandLineApplication app, CommandLineOptions options) {
app.Name = "ninja";
app.HelpOption("-?|-h|--help");
// Changed here
app.Command("hide", c => HideCommand.Configure(c, options));
app.Command("attack", c => AttackCommand.Configure(c, options));
app.OnExecute(() => {
options.Command = new RootCommand(app);
return 0;
});
}
private readonly CommandLineApplication _app;
public RootCommand(CommandLineApplication app) {
_app = app;
}
public void Run() {
_app.ShowHelp();
}
}
You get the idea.
But wait, that’s not all!
My Gift to You: ~dotnet-core-neat-console-starter~
There’s some boilerplating going on here. So I made a little starter kit for you to kickstart your adventures creating console applications!
It’s up on GitHub so go ahead, use it and make more beautiful console apps, on the outside and on the inside!
awesome thank you!