bilal-fazlani / commanddotnet

A modern framework for building modern CLI apps
https://commanddotnet.bilal-fazlani.com
MIT License
560 stars 32 forks source link

[Not a Bug] Just wanted to share how to register all commands & subcommands as 'singleton' #453

Closed jeffward01 closed 1 year ago

jeffward01 commented 1 year ago

Hello!

I was not sure where to share this code snippet, I figured it was to small for a pull request, however, i do think that it would be helpful to add to the GREAT library CommandDotNet, or at least have in the wiki.

This command below uses Microsoft.Extensions.DependencyInjection and registers all commands (and subcommands) as singletons.

This will not work if you would like to use interfaces. If you want to use interfaces, I advise you to check out Scrutor which is a great library for scanning assemblies for dependency injection

Docs for Microsoft.Extensions.DependencyInjection


// Register all commands and sub commands here, then inject the ServiceProvider in the AppRunner as seen Docs for Microsoft.Extensions.DependencyInjectio above

private static void RegisterAllCommands(this IServiceCollection services)
    {
        var commandClasses = from assembly in AppDomain.CurrentDomain.GetAssemblies() from type in assembly.GetTypes() where type.IsDefined(typeof(CommandAttribute)) select type;

        foreach (var commandClass in commandClasses)
        {
            services.AddSingleton(commandClass);

          // This code registers NESTED subcommands. This can be removed if you do not have nested subcommands within your parent command classes
            foreach (var pi in commandClass.GetProperties()
                         .Where(pi => Attribute.IsDefined(pi, typeof(SubcommandAttribute))))
            {
                var subCommandClass = pi.PropertyType;
                services.AddSingleton(subCommandClass);
            }
        }

I hope someone will find this helpful!

Thanks for the very advanced and helpful library CommandDotNet! I hope to contribute some Pull Requests in the future as I build more tools


Edit:

This will only work if for nested subcommands.

If you do not use nested subcommands, remove the code that registers subcommands

drewburlingame commented 1 year ago

Hi @jeffward01,

Thanks for the compliments and for contributing to the community. Glad you're finding the library helpful.

We have an example documented here at https://commanddotnet.bilal-fazlani.com/otherfeatures/dependency-injection/#registering-all-command-classes.

private static void RegisterSimpleInjector(AppRunner appRunner)
{
    var container = new SimpleInjector.Container();
    foreach(Type type in appRunner.GetCommandClassTypes())
    {
        container.Register(type);
    }
    appRunner.UseSimpleInjector(container);
}

This approach has the benefit of only registering all and only the commands that can be surfaced by the app. So, it won't matter what assemblies to scan and it won't include commands that are not yet linked as subcommands, etc. It will also handle cases where middleware has dynamically added and removed commands.

In an effort to improve discoverability of this example, is there something we could have done to make it easier to find?

Cheers, Drew