dotnet / dotnet/command-line-api

System.Commandline DependencyInjection could be much better if the underlying BindingContext was easier to extend.

Open
#1,874 0 comments 0 reactions 0 assignees View on GitHub
Area-Hosting and DI
Dominant language
C#
Stars
3.7k
Forks
428
PR merge metrics
No merged PRs in 30d

Description

I'll share some code and describe my issue:

return await new ConsoleApplicationBuilder()
.UseServiceCollection(new ServiceCollection())
.GetConfiguration(() =>
{
var builder = new ConfigurationBuilder()
.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
.AddJsonFile("appSettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appSettings.Development.json", optional: true, reloadOnChange: true);

var configuration = builder.Build();
return configuration;
})
.ConfigureServices((configuration, services) =>
{
services
.AddCommandHierarchy(builder =>
{
builder
.IsParentOf()
.IsParentOf()
.IsParentOf();
})
.AddLogging(configure =>
{
configure
.AddConsole(options => {
options.FormatterName = "nonamespace";
}).AddNoNamespaceConsoleFormatter(options =>
{
options.IncludeNamespace = false;
});
});

Bootstrap.Configure(configuration, services);
})
.ConfigureCommandLine(context =>
{
var applicationRootCommand = context.ServiceProvider.GetRequiredService().GetCommand();
var commandline = new CommandLineBuilder(applicationRootCommand)
.UseHost(Host.CreateDefaultBuilder)
.UseRuntimeLogLevel(() => GlobalParameters.LogLevel,
context => context.GetRequiredService().Services.GetRequiredService>(),
"CurseDownload")
.UseDefaults();

return commandline;
}).ExecuteAsync(args);

I want to to utilize a couple things with my application:
- config files
- runtime loglevel switch (to control verbosity of entire execution) (implemented as `CommandLineBuilder`middleware)
- constructor dependency injection in commands

unfortunately there are a couple struggles when doing this:
- if i want commands with constuctor injection i need to construct commands prior to building the `CommandLineBuilder` using an external ServiceCollection.
- If i want external ServiceConfiguration i cannot use the built in configuration loading from `System.CommandLine.Hosting`, but have to do it myself.
- If i add my service to `CommandLineBuilder`.ConfigureServices i depend on `System.CommandLine.Hosting` to go through `GetRequiredService.Services.GetRequiredService` which feels really awkward depency wise (when developing packages to help with console applications. Suddenly you require the dependency on hosting, even though all you need is the `IHost` to get the service.
- If i add services to an external serviceProvider i will still be unable to access my service through the `BindingContext` unless i add a dependency to `System.CommandLine.Hosting` and repeat service registration.

Unfortunately DI scenarios still feel rather awkward from an extensibility point of view, and the BindingBase solution was something i absolutely did not want to bother with.

Ideally there should be an API to extend the pool of services to retrieve services from a binding context (without depending on `System.CommandLine.Hosting`)

Like adding a method like `AddServiceProvider` to `CommandLineBuilder` would solve all my issues if that lead to the binding context delegating to a given additional serviceProvider as fallback.

Would that perhaps be an option?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.