Skip to content

Filling Option default values from IConfiguration using Host #1405

Open
@axtimhaus

Description

@axtimhaus

I want to fill the default values from some of my options from configuration stack. Since when using UseHost() the Host instance is built within the middleware, my Command classes have no access to the Host and it's configuration stack while defining options.

This is related to #1058 and #826.

I tried to extract this functionality into another middleware (calling RootCommand.Configure()), but the default values are determined before my middleware gets in action, so the value of option1 is still "abc" and not affected by settings.json.

static void Main(string[] args)
{
    var rootCommand = new RootCommand();

    var commandLineParser =
        new CommandLineBuilder(rootCommand)
            .UseHost(hostBuilder =>
                hostBuilder.ConfigureAppConfiguration(builder =>
                {
                    builder.SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("settings.json", true, true)
                        .AddEnvironmentVariables();
                }))
            .UseMiddleware(context => { rootCommand.Configure(context); })
            .UseDefaults()
            .Build();

    commandLineParser.Invoke(args);
}
public class RootCommand : System.CommandLine.RootCommand
{
      public RootCommand()
      {
          Add(Option1);
          Handler = CommandHandler.Create<string, IHost>(Handle);
      }
      
      public Option<string> Option1 { get; } = new("--option1", () => "abc", "first option");
      
      public void Configure(InvocationContext context)
      {
          Option1.SetDefaultValueFactory(() => context.GetHost().Services.GetRequiredService<IConfiguration>().GetValue<string>("DefaultOption1"));
      }
      
      public void Handle(string option1, IHost host)
      {
          Console.WriteLine(option1);
      }
}

A nice solution were to allow somehow DI in the constructor of my RootCommand.
For now the only solution I have is to bypass UseHost() and create a host myself, passing it to the RootCommand constructor and simply adding the Host to Commandlines DI system. Alternatively just saving a reference to the host in a field and ignoring Commandline totally from the view of the Host. But both lack of some functionality of UseHost() like config directives and InvocationLifetime.

static void Main(string[] args)
{
    var host = new HostBuilder()
        .ConfigureAppConfiguration(builder =>
        {
            builder.SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("settings.json", true, true)
                .AddEnvironmentVariables();
        })
        .Build();
    
    var rootCommand = new RootCommand(host);

    var commandLineParser =
        new CommandLineBuilder(rootCommand)
            .UseMiddleware(context => { context.BindingContext.AddService<IHost>(_ => host); })
            .UseDefaults()
            .Build();

    commandLineParser.Invoke(args);
}
public class RootCommand : System.CommandLine.RootCommand
{
    public RootCommand(IHost host)
    {
        Add(Option1);
        Option1.SetDefaultValueFactory(() => host.Services.GetRequiredService<IConfiguration>().GetValue<string>("DefaultOption1"));
        Handler = CommandHandler.Create<string, IHost>(Handle);
    }

    public Option<string> Option1 { get; } = new("--option1", () => "abc", "first option");

    public void Handle(string option1, IHost host)
    {
        Console.WriteLine(option1);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions