Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HostedCommandHandler helper class #1356

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

fredrikhr
Copy link
Contributor

Following the discussion in #1344 we have detected flaws in the implementation of the UseCommandHandler extension methods.

This PR adds a new helper class HostedCommandHandler which works similarly to CommandHandler by creating instances of ICommandHandler that forward the call to InvokeAsync(InvocationContext) to a handler instance constructed by the .NET Generic Host DI-system.

UseCommandHandler is removed by this PR.

Test have also been updated to use the HostedCommandHandler instead of UseCommandHandler.

The test have also been adjusted to better reflect design principles using the .NET Generic Host. In particular this involves having options-classes that hold the values bound from command-line. The handlers get an injected IOptions<T> instance from which they read bound command-line argument values.

/cc @leonardochaia, @Gronex

A minimalistic example of using DI-bound handlers would look like this:

public static class Program
{
    public static Task<int> Main(string[] args)
    {
        var rootCommand = new RootCommand
        {
            Handler = HostedCommandHandler.CreateFromHost<MyCommandHandler>()
        };
        var parser = new CommandLineBuilder(rootCommand)
            .UseHost(CreateHostBuilder)
            .Build();
        return parser.InvokeAsync(args);
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices(services =>
            {
                services.AddTransient<MyCommandHandler>();
                services.AddOptions<MyAppOptions>()
                    .BindCommandLine()
                    ;
            })
            ;
}

class MyCommandHandler : ICommandHandler
{
    private readonly MyAppOptions options;

    public MyCommandHandler(IOptions<MyAppOptions> options) : base()
    {
        this.options = options.Value;
    }

    public Task<int> InvokeAsync(InvocationContext context)
    {
        /* Do something here */
    }
}

class MyAppOptions { /* Add properties here which match Options/Arguments from command line */ }

closes #1344

@fredrikhr fredrikhr changed the title Hosting di inject commandhandler Add HostedCommandHandler helper class Jul 19, 2021
@fredrikhr
Copy link
Contributor Author

Currently this PR only constructs the Handler from .NET Generic Host DI.

We could extend the logic to do the construction in the following order:

  1. Attempt host.Services.GetService<THandler>()
    1.1. If successful, get or create a ModelBinder<THandler> and use UpdateInstance on the constructed handler instance.
    1.2. Return instance
  2. Get or create ModelBinder<THandler>
    2.1. Call CreateInstance to create THandler from BindingContext
    2.2. Return instance

This would allow the handler to be constructed both from the hosted DI-system as well as from Added services from the command-line binding context. The additional call to UpdateInstance would also allow the ModelBinder to bind command-line arguments directly to the handler instance instead of requiring an Options type, this would simply be a shortcut to the normal .NET Generic Host way of doing things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When using UseCommandHandler with subcomand, Help overrides RootCommand
1 participant