Skip to content

Add Command

Anrijs Vitolins edited this page Jan 28, 2022 · 2 revisions

Overall idea behind commands read on common wiki page.

Interface implementation

Command should be implemented in its own class and derive from IConsoleOperation interface, which demands to specify operation name and short descriptive help text, implement IsReady property (return true, if command is ready for launch) and all necessary command work in DoWork() method.

The simplest possible "Hello World" command class would look like this:

public class Simplest : IConsoleOperation
{
    public string OperationName => "simple";

    public string HelpText => "Hello World command.";

    public async Task<int> DoWork()
    {
        Consolix.WriteLine("Hello {0}", "Consolix", ConsoleColor.DarkYellow, ConsoleColor.Yellow);
        return Task.FromResult(0);
    }

    public bool IsReady => true;
}

OperationName

It is actual command name to be used as parameter to console application. Operation Handler finds it and uses for execution based on this property value.

HelpText

Is short descriptive help text for this command which gets displayed to user when help text is shown.

IsReady

This property must evaluate to true if command has all necessary things to do its work. For simplest cases you can just return true. For more elaborate checks you can implement its getter to validate all necessary things and only when everything checks return true (otherwise - false).

DoWork()

Method to perform desired work when command is executed. Should return 0 if work is completed successfully, otherwise any other number. This is returned by console application to invoker and can be checked by it. Consolix actually does not care what is returned. It is just best practice.

You can use command constructor to inject any necessary dependencies for its work (and check them in IsReady property.)

Making command(s) available

After you created a command class, you have to wire it up with console application dependency injection container.

Together with command you must register also ConsoleOperationHandler which does the orchestration of launching one(s).

In Program.cs Main method add container setup method and specify lifetime.

IHost? host = Host.CreateDefaultBuilder(args)
    .ConfigureLogging(logging => logging.ClearProviders())
    .UseConsoleLifetime()
    .ConfigureServices(SetupContainer)
    .Build();
    
// Container setup method (can be in same `Program.cs` file)
private static void SetupContainer(HostBuilderContext context, IServiceCollection services)
{
    // These are commands setup
    services.AddTransient<IConsoleOperation, MyCommandOne>();
    services.AddTransient<IConsoleOperation, MyCommandTwo>();

    // Also need this for DI to work
    services.AddTransient<ConsoleOperationHandler>();
}

Then use Operation Handler to prepare command(s) and launch chosen one right after host buildup in Program.cs:

try
{
    // Get the handler and make it prepare commands
    var consoleOperationHandler = host.Services.GetRequiredService<ConsoleOperationHandler>();
    consoleOperationHandler.PrepareOperation(args);
    
    // This is necessary to handle explicit `--help` argument from command line
    if (args.Contains("--h") || args.Contains("--help"))
    {
        consoleOperationHandler.OutputHelp(
            typeof(Program).Assembly.GetName().Name,
            "Here goes some general description of console application");
        return 0;
    }

    // After PrepareOperation handler should know which command to use.
    // Additionally we ask that command whether it is ready...
    if (consoleOperationHandler.SelectedOperation is { IsReady: true })
    {
        // Here operation is called to do its work.
        return await consoleOperationHandler.SelectedOperation.DoWork();
    }

    // Fallback to displaying Help when something is not specified or ready.
    consoleOperationHandler.OutputHelp(
        typeof(Program).Assembly.GetName().Name,
        "Here goes some general description of console application");
    return -1;
}
catch (Exception ex)
{
    Consolix.WriteLine(ex.Message, ConsoleColor.Red);
    return -1;
}