Open rshillington opened 5 years ago
Welcome @rshillington
Dependency Injection isn't supported by the library. The parser supported Types are: the primitive data type: string,int,..., enum and the collection IEnumerable
Given the following snippet:
abstract class Verb
{
public abstract void Run();
}
[Verb("inspect")] class InspectVerb : Verb
{
}
[Verb("run")] class RunVerb : Verb
{
RunVerb(IDoSomething doSomething) { ... }
}
...
var result = parser.ParseArguments(args,
typeof(InspectVerb),
typeof(RunVerb)
);
var verbToRun = result.MapResult(o => o as Verb, _ => null);
if (verbToRun == null) Environment.Exit(-1);
await verbToRun.Run();
If I try and use the run
verb I will get a MissingMethodException
No parameterless constructor defined for this object. Ideally the RunVerb
instance would be created from DI
No parameterless constructor defined for this object
If the Verb/Option type is Mutable then it must have a default parameter-less constructor If Immutable Type, a constructor that takes the options/args needed to create the type.
The class RunVerb constructor has a parameter need to be injected and this not supported.
The next scenario may help:
[Verb("run")] class RunVerb : Verb
{
[Option]
public string PluginName {get;set;}
// based on the value of PluginName option you RunPlugin with the corresponding plugin
public void RunPlugin(IDoSomething doSomething)
{
//...
}
}
I'm a huge fan of Micorosft's DI so I ended up writing my own DI extension for CommandLineParser. You're welcome to try it out for yourself here: https://github.com/JaronrH/CommandLineParser.DependencyInjection
1) Add your Options as classes (Verbs or single options class) and have them implement ICommandLineOptions (interface does nothing on it's own). These should not require DI injection as they are just used to identify Options using DI!
2) Create a service that implements IExecuteCommandLineOptions<TCommandLineOptions, TResult>. TCommandLineOptions should be an options class from step 1 and TResult is what the parser will ultimately return as a result.
3) [Optional] Implement a service that implements IExecuteParsingFailure
If you want to manually control what gets pulled into DI, just use AddCommandLineParser() without passing in any assemblies and then register the implementations yourself.
Note: If you want the help information dispatched to the console, you will need to specify that in the configuration. For example:
parser.ParseArguments(args, o => { o.HelpWriter = System.Console.Error; });
Hope that helps!
@JaronrH, Adding DI to CLP increase the complexity and violate the concept of SR (single responsibility).
Why not simplify the problem and re-design the system by moving all DI parts outside the Verb classes to a new class which is DI support and use one of DI containers like Microsoft DI / autofac or whatever to resolve the dependency.
Also , CLP is a nuget package with no external dependency at all, and using DI will need to reference these DI libraries which will cause a major Break Change. . Verbs are ParameterLess Ctor classes and are instantiated by Reflection,
@moh-hassan I did not add add DI to CLP. Instead, the DI package I built uses CLP as one of its dependencies (along with DI) and doesn't change anything about CLP itself. It did not violate the concept of SR either. In fact, what I did is almost exactly what you described!
Thanks @JaronrH for clarification.
@moh-hassan Dependency injection can prove very useful. For instance I can configure a logging framework and may want to inject it across different verb classes. Also I may want to inject other services as needed to call APIs.
Adding DI to CLP increase the complexity and violate the concept of SR (single responsibility).
How?
My simple workaround:
IInjectableServiceProvider
public interface IInjectableServiceProvider
{
public IServiceProvider ServiceProvider { get; set; }
}
public IServiceProvider ServiceProvider { get; set; }
if (command is IInjectableServiceProvider injectable)
injectable.ServiceProvider = _serviceProvider;
i haven't looked at the library code yet, but i think you can keep single responsibility and without adding aspnetcore dependency. How? Allowing us to intervene between data type detection and creation of the instance
commandlineparser
commandlineparser
populates propertiesthoughts?
@JaronrH thank you for your implementation of DI really great :D
Would LOVE to see this incorporated within the core library. This would add a nice improvement that is inline with the direction Microsoft is going to extend the core application host.
When constructing the instance of from the array of types provided to the
ParseArguments
it would be nice if the instance supplied had it's dependencies resolved via constructor arguments. Perhaps this is already in place and I'm just not seeing how it's configured?