diff --git a/src/CommandLineUtils/Internal/ReflectionHelper.cs b/src/CommandLineUtils/Internal/ReflectionHelper.cs index 07b6daec..b8f6b531 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 d3f8037a..f5dca581 100644 --- a/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj +++ b/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true true Command-line parsing API. @@ -25,4 +25,8 @@ McMaster.Extensions.CommandLineUtils.ArgumentEscaper + + + + diff --git a/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj b/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj index af94d444..d26014ba 100644 --- a/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj +++ b/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net8.0 true true Provides command-line parsing API integration with the generic host API (Microsoft.Extensions.Hosting). 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 a283c818..c19397d8 100644 --- a/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs +++ b/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs @@ -4,10 +4,14 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Moq; using Xunit; using Xunit.Abstractions; +using McMaster.Extensions.CommandLineUtils; + namespace McMaster.Extensions.CommandLineUtils.Tests { public class ExecuteMethodConventionTests @@ -141,5 +145,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); + } + + } } diff --git a/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj b/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj index 1ab1af5f..1a166a7c 100644 --- a/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj +++ b/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net6.0 + net8.0;net9.0 annotations @@ -13,7 +13,9 @@ - + + + diff --git a/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj b/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj index 5e19f487..9c62798c 100644 --- a/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj +++ b/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net6.0 + net8.0;net9.0