dotnet / command-line-api

Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api/wiki
MIT License
3.41k stars 382 forks source link

Question: Usage models with Service Collection (not using Generic Host) #1455

Open Simonl9l opened 3 years ago

Simonl9l commented 3 years ago

I have been using a different cli library, that has some great features, and is very usable, but lacks functionality such as tab completion (per dotnetsuggest`) However it does work well with using a raw ServiceCollection and provides the ability to inject those services into commands.

Im assessing the lift to migrate.

I see that command-line-api has Host Builder support (via .UseHost by scanning issues - open and closed) bit not specific documentation.

Are there any examples out there of just using the ServiceCollection with DI support (the other library uses reflection)?

One hopes in-time that (as other libraries) the DI integration is not tightly integrated such that one can use different services containers (albeit in the other library once would need to write a custom Convention vs the included DI one to achieve this) ?

jonsequitur commented 3 years ago

I've been distracted from looking at the Hosting package lately while we work on stabilizing the core library, but playing nicely with DI is definitely an important goal.

@fredrikhr Do we have a good sample for this, potentially including the proposed API changes in the open PRs (e.g. #1356)? I think a how-to document would be very helpful in assessing the overall shape of the API and the proposed changes.

Simonl9l commented 3 years ago

@jonsequitur @fredrikhr thanks for the speedy reply!

Per the item linked, I'm specifically looking at not using any HostBuilder, Generic or otherwise from dotnet Core. I don't need the weight of that and am managing lifecycles of my use cases.

Im not using hosted services and am not using configuration, or and logical startup.cs. On face value as it stand the .UseHost is not apparently helpful in this regard?...

However if you do have any examples that just work with a built service collection in perhaps a similar way to this, that would be great...

jonsequitur commented 3 years ago

You can do something like the following and delegate the call to your IServiceProvider:

 var commandLineBuilder =
                    new CommandLineBuilder(rootCommand)
                        .UseDefaults()
                        .UseMiddleware(
                            context =>
                            {
                                context.BindingContext.AddService(
                                           typeof(MyService),
                                           theSystemCommandLineServiceProvider => yourServiceProvider.GetService(typeof(MyService)));
                            });

This is a deliberately limited shim to provide your own services when binding and shouldn't be seen as a way to replace the System.CommandLine implementations (e.g. InvocationContext). This is why there's no catch-all for all possible types. No lifetime management, subtype mapping, open generic support, etc. is provided.

Simonl9l commented 3 years ago

@jonsequitur thanks...

So I can then "inject" that service into the ICommandHandler implementations constructor?

From a lifecycle perspective, I create, configure and build the service collection up front, and and Dispose it after the IinvokeAsync it seems.

I've not looked at coding anything as yet, just looking at feasibility. I'd suggest something a bit more generic that perhaps reflects the services from the service collection dynamically based on the command handlers constructor, might be a consideration of a future feature.

However any approach there may need to be configurable as your currently suggested approach is obviously DI container agnostic.

I'd only otherwise suggest that having a Generic function that would say allow you to do this (sorta) might be better syntactic sugar...to remove the typeof's:

ar commandLineBuilder =
                    new CommandLineBuilder(rootCommand)
                        .UseDefaults()
                        .UseMiddleware(
                            context =>
                            {
                                context.BindingContext.AddService<MyService>(
                                           theSystemCommandLineServiceProvider => yourServiceProvider.GetRequiredService<MyService>();
                            });

however I'm slightly lost as to the difference between theSystemCommandLineServiceProvider and yourServiceProvider.GetRequiredService in your specific example.

jonsequitur commented 3 years ago

You can inject into the handler method. Injection into command handlers isn't supported by default but it's not hard to do. #1356 has an example using the generic host but that's not required. Generally though we've leaned toward services and arguments being bound to a handler single method, which also happens to align to the minimal web API design.

I've not looked at coding anything as yet, just looking at feasibility. I'd suggest something a bit more generic that perhaps reflects the services from the service collection dynamically based on the command handlers constructor, might be a consideration of a future feature.

We're heading in this direction with our source generator support: https://github.com/dotnet/command-line-api/tree/main/src/System.CommandLine.Generator.

I'd only otherwise suggest that having a Generic function that would say allow you to do this (sorta) might be better syntactic sugar...to remove the typeof's:

There is a generically-typed overload.

Simonl9l commented 3 years ago

@jonsequitur thanks for the help, I think I can probably make things work!

TBH I'd rather re-structure code not to integrate services into the CLI commands but the other way around, and construct the needed service container on an as needed basis dependent on the command's needs.

However when is the package destined for a non pre-release version ?

Simonl9l commented 3 years ago

Actually, the docs need a lot of work...

Define the difference and capabilities for Command, Options, Arguments etc. would be a start...

Digging around the open/closed issues for examples based on others questions is is not really helpful.

jonsequitur commented 3 years ago

Did you happen on this and is it helpful at all? https://github.com/dotnet/command-line-api/blob/main/docs/Syntax-Concepts-and-Parser.md

Simonl9l commented 3 years ago

Helpful of course, but not an exhaustively complete API guide, that covers things like IFileInfo and extensions like .ExistingOnly and anything similar that once seems to only find searching the issues/questions.