-
Notifications
You must be signed in to change notification settings - Fork 403
Simplified usage of BindingContext resolved command handlers #1358
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
d556325
2f2796b
b811921
b050e7a
5db4df8
765d3a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.CommandLine.Binding; | ||
using System.CommandLine.Builder; | ||
using System.CommandLine.Invocation; | ||
using System.CommandLine.IO; | ||
using System.CommandLine.Parsing; | ||
|
@@ -322,7 +323,6 @@ public async Task Method_parameters_of_type_InvocationContext_receive_the_curren | |
boundContext.ParseResult.ValueForOption(option).Should().Be(123); | ||
} | ||
|
||
|
||
private class ExecuteTestClass | ||
{ | ||
public string boundName = default; | ||
|
@@ -507,5 +507,101 @@ public class OverridenVirtualTestCommandHandler : VirtualTestCommandHandler | |
public override Task<int> InvokeAsync(InvocationContext context) | ||
=> Task.FromResult(41); | ||
} | ||
|
||
[Fact] | ||
public static void FromBindingContext_forwards_invocation_to_bound_handler_type() | ||
{ | ||
var command = new RootCommand | ||
{ | ||
Handler = CommandHandler.FromBindingContext<BindingContextResolvedCommandHandler>() | ||
}; | ||
command.Handler.Should().NotBeOfType<BindingContextResolvedCommandHandler>(); | ||
var parser = new CommandLineBuilder(command) | ||
.ConfigureBindingContext(context => context.AddService<BindingContextResolvedCommandHandler>()) | ||
.Build(); | ||
|
||
var console = new TestConsole(); | ||
parser.Invoke(Array.Empty<string>(), console); | ||
console.Out.ToString().Should().Be(typeof(BindingContextResolvedCommandHandler).FullName); | ||
} | ||
|
||
[Fact] | ||
public static void Subsequent_call_to_configure_overrides_service_registration() | ||
{ | ||
BindingContextCommandHandlerAction action = (handler, Console) => | ||
{ | ||
handler.Should().BeOfType<BindingContextCommandHandler2>(); | ||
fredrikhr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
var parser = new CommandLineBuilder(new RootCommand | ||
{ | ||
Handler = CommandHandler.FromBindingContext<IBindingContextCommandHandlerInterface>() | ||
}) | ||
.ConfigureBindingContext(context => context.AddService(_ => action)) | ||
.ConfigureBindingContext(context => context.AddService<IBindingContextCommandHandlerInterface, BindingContextCommandHandler1>()) | ||
.ConfigureBindingContext(context => context.AddService<IBindingContextCommandHandlerInterface, BindingContextCommandHandler2>()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One concern I have with this approach is that all handler registrations for all subcommands will happen regardless of which subcommand was used, when only one subcommand is ever used. This can be a bit wasteful and is part of why we've generally steered away from the common pattern for longer-lived apps where DI is configured up front but the cost is amortized over a longer lifetime. This is very often not the case for command-line apps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jonsequitur In this case (using the lazily bound registration) only the registration is added, no instance of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Understood. At invocation time though, all of these registrations happen on the |
||
.Build(); | ||
parser.Invoke(Array.Empty<string>(), new TestConsole()); | ||
} | ||
|
||
public class BindingContextResolvedCommandHandler : ICommandHandler | ||
{ | ||
public BindingContextResolvedCommandHandler(IConsole console) | ||
{ | ||
Console = console; | ||
} | ||
|
||
public IConsole Console { get; } | ||
|
||
public Task<int> InvokeAsync(InvocationContext context) | ||
{ | ||
Console.Out.Write(GetType().FullName); | ||
return Task.FromResult(0); | ||
} | ||
} | ||
|
||
public interface IBindingContextCommandHandlerInterface : ICommandHandler | ||
{ | ||
} | ||
|
||
public class BindingContextCommandHandler1 : IBindingContextCommandHandlerInterface | ||
{ | ||
private readonly BindingContextCommandHandlerAction invokeAction; | ||
|
||
public BindingContextCommandHandler1(IConsole console, | ||
BindingContextCommandHandlerAction invokeAction) | ||
{ | ||
Console = console; | ||
this.invokeAction = invokeAction; | ||
} | ||
|
||
public IConsole Console { get; } | ||
public Task<int> InvokeAsync(InvocationContext context) | ||
{ | ||
invokeAction(this, Console); | ||
return Task.FromResult(0); | ||
} | ||
} | ||
|
||
public class BindingContextCommandHandler2 : IBindingContextCommandHandlerInterface | ||
{ | ||
private readonly BindingContextCommandHandlerAction invokeAction; | ||
|
||
public BindingContextCommandHandler2(IConsole console, | ||
BindingContextCommandHandlerAction invokeAction) | ||
{ | ||
Console = console; | ||
this.invokeAction = invokeAction; | ||
} | ||
|
||
public IConsole Console { get; } | ||
|
||
public Task<int> InvokeAsync(InvocationContext context) | ||
{ | ||
invokeAction(this, Console); | ||
return Task.FromResult(0); | ||
} | ||
} | ||
|
||
public delegate void BindingContextCommandHandlerAction(ICommandHandler handler, IConsole console); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -282,6 +282,10 @@ public static ICommandHandler Create<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T1 | |
Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, Task<int>> action) => | ||
HandlerDescriptor.FromDelegate(action).GetCommandHandler(); | ||
|
||
public static ICommandHandler FromBindingContext<THandler>() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be helpful for the naming to reflect that the actual handler will be created during binding. One not great idea to open up discussion: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
where THandler : ICommandHandler => | ||
Create((InvocationContext context, THandler handler) => handler.InvokeAsync(context)); | ||
|
||
internal static async Task<int> GetExitCodeAsync(object value, InvocationContext context) | ||
{ | ||
switch (value) | ||
|
Uh oh!
There was an error while loading. Please reload this page.