Open
Description
Align System.CommadLine.Hosting
model with the way ASP.NET Core uses Generic Host.
It should be possible to configure CommandLineBuilder like this:
public static IHostBuilder CreateHostBuilder(string[] args) =>
CommandLineHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
})
.ConfigureCommandLineDefaults(cmdHost =>
{
cmdHost.ConfigureServices(services =>
{
})
})
.ConfigureCommandLineBuilder(cmdBuilder => // CommandLineBuilder
{
cmdBuilder.UseReflectionAppModel(); // e.g. https://github.com/KathleenDollard/command-line-api-starfruit
});
});
Activity
shaggygi commentedon Jun 6, 2020
Would be nice to make it easier to use the Worker Service, as well. Referencing #556.
NikiforovAll commentedon Jun 6, 2020
@shaggygi I think this solution allows it, because ParseResult is added as Singleton, so you can consume it from Worker.
Currently, it is not documented though.
KathleenDollard commentedon Jun 9, 2020
You all will teach me a good bit on this, so let me start with what might be a simple question.
When generic host is in place, does it make sense to have the CommandLine Host on the stack, or (at least as an option) can we put the results in an instance (from StarFruit) available via DI ad then step out of the way.
(Question is based on current assumptions about how GenericHost works)
NikiforovAll commentedon Jun 10, 2020
@KathleenDollard
It is definitely useful to be able to build DI container based on parsing results.
Also, there is already a method to use
System.CommandLine.Binding.ModelBinder<T>
binder:Although, it introduces a cyclic dependency between
CommandLineHostBuilder
andCommandLineBuilder
because:InvocationContext
,IConsole
,IInvocationResult
,ParseResult
to be available during host configuration. (It is up to debate what services should be provided, but I mentioned services used in the current implementation.)IHost
service toinvocationContext.BindingContext
outside ofUseMiddleware
method. In order to trigger middleware, we need to triggercommandLineBuilder.Build().InvokeAsync
.The current implementation gets around this by making
IHost host = hostBuilder.Build()
a part of the middleware, soInvocationContext
is available from the middleware context.jonsequitur commentedon Jun 10, 2020
Can the patterns we use here be generalized to include
IWebHostBuilder
? The goals are the same it seems: #916.NikiforovAll commentedon Jun 10, 2020
Unfortunatelly,
IWebHostBuilder
doesn't implement IHostBuilder directly. It should be possible to migrate to generic host.jonsequitur commentedon Jun 10, 2020
To the extent that the patterns are similar, it would be good to have approaches and matching code for both, even if it means we need another project.
KathleenDollard commentedon Jun 10, 2020
Aarrgh. Seems like an understanding of why IWebHostBuilder and IHostBuilder differ would be a starting point.
I don't yet see why multiple projects help over other approaches.
jonsequitur commentedon Jun 10, 2020
They'll have different dependencies and you won't want the union of the two dependency graphs.
jonsequitur commentedon Jun 10, 2020
I think a more inverted model, and doing away with the concept of a
CommandLineHost
, might align well to the existing host models, which would give us something like:NikiforovAll commentedon Jun 10, 2020
@jonsequitur
ConfigureCommandLine
in the case ofWebHostBuilder:IWebHostBuilder
. How is it going to be consumed and what services should be added to DI?For configuration purposes next methods are used:
Please see: comment above
ConfigureCommandLine
that could be host-dependent. Mainly, at what point of timeparseResult.InvokeAsync()
is executed. What do you think, how should it be organized?In Add generic host builder pattern [WIP] #919, I've added
CommandLineExecutorService
that shuts downIHostBuilder
after command execution.CommandLineHost
might be a dubious concept to follow.It is just something similar to
WebHost.CreateDefaultBuilder
. A factory to createIHostBuilder
=Host.CreateDefaultBuilder(args)
+cmdBuilder.UseDefaults()
.I apologize for so many questions. It is quite an interesting bit of functionality to me.
jonsequitur commentedon Jun 10, 2020
re: 1. I think the use cases are the same for both generic host and web host, which is I want to use DI, logging, etc. in an app that also has a command line experience that provides help, completions, and so on. The application type is orthogonal to the desire to have a good CLI. As examples,
dotnet-interactive
anddotnet-try
are both .NET tools that have some modes that spin up a web server and others that do not, specified using command-line arguments.KathleenDollard commentedon Jun 21, 2020
Update after some exploration.
You can find the code I wrote on this at https://github.com/KathleenDollard/command-line-api-starfruit. It's the hosting projects in the main branch. Here are my updated thoughts:
Sorry for the long post.
niemyjski commentedon Sep 18, 2020
I've been struggling a bit with working on a way in a sample asp.net project to use the CommandLineBuilder / hosting project to dynamically create hosted services using IHost/IHostBuilder based on the parse result.
I'd see myself registering sub commands in
ConfigureCommandLine
and then getting access to the parse result inConfigureServices
or some kind of callback before the container is created to dynamically add my hosted services too. Has this been done before or considered?NikiforovAll commentedon Sep 18, 2020
@niemyjski I think this under consideration, please see #1025 (comment)
StephenCleary commentedon Nov 9, 2021
I have a different desired usage: I want to use the hosted services injected in the host to define which commands are available. In the bigger context, this is for a .NET Core worker app that normally runs as a Win32 service and performs scheduled jobs, and the command line interface is for manually executing one of those jobs.
Currently, I'm doing this by creating the host with the full command line (it ignores unknown arguments), and then I create a command line with dynamically added commands which directly invoke the appropriate hosted service. In order to allow dotnet host/app arguments (.NET command line configuration provider), I also have a catch-all
new Argument<string[]>("dotnet runtime options", "Configuration value overrides")
on my root command. Which is never used; it just is there to prevent System.CommandLine parse errors.The drawback to my current approach is that the error handling isn't great. The host attempts to parse any System.CommandLine arguments into configuration values, and the System.CommandLine ignores any unknown arguments to allow for host configuration. So typos are not obvious.
I tried creating a separate
RootCommand
which would allow specifying the dotnet arguments explicitly in a--dotnet
argument, then building the host, and then creating the "real"RootCommand
, but that had the drawback that short-circuited execution like help output would only understand the first root command (and not display help for the subcommands of the "real" root command).Anyway, it seems like there are two different and possibly conflicting desires in this thread:
Good luck! ;)