command-line-api icon indicating copy to clipboard operation
command-line-api copied to clipboard

All commands are always optional when using hosting

Open altafard opened this issue 9 months ago • 1 comments

In the example bellow I was expecting that running cli without specifying a subcommand would fail with the error "Required command was not provided." as it happens without using the host

var root = new Command("cli")
{
    Subcommands = { new Command("cmd") }
};

var configuration = new CommandLineConfiguration(root).UseHost(
   _ => Host.CreateDefaultBuilder(),
   cfg => cfg.UseInvocationLifetime());

await configuration.InvokeAsync(args);

However, it works fine and no errors pop up. It seems that UseHost calls HostingAction.SetHandlers extensions on the root command and it sets actions to all commands in the chain. As a result, this validation never sets the error.

https://github.com/dotnet/command-line-api/blob/b7f0d1c4f0da129f7a115e32b21f0b773e60805b/src/System.CommandLine/Parsing/CommandResult.cs#L46-L55

Was it made intentionally? As a workaround I can use Validators and check that there are tokens provided in the arguments list, but it would be nice to keep the same behaviour as it works without using a host.

altafard avatar Mar 07 '25 04:03 altafard

I worked around this by throwing an exception when there are unmatched tokens passed to the hostBuilderFactory.

var config = new CommandLineConfiguration(rootCommand);
config.UseHost(
    hostBuilderFactory: unmatchedArgs =>
        {
            if (unmatchedArgs.Length > 0)
            {
                var helpBuilder = new HelpBuilder();
                var stringWriter = new StringWriter();
                helpBuilder.Write(rootCommand, stringWriter);
                Console.WriteLine(stringWriter.ToString());
                throw new InvalidOperationException($"Unmatched tokens: {string.Join(" ", unmatchedArgs)}");
            }

            return Host.CreateDefaultBuilder();
        },
    configureHost: host => host.ConfigureServices(services =>
        {
            // register your services
        })
    );

return await config.InvokeAsync(args);

This is with version 2.0.0-beta5.25210.1

https://github.com/lbussell/dotnet-docker/commit/961459765997a1f5d570cac45623a495ea57f77e

lbussell avatar Apr 29 '25 16:04 lbussell