diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs index 81516568c14e64..62727442fc1492 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs @@ -31,18 +31,35 @@ public static object CreateInstance( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, params object[] parameters) { - int bestLength = -1; - bool seenPreferred = false; + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } - ConstructorMatcher bestMatcher = default; + if (instanceType.IsAbstract) + { + throw new InvalidOperationException(SR.CannotCreateAbstractClasses); + } - if (!instanceType.IsAbstract) + IServiceProviderIsService? serviceProviderIsService = provider.GetService(); + // if container supports using IServiceProviderIsService, we try to find the longest ctor that + // (a) matches all parameters given to CreateInstance + // (b) matches the rest of ctor arguments as either a parameter with a default value or as a service registered + // if no such match is found we fallback to the same logic used by CreateFactory which would only allow creating an + // instance if all parameters given to CreateInstance only match with a single ctor + if (serviceProviderIsService != null) { + int bestLength = -1; + bool seenPreferred = false; + + ConstructorMatcher bestMatcher = default; + bool multipleBestLengthFound = false; + foreach (ConstructorInfo? constructor in instanceType.GetConstructors()) { var matcher = new ConstructorMatcher(constructor); bool isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false); - int length = matcher.Match(parameters); + int length = matcher.Match(parameters, serviceProviderIsService); if (isPreferred) { @@ -61,19 +78,37 @@ public static object CreateInstance( { bestLength = length; bestMatcher = matcher; + multipleBestLengthFound = false; + } + else if (bestLength == length) + { + multipleBestLengthFound = true; } seenPreferred |= isPreferred; } + + if (bestLength != -1) + { + if (multipleBestLengthFound) + { + throw new InvalidOperationException(SR.Format(SR.MultipleCtorsFoundWithBestLength, instanceType, bestLength)); + } + + return bestMatcher.CreateInstance(provider); + } } - if (bestLength == -1) + Type?[] argumentTypes = new Type[parameters.Length]; + for (int i = 0; i < argumentTypes.Length; i++) { - string? message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided."; - throw new InvalidOperationException(message); + argumentTypes[i] = parameters[i]?.GetType(); } - return bestMatcher.CreateInstance(provider); + FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructorInfo, out int?[] parameterMap); + var constructorMatcher = new ConstructorMatcher(constructorInfo); + constructorMatcher.MapParameters(parameterMap, parameters); + return constructorMatcher.CreateInstance(provider); } /// @@ -92,7 +127,7 @@ public static ObjectFactory CreateFactory( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type[] argumentTypes) { - FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo? constructor, out int?[]? parameterMap); + FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); ParameterExpression? provider = Expression.Parameter(typeof(IServiceProvider), "provider"); ParameterExpression? argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); @@ -152,8 +187,7 @@ private static MethodInfo GetMethodInfo(Expression expr) object? service = sp.GetService(type); if (service == null && !isDefaultParameterRequired) { - string? message = $"Unable to resolve service for type '{type}' while attempting to activate '{requiredBy}'."; - throw new InvalidOperationException(message); + throw new InvalidOperationException(SR.Format(SR.UnableToResolveService, type, requiredBy)); } return service; } @@ -202,7 +236,7 @@ private static Expression BuildFactoryExpression( private static void FindApplicableConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, - Type[] argumentTypes, + Type?[] argumentTypes, out ConstructorInfo matchingConstructor, out int?[] matchingParameterMap) { @@ -212,8 +246,7 @@ private static void FindApplicableConstructor( if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref constructorInfo, ref parameterMap) && !TryFindMatchingConstructor(instanceType, argumentTypes, ref constructorInfo, ref parameterMap)) { - string? message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided."; - throw new InvalidOperationException(message); + throw new InvalidOperationException(SR.Format(SR.CtorNotLocated, instanceType)); } matchingConstructor = constructorInfo; @@ -223,7 +256,7 @@ private static void FindApplicableConstructor( // Tries to find constructor based on provided argument types private static bool TryFindMatchingConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, - Type[] argumentTypes, + Type?[] argumentTypes, [NotNullWhen(true)] ref ConstructorInfo? matchingConstructor, [NotNullWhen(true)] ref int?[]? parameterMap) { @@ -233,7 +266,7 @@ private static bool TryFindMatchingConstructor( { if (matchingConstructor != null) { - throw new InvalidOperationException($"Multiple constructors accepting all given argument types have been found in type '{instanceType}'. There should only be one applicable constructor."); + throw new InvalidOperationException(SR.Format(SR.MultipleCtorsFound, instanceType)); } matchingConstructor = constructor; @@ -253,7 +286,7 @@ private static bool TryFindMatchingConstructor( // Tries to find constructor marked with ActivatorUtilitiesConstructorAttribute private static bool TryFindPreferredConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, - Type[] argumentTypes, + Type?[] argumentTypes, [NotNullWhen(true)] ref ConstructorInfo? matchingConstructor, [NotNullWhen(true)] ref int?[]? parameterMap) { @@ -289,7 +322,7 @@ private static bool TryFindPreferredConstructor( // Creates an injective parameterMap from givenParameterTypes to assignable constructorParameters. // Returns true if each given parameter type is assignable to a unique; otherwise, false. - private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters, Type[] argumentTypes, out int?[] parameterMap) + private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters, Type?[] argumentTypes, out int?[] parameterMap) { parameterMap = new int?[constructorParameters.Length]; @@ -336,39 +369,48 @@ public ConstructorMatcher(ConstructorInfo constructor) _parameterValues = new object?[_parameters.Length]; } - public int Match(object[] givenParameters) + public int Match(object[] givenParameters, IServiceProviderIsService serviceProviderIsService) { - int applyIndexStart = 0; - int applyExactLength = 0; - for (int givenIndex = 0; givenIndex != givenParameters.Length; givenIndex++) + for (int givenIndex = 0; givenIndex < givenParameters.Length; givenIndex++) { Type? givenType = givenParameters[givenIndex]?.GetType(); bool givenMatched = false; - for (int applyIndex = applyIndexStart; givenMatched == false && applyIndex != _parameters.Length; ++applyIndex) + for (int applyIndex = 0; applyIndex < _parameters.Length; applyIndex++) { if (_parameterValues[applyIndex] == null && _parameters[applyIndex].ParameterType.IsAssignableFrom(givenType)) { givenMatched = true; _parameterValues[applyIndex] = givenParameters[givenIndex]; - if (applyIndexStart == applyIndex) - { - applyIndexStart++; - if (applyIndex == givenIndex) - { - applyExactLength = applyIndex; - } - } + break; } } - if (givenMatched == false) + if (!givenMatched) { return -1; } } - return applyExactLength; + + // confirms the rest of ctor arguments match either as a parameter with a default value or as a service registered + for (int i = 0; i < _parameters.Length; i++) + { + if (_parameterValues[i] == null && + !serviceProviderIsService.IsService(_parameters[i].ParameterType)) + { + if (ParameterDefaultValue.TryGetDefaultValue(_parameters[i], out object? defaultValue)) + { + _parameterValues[i] = defaultValue; + } + else + { + return -1; + } + } + } + + return _parameters.Length; } public object CreateInstance(IServiceProvider provider) @@ -382,7 +424,7 @@ public object CreateInstance(IServiceProvider provider) { if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out object? defaultValue)) { - throw new InvalidOperationException($"Unable to resolve service for type '{_parameters[index].ParameterType}' while attempting to activate '{_constructor.DeclaringType}'."); + throw new InvalidOperationException(SR.Format(SR.UnableToResolveService, _parameters[index].ParameterType, _constructor.DeclaringType)); } else { @@ -411,16 +453,27 @@ public object CreateInstance(IServiceProvider provider) return _constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null); #endif } + + public void MapParameters(int?[] parameterMap, object[] givenParameters) + { + for (int i = 0; i < _parameters.Length; i++) + { + if (parameterMap[i] != null) + { + _parameterValues[i] = givenParameters[(int)parameterMap[i]!]; + } + } + } } private static void ThrowMultipleCtorsMarkedWithAttributeException() { - throw new InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}."); + throw new InvalidOperationException(SR.Format(SR.MultipleCtorsMarkedWithAttribute, nameof(ActivatorUtilitiesConstructorAttribute))); } private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments() { - throw new InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types."); + throw new InvalidOperationException(SR.Format(SR.MarkedCtorMissingArgumentTypes, nameof(ActivatorUtilitiesConstructorAttribute))); } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx index 7c9626ed815267..bfe0f89909ad35 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx @@ -140,4 +140,31 @@ Implementation type cannot be '{0}' because it is indistinguishable from other services registered for '{1}'. {0} = implementation type, {1} = service type + + Multiple constructors were marked with {0}. + {0} = attribute used with ActivatorUtilities + + + Constructor marked with {0} does not accept all given argument types. + {0} = attribute used with ActivatorUtilities + + + Instances of abstract classes cannot be created. + + + Multiple constructors for type '{0}' were found with length {1}. + {0} = instance type, {1} = best length + + + Unable to resolve service for type '{0}' while attempting to activate '{1}'. + {0} = service type, {1} = required by + + + A suitable constructor for type '{0}' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided. + {0} = instance type + + + Multiple constructors accepting all given argument types have been found in type '{0}'. There should only be one applicable constructor. + {0} = instance type + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs index fd9d639d9d091c..860768f0e4612c 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs @@ -78,7 +78,9 @@ public void TypeActivatorAcceptsAnyNumberOfAdditionalConstructorParametersToProv public void TypeActivatorWorksWithStaticCtor(CreateInstanceFunc createFunc) { // Act - var anotherClass = CreateInstance(createFunc, provider: null); + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); + var anotherClass = CreateInstance(createFunc, provider: serviceProvider); // Assert Assert.NotNull(anotherClass); @@ -151,10 +153,12 @@ public void TypeActivatorRequiresPublicConstructor(CreateInstanceFunc createFunc // Arrange var expectedMessage = $"A suitable constructor for type '{type}' could not be located. " + "Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided."; + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); // Act and Assert var ex = Assert.Throws(() => - createFunc(provider: null, type: type, args: Array.Empty())); + createFunc(provider: serviceProvider, type: type, args: Array.Empty())); Assert.Equal(expectedMessage, ex.Message); } @@ -184,11 +188,14 @@ public void TypeActivatorRequiresAllArgumentsCanBeAccepted(CreateInstanceFunc cr public void TypeActivatorRethrowsOriginalExceptionFromConstructor(CreateInstanceFunc createFunc) { // Act + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); + var ex1 = Assert.Throws(() => - CreateInstance(createFunc, provider: null)); + CreateInstance(createFunc, provider: serviceProvider)); var ex2 = Assert.Throws(() => - CreateInstance(createFunc, provider: null, args: new[] { new FakeService() })); + CreateInstance(createFunc, provider: serviceProvider, args: new[] { new FakeService() })); // Assert Assert.Equal(nameof(ClassWithThrowingEmptyCtor), ex1.Message); @@ -214,9 +221,9 @@ public void TypeActivatorCreateFactoryDoesNotAllowForAmbiguousConstructorMatches } [Theory] - [InlineData("", "string")] + [InlineData("", "IFakeService, string")] [InlineData(5, "IFakeService, int")] - public void TypeActivatorCreateInstanceUsesFirstMathchedConstructor(object value, string ctor) + public void TypeActivatorCreateInstanceUsesLongestAvailableConstructor(object value, string ctor) { // Arrange var serviceCollection = new TestServiceCollection(); @@ -224,11 +231,24 @@ public void TypeActivatorCreateInstanceUsesFirstMathchedConstructor(object value var serviceProvider = CreateServiceProvider(serviceCollection); var type = typeof(ClassWithAmbiguousCtors); - // Act - var instance = ActivatorUtilities.CreateInstance(serviceProvider, type, value); + if (SupportsIServiceProviderIsService) + { + // Act + var instance = ActivatorUtilities.CreateInstance(serviceProvider, type, value); - // Assert - Assert.Equal(ctor, ((ClassWithAmbiguousCtors)instance).CtorUsed); + // Assert + Assert.Equal(ctor, ((ClassWithAmbiguousCtors)instance).CtorUsed); + } + else + { + // Act + var ex = Assert.Throws(() => + ActivatorUtilities.CreateInstance(serviceProvider, type, value)); + + // Assert + Assert.Equal($"Multiple constructors accepting all given argument types have been found in type '{type}'. " + + "There should only be one applicable constructor.", ex.Message); + } } [Theory] @@ -251,8 +271,12 @@ public void TypeActivatorUsesMarkedConstructor(CreateInstanceFunc createFunc) [MemberData(nameof(CreateInstanceFuncs))] public void TypeActivatorThrowsOnMultipleMarkedCtors(CreateInstanceFunc createFunc) { + // Arrange + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); + // Act - var exception = Assert.Throws(() => CreateInstance(createFunc, null, "hello")); + var exception = Assert.Throws(() => CreateInstance(createFunc, serviceProvider, "hello")); // Assert Assert.Equal("Multiple constructors were marked with ActivatorUtilitiesConstructorAttribute.", exception.Message); @@ -262,11 +286,15 @@ public void TypeActivatorThrowsOnMultipleMarkedCtors(CreateInstanceFunc createFu [MemberData(nameof(CreateInstanceFuncs))] public void TypeActivatorThrowsWhenMarkedCtorDoesntAcceptArguments(CreateInstanceFunc createFunc) { - // Act - var exception = Assert.Throws(() => CreateInstance(createFunc, null, 0, "hello")); + // Arrange + string message = "Constructor marked with ActivatorUtilitiesConstructorAttribute does not accept all given argument types."; + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); - // Assert - Assert.Equal("Constructor marked with ActivatorUtilitiesConstructorAttribute does not accept all given argument types.", exception.Message); + // Act & Assert + var exception = Assert.Throws(() => + CreateInstance(createFunc, serviceProvider, 0, "hello")); + Assert.Equal(message, exception.Message); } [Fact] @@ -359,7 +387,7 @@ public void UnRegisteredServiceAsConstructorParameterThrowsException(CreateInsta var serviceProvider = CreateServiceProvider(serviceCollection); var ex = Assert.Throws(() => - CreateInstance(createFunc, serviceProvider)); + CreateInstance(createFunc, serviceProvider)); Assert.Equal($"Unable to resolve service for type '{typeof(IFakeService)}' while attempting" + $" to activate '{typeof(CreationCountFakeService)}'.", ex.Message); @@ -368,9 +396,13 @@ public void UnRegisteredServiceAsConstructorParameterThrowsException(CreateInsta [Fact] public void CreateInstance_WithAbstractTypeAndPublicConstructor_ThrowsCorrectException() { + // Arrange + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); + // Act & Assert - var ex = Assert.Throws(() => ActivatorUtilities.CreateInstance(default(IServiceProvider), typeof(AbstractFoo))); - var msg = "A suitable constructor for type 'Microsoft.Extensions.DependencyInjection.Specification.DependencyInjectionSpecificationTests+AbstractFoo' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided."; + var ex = Assert.Throws(() => ActivatorUtilities.CreateInstance(serviceProvider, typeof(AbstractFoo))); + var msg = "Instances of abstract classes cannot be created."; Assert.Equal(msg, ex.Message); } @@ -378,7 +410,10 @@ public void CreateInstance_WithAbstractTypeAndPublicConstructor_ThrowsCorrectExc public void CreateInstance_CapturesInnerException_OfTargetInvocationException() { // Act & Assert - var ex = Assert.Throws(() => ActivatorUtilities.CreateInstance(default(IServiceProvider), typeof(Bar))); + var serviceCollection = new TestServiceCollection(); + var serviceProvider = CreateServiceProvider(serviceCollection); + + var ex = Assert.Throws(() => ActivatorUtilities.CreateInstance(serviceProvider, typeof(Bar))); var msg = "some error"; Assert.Equal(msg, ex.Message); } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs index ad865829d9d5e7..acc8a124502a37 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs @@ -9,8 +9,6 @@ namespace Microsoft.Extensions.DependencyInjection.Specification { public class AutofacDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests { - public override bool SupportsIServiceProviderIsService => false; - public override string[] SkippedTests => new[] { "ScopesAreFlatNotHierarchical", diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/LightInject.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/LightInject.cs index 2c08457b6c2e20..d5af6e6862ced8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/LightInject.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/LightInject.cs @@ -10,8 +10,6 @@ namespace Microsoft.Extensions.DependencyInjection.Specification { public class LightInjectDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests { - public override bool SupportsIServiceProviderIsService => false; - public override string[] SkippedTests => new string[] { "NonSingletonService_WithInjectedProvider_ResolvesScopeProvider" diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Microsoft.Extensions.DependencyInjection.ExternalContainers.Tests.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Microsoft.Extensions.DependencyInjection.ExternalContainers.Tests.csproj index f18001943acacc..ef3967f7d162e7 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Microsoft.Extensions.DependencyInjection.ExternalContainers.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Microsoft.Extensions.DependencyInjection.ExternalContainers.Tests.csproj @@ -15,16 +15,16 @@ - + - + - + - + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs new file mode 100644 index 00000000000000..3ca6e77663caa2 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs @@ -0,0 +1,336 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Xunit; +using static Microsoft.Extensions.DependencyInjection.Tests.AsyncServiceScopeTests; + +namespace Microsoft.Extensions.DependencyInjection.Tests +{ + public class ActivatorUtilitiesTests + { + [Fact] + public void CreateInstance_ClassWithABCS_UsesTheLongestAvailableConstructor() + { + var services = new ServiceCollection(); + services.AddScoped(); + services.AddScoped(); + using var provider = services.BuildServiceProvider(); + var a = new A(); + var c = new C(); + + var instance = ActivatorUtilities.CreateInstance(provider, a, c); + + Assert.NotNull(instance.B); + Assert.NotNull(instance.S); + Assert.Same(a, instance.A); + Assert.Same(c, instance.C); + } + + [Fact] + public void CreateInstance_OneCtor_IsRegistered_CreatesInstanceSuccessfully() + { + var services = new ServiceCollection(); + services.AddScoped(); + using var provider = services.BuildServiceProvider(); + + var instance = ActivatorUtilities.CreateInstance(provider); + Assert.NotNull(instance.A); + } + + [Fact] + public void CreateInstance_BadlyConfiguredIServiceProviderIsService_ProperlyCreatesUnambiguousInstance() + { + var serviceCollection = new ServiceCollection() + .AddScoped(); + var fakeServiceProvider = new FakeServiceProvider(); + fakeServiceProvider.Populate(serviceCollection); + fakeServiceProvider.Build(); + + var instance = ActivatorUtilities.CreateInstance(fakeServiceProvider); + + Assert.NotNull(instance); + Assert.True(fakeServiceProvider.FakeServiceProviderIsService.IsServiceGotCalled); + } + + [Theory] + [InlineData(typeof(ABCS1))] + [InlineData(typeof(ABCS2))] + [InlineData(typeof(ABCS3))] + public void CreateInstance_DifferentOrders_CreatesInstanceSuccessfully(Type type) + { + var services = new ServiceCollection(); + services.AddScoped(); + using var provider = services.BuildServiceProvider(); + B b = new B(); + C c = new C(); + + var instance = (ABCS)ActivatorUtilities.CreateInstance(provider, type, b, c); + Assert.Null(instance.A); + Assert.Same(b, instance.B); + Assert.Same(c, instance.C); + Assert.NotNull(instance.S); + } + + [Fact] + public void CreateInstance_NullInstance_HandlesBadInputWithInvalidOperationException() + { + var serviceCollection = new ServiceCollection() + .AddScoped(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + B? nullB = null; + + Assert.Throws(() => + ActivatorUtilities.CreateInstance(serviceProvider, nullB!, new C())); + } + + [Fact] + public void TypeActivatorThrowsOnNullProvider() + { + Assert.Throws(() => ActivatorUtilities.CreateInstance(null, "hello")); + } + + [Fact] + public void CreateInstance_ClassWithABCS_UsesTheLongestAvailableConstructor_ParameterOrderDoesntMatter() + { + var services = new ServiceCollection(); + services.AddScoped(); + services.AddScoped(); + using var provider = services.BuildServiceProvider(); + var a = new A(); + var c = new C(); + + var instance = ActivatorUtilities.CreateInstance(provider, c, a); + + Assert.NotNull(instance.B); + Assert.NotNull(instance.S); + Assert.Same(a, instance.A); + Assert.Same(c, instance.C); + } + + [Theory] + [InlineData(typeof(ClassWithABC_DefaultConstructorFirst))] + [InlineData(typeof(ClassWithABC_DefaultConstructorLast))] + public void CreateInstance_ClassWithABC_ChoosesDefaultConstructorNoMatterCtorOrder(Type instanceType) + { + var services = new ServiceCollection(); + using var provider = services.BuildServiceProvider(); + + var instance = ActivatorUtilities.CreateInstance(provider, instanceType) as ClassWithABC; + + Assert.NotNull(instance); + Assert.Null(instance.A); + Assert.Null(instance.B); + } + + [Fact] + public void CreateInstance_ClassWithABCS_BNotRegistered_UsesLongestPossibleCtorTakingAllRegisteredAndPassedParameters() + { + var services = new ServiceCollection(); + services.AddScoped(); + using var provider = services.BuildServiceProvider(); + var a = new A(); + var c = new C(); + + var instance = ActivatorUtilities.CreateInstance(provider, c, a); + + Assert.Same(a, instance.A); + Assert.Same(c, instance.C); + Assert.NotNull(instance.S); + Assert.Null(instance.B); + } + + [Theory] + [InlineData(typeof(ClassWithABC_FirstConstructorWithAttribute))] + [InlineData(typeof(ClassWithABC_LastConstructorWithAttribute))] + public void CreateInstance_ClassWithABC_ConstructorWithAttribute_PicksCtorWithAttr_NoMatterDefinitionOrder(Type instanceType) + { + var services = new ServiceCollection(); + var a = new A(); + services.AddSingleton(a); + using var provider = services.BuildServiceProvider(); + + var instance = (ClassWithABC)ActivatorUtilities.CreateInstance(provider, instanceType, new B(), new C()); + + Assert.Same(a, instance.A); + } + + [Fact] + public void CreateInstance_ClassWithABC_MultipleCtorsWithSameLength_ThrowsAmbiguous() + { + string message = $"Multiple constructors for type '{typeof(ClassWithABC_MultipleCtorsWithSameLength)}' were found with length 1."; + var services = new ServiceCollection(); + var a = new A(); + var b = new B(); + services.AddSingleton(a); + services.AddSingleton(b); + using var provider = services.BuildServiceProvider(); + + var exception = Assert.Throws(() => + ActivatorUtilities.CreateInstance(provider)); + Assert.Equal(message, exception.Message); + } + } + + internal class A { } + internal class B { } + internal class C { } + internal class S { } + + internal class ClassWithABCS : ClassWithABC + { + public S S { get; } + public ClassWithABCS(A a, B b, C c, S s) : base (a, b, c) { S = s; } + public ClassWithABCS(A a, C c, S s) : this (a, null, c, s) { } + } + + internal class ClassWithABC_FirstConstructorWithAttribute : ClassWithABC + { + [ActivatorUtilitiesConstructor] + public ClassWithABC_FirstConstructorWithAttribute(A a, B b, C c) : base(a, b, c) { } + public ClassWithABC_FirstConstructorWithAttribute(B b, C c) : this(null, b, c) { } + } + + internal class ClassWithABC_LastConstructorWithAttribute : ClassWithABC + { + public ClassWithABC_LastConstructorWithAttribute(B b, C c) : this(null, b, c) { } + [ActivatorUtilitiesConstructor] + public ClassWithABC_LastConstructorWithAttribute(A a, B b, C c) : base(a, b , c) { } + } + + internal class FakeServiceProvider : IServiceProvider + { + private IServiceProvider _inner; + private IServiceCollection _services; + public IServiceCollection Services => _services; + public FakeIServiceProviderIsService FakeServiceProviderIsService { get; set; } = new FakeIServiceProviderIsService(); + + public object GetService(Type serviceType) + { + if (serviceType == typeof(IServiceProviderIsService)) + { + return FakeServiceProviderIsService; + } + + return _inner.GetService(serviceType); + } + + public void Populate(IServiceCollection services) + { + _services = services; + _services.AddSingleton(this); + _services.AddSingleton((p) => (IServiceProviderIsService)FakeServiceProviderIsService); + } + + public void Build() + { + _inner = _services.BuildServiceProvider(); + } + } + + internal class FakeIServiceProviderIsService : IServiceProviderIsService + { + public FakeIServiceProviderIsService() { } + public bool IsServiceGotCalled { get; set; } + public bool IsService(Type serviceType) { IsServiceGotCalled = true; return false; } + } + + internal class ClassWithA + { + public A A { get; } + public ClassWithA(A a) + { + A = a; + } + } + + internal class ABCS + { + public A A { get; } + public B B { get; } + public C C { get; } + public S S { get; } + + public ABCS(A a, B b, C c) + { + A = a; + B = b; + C = c; + } + + public ABCS(B b, C c, S s) + { + B = b; + C = c; + S = s; + } + } + + internal class ABCS1 : ABCS + { + public ABCS1(A a, B b, C c) : base(a, b, c) { } + public ABCS1(B b, C c, S s) : base(b, c, s) { } + } + + internal class ABCS2 : ABCS + { + public ABCS2(B b, A a, C c) : base(a, b, c) { } + public ABCS2(B b, S s, C c) : base(b, c, s) { } + } + + internal class ABCS3 : ABCS + { + public ABCS3(B b, S s, C c) : base(b, c, s) { } + public ABCS3(A a, B b, C c) : base(a, b, c) { } + } + + internal class ClassWithABC + { + public A A { get; } + public B B { get; } + public C C { get; } + + public ClassWithABC() { } + + public ClassWithABC(A a) + { + A = a; + } + + public ClassWithABC(A a, B b) + { + A = a; + B = b; + } + + public ClassWithABC(A a, B b, C c) + { + A = a; + B = b; + C = c; + } + } + + internal class ClassWithABC_MultipleCtorsWithSameLength : ClassWithABC + { + public ClassWithABC_MultipleCtorsWithSameLength() : base() { } + public ClassWithABC_MultipleCtorsWithSameLength(A a) : base(a, null) { } + public ClassWithABC_MultipleCtorsWithSameLength(B b) : base(null, b) { } + } + + internal class ClassWithABC_DefaultConstructorFirst : ClassWithABC + { + public ClassWithABC_DefaultConstructorFirst() : base() { } + public ClassWithABC_DefaultConstructorFirst(A a) : base(a) { } + public ClassWithABC_DefaultConstructorFirst(A a, B b) : base (a, b) { } + public ClassWithABC_DefaultConstructorFirst(A a, B b, C c) : base (a, b, c) { } + } + + internal class ClassWithABC_DefaultConstructorLast : ClassWithABC + { + public ClassWithABC_DefaultConstructorLast(A a, B b, C c) : base (a, b, c) { } + public ClassWithABC_DefaultConstructorLast(A a, B b) : base (a, b) { } + public ClassWithABC_DefaultConstructorLast(A a) : base(a) { } + public ClassWithABC_DefaultConstructorLast() : base() { } + } +}