private (ConstructorInfo, Func <ParameterInfo[], IEnumerable <(IConfigurationSection, Object)> >) DeduceConstructor( TConfigurationTypeLoader configurationTypeLoader, TypeInfo loadedType, IConfigurationSection ctorConfig ) { ConstructorInfo suitableCtor; Func <ParameterInfo[], IEnumerable <(IConfigurationSection, Object)> > ctorArgs; var possibleCtors = loadedType.DeclaredConstructors.Where(ctor => ctor.IsPublic && !ctor.IsStatic); Type singleConfigType; if ((singleConfigType = configurationTypeLoader(loadedType)) != null) { suitableCtor = possibleCtors.FirstOrDefault(ctor => { var paramz = ctor.GetParameters(); return(paramz.Length == 1 && paramz[0].ParameterType.GetTypeInfo().IsAssignableFrom(singleConfigType.GetTypeInfo())); }); ctorArgs = paramz => Enumerable.Repeat((ctorConfig, ctorConfig.Get(singleConfigType)), 1); } else { var children = ctorConfig.GetChildren().ToArray(); if (children.Length > 0) { if (children.All(child => Int32.TryParse(child.Key, out var ignored))) { // This is an array - need to find constructor which accepts given amount of parameters suitableCtor = possibleCtors .Where(ctor => { var paramz = ctor.GetParameters(); return(paramz.Length >= children.Length && paramz.Skip(children.Length).All(p => p.IsOptional)); }) .OrderBy(c => c.GetParameters().Length) .FirstOrDefault(); ctorArgs = paramz => children.Select((c, idx) => (c, c.Get(paramz[idx].ParameterType))); } else { // This is an object - try to find constructor with all required parameters suitableCtor = possibleCtors .Where(ctor => { var paramz = ctor.GetParameters(); return(paramz.All(p => !String.IsNullOrEmpty(ctorConfig.GetSection(p.Name)?.Value))); }) .FirstOrDefault(); ctorArgs = paramz => paramz.Select(p => { var thisSection = ctorConfig.GetSection(p.Name); return(thisSection, thisSection.Get(p.ParameterType)); }); } }
/// <summary> /// Potentially asynchronously instantiates an object from type information provided in given <see cref="IConfiguration"/>. /// </summary> /// <param name="config">The configuration containing type information.</param> /// <param name="targetType">The type which should be parent type of the object to be instantiated.</param> /// <param name="configurationTypeLoader">The optional custom callback to load configuration type for given type. By default, the value of <see cref="ConfigurationTypeAttribute.ConfigurationType"/> property of the applied <see cref="ConfigurationTypeAttribute"/> is used.</param> /// <returns>The potentially asynchronous operation which results in instantiated object, or <c>null</c>.</returns> /// <remarks> /// This method takes <see cref="ConfigurationTypeAttribute"/> and <see cref="NestedDynamicConfigurationAttribute"/> attributes into account when instantiating and traversing object. /// This method is only asynchronous if <see cref="TypeLoaderDelegate"/> callback provided to this <see cref="DynamicConfigurableTypeLoader"/> is asynchronous. /// </remarks> /// <seealso cref="DefaultConfigurationTypeLoaderCallback"/> public async ValueTask <Object> InstantiateWithConfiguration( IConfiguration config, TypeInfo targetType, TConfigurationTypeLoader configurationTypeLoader = null ) { var type = await this._typeLoader(config, targetType); if (configurationTypeLoader == null) { configurationTypeLoader = DefaultConfigurationTypeLoaderCallback; } Object retVal = null; if (type != null && !type.IsInterface && !type.IsAbstract) { // Type load successful - now figure out constructor and arguments to it (var ctor, var ctorArgs) = this.DeduceConstructor(configurationTypeLoader, type, this._constructorArgumentsLoader(config, type)); if (ctor != null) { Object[] actualCtorArgs; if (ctorArgs != null) { var tuples = ctorArgs(ctor.GetParameters()).ToArray(); await Task.WhenAll(tuples .Select( tuple => this.ScanForNestedConfigs(configurationTypeLoader, tuple.Item1, tuple.Item2).AsTask()) .ToArray()); actualCtorArgs = tuples.Select(t => t.Item2).ToArray(); } else { actualCtorArgs = null; } retVal = ctor.Invoke(actualCtorArgs); } } return(retVal); }