Open taori opened 4 years ago
The way you're doing it is a simple approach that lets you use a dependency injection library of your choice but was intended for the 80% cases.
For more complex cases, Microsoft.Extensions.DependencyInjection is supported via the System.CommandLine.Hosting package. Would this work for you?
The way you're doing it is a simple approach that lets you use a dependency injection library of your choice but was intended for the 80% cases.
For more complex cases, Microsoft.Extensions.DependencyInjection is supported via the System.CommandLine.Hosting package. Would this work for you?
That sounds very promising actually. Somehow i only stumbled upon DragonFruit and dotnet-suggest through the wiki of this project. I'll have a look at it and get back to this issue with feedback
This certainly seems to be what i am looking for. What i plan on doing is having an invocation structure where i implement something like ITypedCommand<TArg1, TArg2, ...>
so i can utilize constructor injection and map arguments through Attributes. Do you think this is doable right now? Essentially i need something similar to how things work with mvc actions.
@jonsequitur This is my current state.
I can't seem to get it working unfortunately. At least i would expect it to forward input to the "testCommand" handler, but not even that appears to work.
Do you think what i am attempting to do is doable with *.Hosting?
using System;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Hosting;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Reflection;
using System.Threading.Tasks;
using Generator.CLI.Component.Parser;
using Generator.CLI.Dependencies;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Generator.CLI
{
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new CommandLineBuilder()
.UseHost(host => { host.ConfigureServices((context, services) =>
{
services.AddTransient(typeof(ILogger<>), typeof(Logger<>));
services.AddTransient(typeof(ILoggerFactory), typeof(LoggerFactory));
}); })
.UseHelp()
.UseTypoCorrections()
.UseAttributedCommands()
.UseSuggestDirective();
var parser = builder.Build();
var parseResult = parser.Parse("test -v hi");
var invoke = await parseResult.InvokeAsync();
return invoke;
}
}
public static class BuilderExtensions
{
public static CommandLineBuilder UseAttributedCommands(this CommandLineBuilder source)
{
source.UseMiddleware(async (context, next) =>
{
var rootCommand = new RootCommand();
var testCommand = new Command("test")
{
new Argument<string>("v")
};
// this is where i want to construct classes inject registered services and register commands to the root command
rootCommand.AddCommand(testCommand);
var method = typeof(BuilderExtensions).GetMethod(nameof(Test), BindingFlags.Static | BindingFlags.NonPublic);
// testCommand.Handler = CommandHandler.Create(method, null);
testCommand.Handler = CommandHandler.Create<string>(p => Console.WriteLine(p));
source.AddCommand(rootCommand);
await next.Invoke(context);
});
return source;
}
private static void Test(string param)
{
throw new NotImplementedException();
}
}
}
I did get it to work like this:
using System;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Hosting;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Generator.CLI
{
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new CommandLineBuilder()
.UseHost(host => { host.ConfigureServices((context, services) =>
{
services.AddTransient(typeof(ILoggerFactory), typeof(LoggerFactory));
services.AddTransient(typeof(ILogger<>), typeof(Logger<>));
}); })
.UseDebugDirective()
.UseHelp()
.UseTypoCorrections()
.UseAttributedCommands()
.UseSuggestDirective();
var parser = builder.Build();
var parseResult = parser.Parse("test -b \"parameter b\" -a \"parameter a\"");
var invoke = await parseResult.InvokeAsync();
return invoke;
}
}
public static class BuilderExtensions
{
public static CommandLineBuilder UseAttributedCommands(this CommandLineBuilder source)
{
var method = typeof(BuilderExtensions).GetMethod(nameof(Test), BindingFlags.Static | BindingFlags.NonPublic);
var testCommand = new Command("test");
testCommand.AddOption(new Option("-a") { Argument = new Argument("-a"){ Arity = ArgumentArity.ExactlyOne}});
testCommand.AddOption(new Option("-b") { Argument = new Argument("-b") { Arity = ArgumentArity.ExactlyOne } });
testCommand.Handler = CommandHandler.Create(method, null);
source.AddCommand(testCommand);
return source;
}
private static void Test(IHost host, string a, string b)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
// logger.LogDebug("just a test really");
Console.WriteLine(a);
Console.WriteLine(b);
}
}
}
Ideally the BindingContext should also try to use the host to resolve services instead of having to go through the host for everything.
Would your expectation be that you can inject, e.g. a parameter of ILogger<Something>
directly into the Test
method?
If i am using a host yes, otherwise it would make sense if that did not work.
Related: #671.
The following pattern should also be enabled by making di "simple" using constructor injection and non static methods.
class TestCommand
{
MyService _service;
TestCommand(MyService service)
{
_service = service;
}
public void Test(string a, string b)
{
Console.WriteLine($"hello {_service} {a} {b}");
}
}
attempting this today creates an exception "Unhandled exception: System.Reflection.TargetException: Non-static method requires a target."
These days it would be nice if there was a simple way of utilizing dependency injection in command line applications as well.
Currently i am using the latest version, using middleware to populate the bindingcontext with services, but that does not allow open generic registration (for easy logger registration). Perhaps that is a scenario worth keeping in mind.