commandlineparser / commandline

The best C# command line parser that brings standardized *nix getopt style, for .NET. Includes F# support
MIT License
4.6k stars 480 forks source link

Is there support for Dependency Injection #466

Open rshillington opened 5 years ago

rshillington commented 5 years ago

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?

moh-hassan commented 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 by using the Option and Value Attributes. Can you describe by example the scenario you want to implement?

rshillington commented 5 years ago

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

moh-hassan commented 5 years ago

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) 
      { 
      //...
      }          

    }
JaronrH commented 5 years ago

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. This will allow you to handle errors from the parser. 4) Setup DI and use the AddCommandLineParser() extension to IServiceCollection to add the CommandLineParser DI extensions (pass in the assemblies that contain the above implementations). This uses Scrutor (https://github.com/khellang/Scrutor) to scan the assemblies and add them as needed. 5) After building your IServiceProvider, you can request the parser or have it injected in a service constructor. i.e. request service ICommandLineParser 6) Use parser.ParseArguments(args) to parse and execute. Behind the scenes, this creates the parser using the class type(s) registered as ICommandLineOptions in DI from step 1. If the parser is successful, the DI attempts to resolve the corresponding IExecuteCommandLineOptions<TCommandLineOptions, TResult> service from DI (step 2). Otherwise, the optional IExecuteParsingFailure service from DI is used from step 3 (if defined, otherwise, we return default(TResult)).

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!

moh-hassan commented 4 years ago

@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,

JaronrH commented 4 years ago

@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!

moh-hassan commented 4 years ago

Thanks @JaronrH for clarification.

ankitbko commented 4 years ago

@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?

TheTrigger commented 1 year ago

My simple workaround:

TheTrigger commented 1 year ago

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

thoughts?

salama135 commented 1 year ago

@JaronrH thank you for your implementation of DI really great :D

ravensorb commented 10 months ago

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.