spectre.console
spectre.console copied to clipboard
AddDelegateAsync or pass original arguments
Is your feature request related to a problem? Please describe. I would like to run ASP Core application in one of the command:
public static async Task Main(string[] args)
{
var app = new CommandApp<RunCommand>();
app.Configure(config =>
{
config.AddCommand<RunCommand>("db-migrate")
.WithDescription("Only starts database migrations and exits the application.");
config.AddCommand<RunCommand>("run")
.WithDescription("Runs ASP core application");
});
await app.RunAsync(args);
}
But in this way, I'm not able to pass original args to my host:
public class RunCommand : AsyncCommand
{
public override async Task<int> ExecuteAsync(CommandContext context)
{
var host = Host.CreateDefaultBuilder(args) // <-- here I need the program arguments, but context contains only parsed list
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).Build();
await host.RunAsync();
return 0;
}
}
So I tried use the AddDelegate
method instead of AddCommand
, but this method can't be async.
public static async Task Main(string[] args)
{
var app = new CommandApp<RunCommand>();
app.Configure(config =>
{
config.AddCommand<RunCommand>("db-migrate")
.WithDescription("Only starts database migrations and exits the application.");
config.AddDelegate("run", context => RunApplication(args)) //<-- the RunApplication method can'd be async
.WithDescription("Runs ASP core application");
});
await app.RunAsync(args);
}
public static async Task RunApplication(string[] args)
{
var host = Host.CreateDefaultBuilder(args) // <-- here I need the program arguments, but context contains only parsed list
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).Build();
await host.RunAsync();
}
Describe the solution you'd like This problem can be solved in two ways:
- Add original parameters to the command context:
public class RunCommand : AsyncCommand
{
public override async Task<int> ExecuteAsync(CommandContext context)
{
var args = context.OriginalArguments //<-- contains args from the main method
return 0;
}
}
- Support async deleget
AddDelegateAsync
:
public static async Task Main(string[] args)
{
var app = new CommandApp<RunCommand>();
app.Configure(config =>
{
config.AddDelegateAsync("run", async context => await RunApplication(args)) //<-- the new AddDelegateAsync method
});
await app.RunAsync(args);
}
I can prepare PR with the solution, but first I need to know which solution do you prefer.
I think that adding an async delegate is more generic. I came here because I need that as a way to constructing an async command by myself (fiddling with the typeregistrar to build it your way is much more complicated).
I would say, though, that ending the method name with Async
can be confusing since the method itself is not async, I'd rather name it AddAsyncDelegate
(we are not adding a delegate asynchronously, but adding an asynchronous delegate).
Hello @WojciechNagorski, I'm going to merge @icalvo's linked PR shortly which will provide you with solution 2.
However, I thought you might be interested to see the following example which passes data to an executing command through the CommandContext:
using Spectre.Console;
using Spectre.Console.Cli;
public class Program
{
public static async Task Main(string[] args)
{
var app = new CommandApp();
app.SetDefaultCommand<AsynchronousCommand>()
.WithData(new string[] { "a", "b" });
app.Configure(config =>
{
config.PropagateExceptions();
});
await app.RunAsync(args);
}
}
public sealed class AsynchronousCommand : AsyncCommand
{
private readonly IAnsiConsole _console;
public AsynchronousCommand(IAnsiConsole console)
{
_console = console;
}
public async override Task<int> ExecuteAsync(CommandContext context)
{
_console.WriteLine($"AsynchronousCommand.ExecuteAsync");
_console.WriteLine($"- context.Data: {string.Join(", ", ((string[])context.Data))}");
return 0;
}
}
Result: