// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.ExceptionServices; #if ActivatorUtilities_In_DependencyInjection using Microsoft.Extensions.Internal; namespace Microsoft.Extensions.DependencyInjection #else namespace Microsoft.Extensions.Internal #endif { /// /// Helper code for the various activator services. /// #if ActivatorUtilities_In_DependencyInjection public #else // Do not take a dependency on this class unless you are explicitly trying to avoid taking a // dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions. internal #endif static class ActivatorUtilities { private static readonly MethodInfo GetServiceInfo = GetMethodInfo>((sp, t, r, c) => GetService(sp, t, r, c)); /// /// Instantiate a type with constructor arguments provided directly and/or from an . /// /// The service provider used to resolve dependencies /// The type to activate /// Constructor arguments not provided by the . /// An activated object of type instanceType public static object CreateInstance(IServiceProvider provider, Type instanceType, params object[] parameters) { int bestLength = -1; var seenPreferred = false; ConstructorMatcher bestMatcher = default; if (!instanceType.GetTypeInfo().IsAbstract) { foreach (var constructor in instanceType .GetTypeInfo() .DeclaredConstructors) { if (!constructor.IsStatic && constructor.IsPublic) { var matcher = new ConstructorMatcher(constructor); var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false); var length = matcher.Match(parameters); if (isPreferred) { if (seenPreferred) { ThrowMultipleCtorsMarkedWithAttributeException(); } if (length == -1) { ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); } } if (isPreferred || bestLength < length) { bestLength = length; bestMatcher = matcher; } seenPreferred |= isPreferred; } } } if (bestLength == -1) { var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; throw new InvalidOperationException(message); } return bestMatcher.CreateInstance(provider); } /// /// Create a delegate that will instantiate a type with constructor arguments provided directly /// and/or from an . /// /// The type to activate /// /// The types of objects, in order, that will be passed to the returned function as its second parameter /// /// /// A factory that will instantiate instanceType using an /// and an argument array containing objects matching the types defined in argumentTypes /// public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes) { FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); var provider = Expression.Parameter(typeof(IServiceProvider), "provider"); var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray); var factoryLamda = Expression.Lambda>( factoryExpressionBody, provider, argumentArray); var result = factoryLamda.Compile(); return result.Invoke; } /// /// Instantiate a type with constructor arguments provided directly and/or from an . /// /// The type to activate /// The service provider used to resolve dependencies /// Constructor arguments not provided by the . /// An activated object of type T public static T CreateInstance(IServiceProvider provider, params object[] parameters) { return (T)CreateInstance(provider, typeof(T), parameters); } /// /// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly. /// /// The type of the service /// The service provider used to resolve dependencies /// The resolved service or created instance public static T GetServiceOrCreateInstance(IServiceProvider provider) { return (T)GetServiceOrCreateInstance(provider, typeof(T)); } /// /// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly. /// /// The service provider /// The type of the service /// The resolved service or created instance public static object GetServiceOrCreateInstance(IServiceProvider provider, Type type) { return provider.GetService(type) ?? CreateInstance(provider, type); } private static MethodInfo GetMethodInfo(Expression expr) { var mc = (MethodCallExpression)expr.Body; return mc.Method; } private static object GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired) { var service = sp.GetService(type); if (service == null && !isDefaultParameterRequired) { var message = $"Unable to resolve service for type '{type}' while attempting to activate '{requiredBy}'."; throw new InvalidOperationException(message); } return service; } private static Expression BuildFactoryExpression( ConstructorInfo constructor, int?[] parameterMap, Expression serviceProvider, Expression factoryArgumentArray) { var constructorParameters = constructor.GetParameters(); var constructorArguments = new Expression[constructorParameters.Length]; for (var i = 0; i < constructorParameters.Length; i++) { var constructorParameter = constructorParameters[i]; var parameterType = constructorParameter.ParameterType; var hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out var defaultValue); if (parameterMap[i] != null) { constructorArguments[i] = Expression.ArrayAccess(factoryArgumentArray, Expression.Constant(parameterMap[i])); } else { var parameterTypeExpression = new Expression[] { serviceProvider, Expression.Constant(parameterType, typeof(Type)), Expression.Constant(constructor.DeclaringType, typeof(Type)), Expression.Constant(hasDefaultValue) }; constructorArguments[i] = Expression.Call(GetServiceInfo, parameterTypeExpression); } // Support optional constructor arguments by passing in the default value // when the argument would otherwise be null. if (hasDefaultValue) { var defaultValueExpression = Expression.Constant(defaultValue); constructorArguments[i] = Expression.Coalesce(constructorArguments[i], defaultValueExpression); } constructorArguments[i] = Expression.Convert(constructorArguments[i], parameterType); } return Expression.New(constructor, constructorArguments); } private static void FindApplicableConstructor( Type instanceType, Type[] argumentTypes, out ConstructorInfo matchingConstructor, out int?[] parameterMap) { matchingConstructor = null; parameterMap = null; if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap) && !TryFindMatchingConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap)) { var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; throw new InvalidOperationException(message); } } // Tries to find constructor based on provided argument types private static bool TryFindMatchingConstructor( Type instanceType, Type[] argumentTypes, ref ConstructorInfo matchingConstructor, ref int?[] parameterMap) { foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors) { if (constructor.IsStatic || !constructor.IsPublic) { continue; } if (TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) { 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."); } matchingConstructor = constructor; parameterMap = tempParameterMap; } } return matchingConstructor != null; } // Tries to find constructor marked with ActivatorUtilitiesConstructorAttribute private static bool TryFindPreferredConstructor( Type instanceType, Type[] argumentTypes, ref ConstructorInfo matchingConstructor, ref int?[] parameterMap) { var seenPreferred = false; foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors) { if (constructor.IsStatic || !constructor.IsPublic) { continue; } if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false)) { if (seenPreferred) { ThrowMultipleCtorsMarkedWithAttributeException(); } if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) { ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); } matchingConstructor = constructor; parameterMap = tempParameterMap; seenPreferred = true; } } return matchingConstructor != null; } // 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) { parameterMap = new int?[constructorParameters.Length]; for (var i = 0; i < argumentTypes.Length; i++) { var foundMatch = false; var givenParameter = argumentTypes[i].GetTypeInfo(); for (var j = 0; j < constructorParameters.Length; j++) { if (parameterMap[j] != null) { // This ctor parameter has already been matched continue; } if (constructorParameters[j].ParameterType.GetTypeInfo().IsAssignableFrom(givenParameter)) { foundMatch = true; parameterMap[j] = i; break; } } if (!foundMatch) { return false; } } return true; } private struct ConstructorMatcher { private readonly ConstructorInfo _constructor; private readonly ParameterInfo[] _parameters; private readonly object[] _parameterValues; public ConstructorMatcher(ConstructorInfo constructor) { _constructor = constructor; _parameters = _constructor.GetParameters(); _parameterValues = new object[_parameters.Length]; } public int Match(object[] givenParameters) { var applyIndexStart = 0; var applyExactLength = 0; for (var givenIndex = 0; givenIndex != givenParameters.Length; givenIndex++) { var givenType = givenParameters[givenIndex]?.GetType().GetTypeInfo(); var givenMatched = false; for (var applyIndex = applyIndexStart; givenMatched == false && applyIndex != _parameters.Length; ++applyIndex) { if (_parameterValues[applyIndex] == null && _parameters[applyIndex].ParameterType.GetTypeInfo().IsAssignableFrom(givenType)) { givenMatched = true; _parameterValues[applyIndex] = givenParameters[givenIndex]; if (applyIndexStart == applyIndex) { applyIndexStart++; if (applyIndex == givenIndex) { applyExactLength = applyIndex; } } } } if (givenMatched == false) { return -1; } } return applyExactLength; } public object CreateInstance(IServiceProvider provider) { for (var index = 0; index != _parameters.Length; index++) { if (_parameterValues[index] == null) { var value = provider.GetService(_parameters[index].ParameterType); if (value == null) { if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out var defaultValue)) { throw new InvalidOperationException($"Unable to resolve service for type '{_parameters[index].ParameterType}' while attempting to activate '{_constructor.DeclaringType}'."); } else { _parameterValues[index] = defaultValue; } } else { _parameterValues[index] = value; } } } #if NETCOREAPP3_0 return _constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null); #else try { return _constructor.Invoke(_parameterValues); } catch (TargetInvocationException ex) when (ex.InnerException != null) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); // The above line will always throw, but the compiler requires we throw explicitly. throw; } #endif } } private static void ThrowMultipleCtorsMarkedWithAttributeException() { throw new InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}."); } private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments() { throw new InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types."); } } }