Skip to content
On this page

TIP

It is perfectly legal to use the same input class across multiple commands

Oakton commands consist of two parts:

  1. A concrete input class that holds all the argument and flag data inputs
  2. A concrete class that inherits from OaktonCommand<T> or OaktonAsyncCommand<T> where the "T" is the input class in the first bullet point

Looking again at the NameCommand from the getting started topic:

cs
[Description("Print somebody's name")]
public class NameCommand : OaktonCommand<NameInput>
{
    public NameCommand()
    {
        // The usage pattern definition here is completely
        // optional
        Usage("Default Color").Arguments(x => x.Name);
        Usage("Print name with specified color").Arguments(x => x.Name, x => x.Color);
    }

    public override bool Execute(NameInput input)
    {
        var text = input.Name;
        if (!string.IsNullOrEmpty(input.TitleFlag))
        {
            text = input.TitleFlag + " " + text;
        }

        AnsiConsole.Write($"[{input.Color}]{text}[/]");

        // Just telling the OS that the command
        // finished up okay
        return true;
    }
}

snippet source | anchor

There's only a couple things to note about a command class:

  • The only entry point is the Execute() method
  • The boolean return from the Execute() method simply denotes whether or not the command completed successfully. This will be important for any kind of console application that you'll want to use in automated builds to prevent false positive results
  • The Usages syntax in the constructor is explained in a section below
  • The [Description] attribute on the class is strictly for the purpose of providing help text and is not mandatory

If you want to make use of async/await, you can inherit from OaktonAsyncCommand<T> instead. The only difference is signature of the Execute() method:

cs
public class DoNameThingsCommand : OaktonAsyncCommand<NameInput>
{
    public override async Task<bool> Execute(NameInput input)
    {
        AnsiConsole.Write($"[{input.Color}]Starting...[/]");
        await Task.Delay(TimeSpan.FromSeconds(3));

        AnsiConsole.Write($"[{input.Color}]Done! Hello {input.Name}[/]");
        return true;
    }
}

snippet source | anchor

Argument Usages

As shown in the NameCommand in the section above, you can specify the valid combinations of arguments and the order in which they should follow in the command line usage by modifying the Usages property in the constructor function of a command:

cs
public OtherNameCommand()
{
    // You can specify multiple usages
    Usage("describe what is different about this usage")
        // Specify which arguments are part of this usage
        // and in what order they should be expressed
        // by the user
        .Arguments(x => x.Name, x => x.Color)

        // Optionally, you can provide a white list of valid
        // flags in this usage
        .ValidFlags(x => x.TitleFlag);
}

snippet source | anchor

If you do not explicitly specify usages, Oakton will assume that all arguments are mandatory and in the order in which they appear within the input class.

Specifying Command Names

By default, Oakton determines the command name for a command class by taking the class name, removing the "Command" suffix, and then using the all lower case remainder of the string. For an example, a command class called CleanCommand would have the command name clean. To override that behavior, you can use the Alias property on Oakton's [Description] attribute as shown below:

cs
[Description("Say my name differently", Name = "different-name")]
public class AliasedCommand : OaktonCommand<NameInput>

snippet source | anchor

Asynchronous Commands

Oakton also supports the ability to write asynchronous commands that take advantage of the ability to use asynchronous Program.Main() method signatures in recent versions of .Net.

To write an asynchronous command, use the OaktonAsyncCommand<T> type as your base class for your command as shown below:

cs
[Description("Say my name", Name = "say-async-name")]
public class AsyncSayNameCommand : OaktonAsyncCommand<SayName>
{
    public AsyncSayNameCommand()
    {
        Usage("Capture the users name").Arguments(x => x.FirstName, x => x.LastName);
    }

    public override async Task<bool> Execute(SayName input)
    {
        await Console.Out.WriteLineAsync($"{input.FirstName} {input.LastName}");

        return true;
    }
}

snippet source | anchor

Likewise, to execute asynchronously from Program.Main(), there are new overloads on CommandExecutor for async:

cs
static Task<int> Main(string[] args)
{
    var executor = CommandExecutor.For(_ =>
    {
        // Find and apply all command classes discovered
        // in this assembly
        _.RegisterCommands(typeof(Program).GetTypeInfo().Assembly);
    });

    return executor.ExecuteAsync(args);
}

snippet source | anchor