Console Commands with dependency injection (C#)
If you're not using C# 8's nullable references, you should be checking for null whenever you're supplied with input.


In the ConsoleCommandHandler constructor, throw ArgumentNullException if logger or commands is null.
In ConsoleCommandHandler.TryHandleCommandAsync, throw ArgumentNullException if input is null. Even though you're only ever passing non-empty strings from your entry point, this method is publicly visible which sends a signal that anybody could potentially use it differently in the future.


In ConsoleCommandHandler, you create a Dictionary that maps command names to ICommands. You would do this if you wanted constant-time lookup of commands from command names. But then you wrote commands.Any(command =&gt; command.Key == header) which will instead do a linear search over every key-value pair in the dictionary until it finds a matching key. This defeats the purpose of using a dictionary. Use TryGetValue to check-and-retrieve a value from a dictionary in one shot:

    if (commands.TryGetValue(header, out var command)
    {
        await command.RunAsync(input.Substring(spacePosition + 1).Split(' '));
    }
    else
    {
        logger.LogWarning("'{0}' is not recognized as an internal command", header);
    }


TryHandleCommandAsync really does two things -- it parses user input into a command + arguments, and then it finds/runs the associated command. It may be worth separating those concerns into methods. Then, if the format of those command strings ever changes then you'll know exactly where and what to change in the code.

Depending on how sophisticated your command line interface needs to be, consider a third-party library that does all the gross string parsing stuff for you. For example, do you need to support commands or arguments that contain spaces? Your implementation currently doesn't.