diff --git a/src/CommandLineUtils/Internal/ReflectionHelper.cs b/src/CommandLineUtils/Internal/ReflectionHelper.cs index 4d7e1c65..52dabb61 100644 --- a/src/CommandLineUtils/Internal/ReflectionHelper.cs +++ b/src/CommandLineUtils/Internal/ReflectionHelper.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Threading; using McMaster.Extensions.CommandLineUtils.Abstractions; +using Microsoft.Extensions.DependencyInjection; namespace McMaster.Extensions.CommandLineUtils { @@ -21,7 +22,7 @@ public static SetPropertyDelegate GetPropertySetter(PropertyInfo prop) var setter = prop.GetSetMethod(nonPublic: true); if (setter != null) { - return (obj, value) => setter.Invoke(obj, new object?[] { value }); + return (obj, value) => setter.Invoke(obj, [value]); } else { @@ -44,7 +45,7 @@ public static GetPropertyDelegate GetPropertyGetter(PropertyInfo prop) if (getter != null) { #pragma warning disable CS8603 // Possible null reference return. - return obj => getter.Invoke(obj, Array.Empty()); + return obj => getter.Invoke(obj, []); #pragma warning restore CS8603 // Possible null reference return. } else @@ -122,8 +123,23 @@ public static MemberInfo[] GetMembers(Type type) } else { - var service = command.AdditionalServices?.GetService(methodParam.ParameterType); - arguments[i] = service ?? throw new InvalidOperationException(Strings.UnsupportedParameterTypeOnMethod(method.Name, methodParam)); + // Check for FromKeyedServicesAttribute + var keyedAttr = methodParam.GetCustomAttribute(); + if (keyedAttr != null) + { + if (command.AdditionalServices is not IKeyedServiceProvider keyedServiceProvider) + { + throw new InvalidOperationException("AdditionalServices does not support keyed service resolution."); + } + + arguments[i] = keyedServiceProvider.GetKeyedService(methodParam.ParameterType, keyedAttr.Key) + ?? throw new InvalidOperationException($"No keyed service found for type {methodParam.ParameterType} and key '{keyedAttr.Key}'."); + } + else + { + var service = command.AdditionalServices?.GetService(methodParam.ParameterType); + arguments[i] = service ?? throw new InvalidOperationException(Strings.UnsupportedParameterTypeOnMethod(method.Name, methodParam)); + } } } diff --git a/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj b/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj index 2a487a49..a9e4b3a9 100644 --- a/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj +++ b/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj @@ -26,6 +26,7 @@ McMaster.Extensions.CommandLineUtils.ArgumentEscaper + diff --git a/test/CommandLineUtils.Tests/CommandLineApplicationOfTTests.cs b/test/CommandLineUtils.Tests/CommandLineApplicationOfTTests.cs index 1696b379..50f72144 100644 --- a/test/CommandLineUtils.Tests/CommandLineApplicationOfTTests.cs +++ b/test/CommandLineUtils.Tests/CommandLineApplicationOfTTests.cs @@ -95,7 +95,7 @@ class ThrowsInCtorClass { public ThrowsInCtorClass() { - throw new XunitException("Parent comand object should not be initialized.\n" + Environment.StackTrace); + throw new XunitException("Parent command object should not be initialized.\n" + Environment.StackTrace); } public void OnExecute() { } @@ -124,7 +124,7 @@ public void AllowNoThrowBehaviorOnUnexpectedOptionWhenHasSubcommand() } [Fact] - public void ItDoesNotInitalizeClassUnlessNecessary() + public void ItDoesNotInitializeClassUnlessNecessary() { using var app = new CommandLineApplication(new TestConsole(_output)); app.Conventions.UseDefaultConventions(); @@ -139,7 +139,7 @@ class SimpleCommand } [Fact] - public void ItDoesNotInitalizeParentClassUnlessNecessary() + public void ItDoesNotInitializeParentClassUnlessNecessary() { using var app = new CommandLineApplication(new TestConsole(_output)); app.Conventions.UseDefaultConventions(); diff --git a/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs b/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs index a25443cd..7dc47d8c 100644 --- a/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs +++ b/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs @@ -4,6 +4,8 @@ using System; using System.Threading; using System.Threading.Tasks; +using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; using Xunit.Abstractions; @@ -141,5 +143,49 @@ public async Task ItExecutesAsyncMethod() Assert.Equal(4, result); Assert.True(app.Model.Token.IsCancellationRequested); } + + + + private class MyClass(string name) + { + public string Name { get; } = name; + } + + private class ProgramWithExecuteAndKeyedArgumentInjection + { + private int OnExecute + ( + [FromKeyedServices("Database1")] MyClass myClass1, + [FromKeyedServices("Database2")] MyClass myClass2, + string nonKeyedArgument + ) + { + Assert.Equal("MyClass1", myClass1.Name); + Assert.Equal("MyClass2", myClass2.Name); + Assert.Equal("42", nonKeyedArgument); + return 42; + } } + + [Fact] + public void OnExecuteWithKeyedArgumentsResolvesArgumentsByKey() + { + var serviceCollection = new ServiceCollection(); + serviceCollection + .AddKeyedSingleton("Database1", new MyClass("MyClass1")) + .AddKeyedSingleton("Database2", new MyClass("MyClass2")) + .AddSingleton("42") + ; + + var app = new CommandLineApplication(); + + app.AdditionalServices = serviceCollection.BuildServiceProvider(); + + app.Conventions.UseOnExecuteMethodFromModel(); + var result = app.Execute(); + Assert.Equal(42, result); + } + + +} }