4

I am working on a POC for a console application and I am struggling to retrieve the command line values from the configuration after using AddCommandLine in the set up.

csproj

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

Program class

public static class Program
    {
        public static async Task Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .WriteTo.RollingFile("Logs//log.txt")
            .CreateLogger();

            await CreateHostBuilder(args)
                .Build()
                .RunAsync();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config.AddJsonFile("settings.json", true, true);
                    config.AddCommandLine(args);
                })
                .ConfigureServices((hostcontext, services) =>
                {
                    services.AddHostedService<ConsoleApp>();
                });
    }

ConsoleApp class

 public class ConsoleApp : IHostedService
    {
        private readonly IConfiguration config;
        private readonly ILogger<ConsoleApp> log;

        public ConsoleApp(IConfiguration configuration, ILogger<ConsoleApp> logger)
        {
            config = configuration;
            log = logger;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            var t = config.GetSection("Args");
            Parser.Default.ParseArguments<DeleteOptions>(t)
                .WithParsed<DeleteOptions>()
                .WithNotParsed();

            foreach (var c in config.AsEnumerable())
            {
                log.LogInformation($"{c.Key, -15}:{c.Value}");
            }


            log.LogInformation($"Completing Start Task");
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            log.LogInformation($"Complete End Task");

            return Task.CompletedTask;
        }
    }

The Parser section before the foreach loop does not compile and the output from the loop does not print out any of the arguments I have added.

I am aware of the general advice that var someValue = Configuration.GetValue<int>("MySetting:SomeValue"); where the argument is --MySetting=SomeValue is the recommended way to retrieve cmd line values.

The values I am using as parameters are delete -e CI -t depchpolestar -l de-DE and when I look at my config object I see

enter image description here

which is why I think the line var t = config.GetSection("Args"); should retrieve the args array. I have also tried var t = config.GetValue<string[]>("Args"); but neither seems to work. It appears to me that index 4 of the configuration object is a string array keyed by "Args"

How do I retrieve the string array so I can pass it into CommandLineParser's ParseArguments method?

[Edit] One Solution:

I can now get the args passed through but it is not a particularly nice approach; If I construct the argument as --delete "-e CI -t depchpolestar -l de-DE" instead of delete -e CI -t depchpolestar -l de-DE and adding the following code to the ConsoleApp class:

var args = config.GetValue<string>("delete");
            string[] arguments = null;
            if(!string.IsNullOrEmpty(args))
            {
                var tempArgs = args.Split(" ");
                arguments = new string[tempArgs.Length + 1];
                arguments[0] = "delete";
                for(int i = 0; i < tempArgs.Length; ++i)
                {
                    arguments[i + 1] = tempArgs[i];
                }
            }

            Parser.Default.ParseArguments<DeleteOptions>(arguments)
                .WithParsed<DeleteOptions>(async c => await c.Dowork())
                .WithNotParsed(HandleParseError);

execution hits the DoWork method. Good but DeleteOptions.cs defines a Verb and the intention is to add more commands. So more work to do but going the right way.

[Edit] I have also realised that I do not need to add the AddCommandLine() call as they are added by default.

onesixtyfourth
  • 744
  • 9
  • 30

1 Answers1

0

ok it seemed that I over complicated this and ended up with this:

public static class Program
{
    public static async Task Main(string[] args)
    {
        var builtHost = CreateHostBuilder(args).Build();

        var console = builtHost.Services.GetService<ConsoleApp>();
        await console.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
         Host.CreateDefaultBuilder(args)
             .UseSerilog()
             .ConfigureAppConfiguration((hostingContext, config) =>
             {
                  config.AddJsonFile("settings.json", true, true);
                  config.AddCommandLine(args);
             })
             .ConfigureServices((hostcontext, services) =>
             {
                 services.AddTransient<ConsoleApp>();
             });
}

and this as the run method in ConsoleApp:

public Task Run()
{
    while (true)
    {
       var input = ReadFromConsole();
       if (string.IsNullOrWhiteSpace(input))
       {
           continue;
       }
       else if (input.ToLower().Equals("exit"))
       {
           break;
       }
       else
       {
              Parser.Default.ParseArguments<DeleteOptions, ConcatOptions,   DownloadOptions, ReportOptions>(input.Split(" "))
                        .WithParsed<DeleteOptions>(async options => await options.DoWork())
                        .WithParsed<ConcatOptions>(async options => await options.DoWork())
                        .WithParsed<DownloadOptions>(async options => await options.DoWork())
                        .WithParsed<ReportOptions>(async options => await options.DoWork())
                        .WithNotParsed(HandleParseError);
        }
  }

  return Task.CompletedTask;
}

This works fine for allowing me to use it as an interactive console app. I do have a problem with the DI though. I have created an OptionsBase class which set up logging and I have done it this way as trying to add a parameter to any of the Options classes fails stating unable to find the parameterless constructor. So I am assuming that CommandLine requires default constructors to work. Gett ing a logger the way I have gives me multiple log files so I need to fix that.

onesixtyfourth
  • 744
  • 9
  • 30
  • Still unsure how you gain access to the `args` you added with`AddCommandLine` in the code you pasted above. – Tea Oct 21 '21 at 18:59
  • yes on reading it now I see what you mean but I don't have access to that code anymore so unable to check for you. The ReadFromConsole() appears to be getting them. It wasn't perfect & I definitely didn't solve everything I wanted to. If I ever have to do something like that again I will post back a better understanding. – onesixtyfourth Oct 22 '21 at 07:33