private static void BindNonScalar(this IConfiguration configuration, object instance, BinderOptions options) { if (instance != null) { foreach (var property in GetAllProperties(instance.GetType().GetTypeInfo())) { BindProperty(property, instance, configuration, options); } } }
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options) { // We don't support set only, non public, or indexer properties if (property.GetMethod == null || (!options.BindNonPublicProperties && !property.GetMethod.IsPublic) || property.GetMethod.GetParameters().Length > 0) { return; } var propertyValue = property.GetValue(instance); var hasSetter = property.SetMethod != null && (property.SetMethod.IsPublic || options.BindNonPublicProperties); if (propertyValue == null && !hasSetter) { // Property doesn't have a value and we cannot set it so there is no // point in going further down the graph return; } propertyValue = BindInstance(property.PropertyType, propertyValue, config.GetSection(property.Name), options); if (propertyValue != null && hasSetter) { property.SetValue(instance, propertyValue); } }
private static void BindDictionary(object dictionary, Type dictionaryType, IConfiguration config, BinderOptions options) { TypeInfo typeInfo = dictionaryType.GetTypeInfo(); // IDictionary<K,V> is guaranteed to have exactly two parameters Type keyType = typeInfo.GenericTypeArguments[0]; Type valueType = typeInfo.GenericTypeArguments[1]; bool keyTypeIsEnum = keyType.GetTypeInfo().IsEnum; if (keyType != typeof(string) && !keyTypeIsEnum) { // We only support string and enum keys return; } PropertyInfo setter = typeInfo.GetDeclaredProperty("Item"); foreach (IConfigurationSection child in config.GetChildren()) { object item = BindInstance( type: valueType, instance: null, config: child, options: options); if (item != null) { if (keyType == typeof(string)) { string key = child.Key; setter.SetValue(dictionary, item, new object[] { key }); } else if (keyTypeIsEnum) { object key = Enum.Parse(keyType, child.Key); setter.SetValue(dictionary, item, new object[] { key }); } } } }
private static void BindCollection(object collection, Type collectionType, IConfiguration config, BinderOptions options) { TypeInfo typeInfo = collectionType.GetTypeInfo(); // ICollection<T> is guaranteed to have exactly one parameter Type itemType = typeInfo.GenericTypeArguments[0]; MethodInfo addMethod = typeInfo.GetDeclaredMethod("Add"); foreach (IConfigurationSection section in config.GetChildren()) { try { object item = BindInstance( type: itemType, instance: null, config: section, options: options); if (item != null) { addMethod.Invoke(collection, new[] { item }); } } catch { } } }
// Try to create an array/dictionary instance to back various collection interfaces private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options) { TypeInfo typeInfo = type.GetTypeInfo(); if (!typeInfo.IsInterface) { return(null); } Type collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList <>), type); if (collectionInterface != null) { // IEnumerable<T> is guaranteed to have exactly one parameter return(BindToCollection(typeInfo, config, options)); } collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary <,>), type); if (collectionInterface != null) { Type dictionaryType = typeof(Dictionary <,>).MakeGenericType(typeInfo.GenericTypeArguments[0], typeInfo.GenericTypeArguments[1]); object instance = Activator.CreateInstance(dictionaryType); BindDictionary(instance, dictionaryType, config, options); return(instance); } collectionInterface = FindOpenGenericInterface(typeof(IDictionary <,>), type); if (collectionInterface != null) { object instance = Activator.CreateInstance(typeof(Dictionary <,>).MakeGenericType(typeInfo.GenericTypeArguments[0], typeInfo.GenericTypeArguments[1])); BindDictionary(instance, collectionInterface, config, options); return(instance); } collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection <>), type); if (collectionInterface != null) { // IReadOnlyCollection<T> is guaranteed to have exactly one parameter return(BindToCollection(typeInfo, config, options)); } collectionInterface = FindOpenGenericInterface(typeof(ICollection <>), type); if (collectionInterface != null) { // ICollection<T> is guaranteed to have exactly one parameter return(BindToCollection(typeInfo, config, options)); } collectionInterface = FindOpenGenericInterface(typeof(IEnumerable <>), type); if (collectionInterface != null) { // IEnumerable<T> is guaranteed to have exactly one parameter return(BindToCollection(typeInfo, config, options)); } return(null); }
private static object BindInstance(Type type, object instance, IConfiguration config, BinderOptions options) { // if binding IConfigurationSection, break early if (type == typeof(IConfigurationSection)) { return(config); } var section = config as IConfigurationSection; string configValue = section?.Value; object convertedValue; Exception error; if (configValue != null && TryConvertValue(type, configValue, section.Path, out convertedValue, out error)) { if (error != null) { throw error; } // Leaf nodes are always reinitialized return(convertedValue); } if (config != null && config.GetChildren().Any()) { // If we don't have an instance, try to create one if (instance == null) { // We are already done if binding to a new collection instance worked instance = AttemptBindToCollectionInterfaces(type, config, options); if (instance != null) { return(instance); } instance = CreateInstance(type); } // See if its a Dictionary Type collectionInterface = FindOpenGenericInterface(typeof(IDictionary <,>), type); if (collectionInterface != null) { BindDictionary(instance, collectionInterface, config, options); } else if (type.IsArray) { instance = BindArray((Array)instance, config, options); } else { // See if its an ICollection collectionInterface = FindOpenGenericInterface(typeof(ICollection <>), type); if (collectionInterface != null) { BindCollection(instance, collectionInterface, config, options); } // Something else else { BindNonScalar(config, instance, options); } } } return(instance); }
private static object?GetPropertyValue(PropertyInfo property, object instance, IConfiguration config, BinderOptions options) { string propertyName = GetPropertyName(property); return(BindInstance( property.PropertyType, property.GetValue(instance), config.GetSection(propertyName), options)); }
private static object BindToCollection(TypeInfo typeInfo, IConfiguration config, BinderOptions options) { Type type = typeof(List <>).MakeGenericType(typeInfo.GenericTypeArguments[0]); object instance = Activator.CreateInstance(type); BindCollection(instance, type, config, options); return(instance); }
private static void BindNonScalar(this IConfiguration configuration, object?instance, BinderOptions options) { if (instance != null) { List <PropertyInfo> modelProperties = GetAllProperties(instance.GetType()); if (options.ErrorOnUnknownConfiguration) { HashSet <string> propertyNames = new(modelProperties.Select(mp => mp.Name), StringComparer.OrdinalIgnoreCase); IEnumerable <IConfigurationSection> configurationSections = configuration.GetChildren(); List <string> missingPropertyNames = configurationSections .Where(cs => !propertyNames.Contains(cs.Key)) .Select(mp => $"'{mp.Key}'") .ToList(); if (missingPropertyNames.Count > 0) { throw new InvalidOperationException(SR.Format(SR.Error_MissingConfig, nameof(options.ErrorOnUnknownConfiguration), nameof(BinderOptions), instance.GetType(), string.Join(", ", missingPropertyNames))); } } foreach (PropertyInfo property in modelProperties) { BindProperty(property, instance, configuration, options); } } }
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options) { // We don't support set only, non public, or indexer properties if (property.GetMethod == null || (!options.BindNonPublicProperties && !property.GetMethod.IsPublic) || property.GetMethod.GetParameters().Length > 0) { return; } bool hasSetter = property.SetMethod != null && (property.SetMethod.IsPublic || options.BindNonPublicProperties); if (!hasSetter) { // The property cannot be set so there is no point going further return; } object?propertyValue = GetPropertyValue(property, instance, config, options); if (propertyValue != null) { property.SetValue(instance, propertyValue); } }
private static object?BindSet(Type type, IEnumerable?source, IConfiguration config, BinderOptions options) { Type elementType = type.GetGenericArguments()[0]; Type keyType = type.GenericTypeArguments[0]; bool keyTypeIsEnum = keyType.IsEnum; if (keyType != typeof(string) && !keyTypeIsEnum) { // We only support string and enum keys return(null); } Type genericType = typeof(HashSet <>).MakeGenericType(keyType); object instance = Activator.CreateInstance(genericType) !; MethodInfo addMethod = genericType.GetMethod("Add", DeclaredOnlyLookup) !; object?[] arguments = new object?[1]; if (source != null) { foreach (object?item in source) { arguments[0] = item; addMethod.Invoke(instance, arguments); } } foreach (IConfigurationSection section in config.GetChildren()) { var itemBindingPoint = new BindingPoint(); try { BindInstance( type: elementType, bindingPoint: itemBindingPoint, config: section, options: options); if (itemBindingPoint.HasNewValue) { arguments[0] = itemBindingPoint.Value; addMethod.Invoke(instance, arguments); } } catch { } } return(instance); }
private static Array BindArray(Type type, IEnumerable?source, IConfiguration config, BinderOptions options) { Type elementType; if (type.IsArray) { if (type.GetArrayRank() > 1) { throw new InvalidOperationException(SR.Format(SR.Error_UnsupportedMultidimensionalArray, type)); } elementType = type.GetElementType() !; } else // e. g. IEnumerable<T> { elementType = type.GetGenericArguments()[0]; } IList list = new List <object?>(); if (source != null) { foreach (object?item in source) { list.Add(item); } } foreach (IConfigurationSection section in config.GetChildren()) { var itemBindingPoint = new BindingPoint(); try { BindInstance( type: elementType, bindingPoint: itemBindingPoint, config: section, options: options); if (itemBindingPoint.HasNewValue) { list.Add(itemBindingPoint.Value); } } catch { } } Array result = Array.CreateInstance(elementType, list.Count); list.CopyTo(result, 0); return(result); }
private static object CreateInstance( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type, IConfiguration config, BinderOptions options) { Debug.Assert(!type.IsArray); if (type.IsInterface || type.IsAbstract) { throw new InvalidOperationException(SR.Format(SR.Error_CannotActivateAbstractOrInterface, type)); } ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); bool hasParameterlessConstructor = type.IsValueType || constructors.Any(ctor => ctor.GetParameters().Length == 0); if (!type.IsValueType && constructors.Length == 0) { throw new InvalidOperationException(SR.Format(SR.Error_MissingPublicInstanceConstructor, type)); } if (constructors.Length > 1 && !hasParameterlessConstructor) { throw new InvalidOperationException(SR.Format(SR.Error_MultipleParameterizedConstructors, type)); } if (constructors.Length == 1 && !hasParameterlessConstructor) { ConstructorInfo constructor = constructors[0]; ParameterInfo[] parameters = constructor.GetParameters(); if (!CanBindToTheseConstructorParameters(parameters, out string nameOfInvalidParameter)) { throw new InvalidOperationException(SR.Format(SR.Error_CannotBindToConstructorParameter, type, nameOfInvalidParameter)); } List <PropertyInfo> properties = GetAllProperties(type); if (!DoAllParametersHaveEquivalentProperties(parameters, properties, out string nameOfInvalidParameters)) { throw new InvalidOperationException(SR.Format(SR.Error_ConstructorParametersDoNotMatchProperties, type, nameOfInvalidParameters)); } object?[] parameterValues = new object?[parameters.Length]; for (int index = 0; index < parameters.Length; index++) { parameterValues[index] = BindParameter(parameters[index], type, config, options); } return(constructor.Invoke(parameterValues)); } object?instance; try { instance = Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type); } catch (Exception ex) { throw new InvalidOperationException(SR.Format(SR.Error_FailedToActivate, type), ex); } return(instance ?? throw new InvalidOperationException(SR.Format(SR.Error_FailedToActivate, type))); }
private static void BindInstance( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, BindingPoint bindingPoint, IConfiguration config, BinderOptions options) { // if binding IConfigurationSection, break early if (type == typeof(IConfigurationSection)) { bindingPoint.TrySetValue(config); return; } var section = config as IConfigurationSection; string?configValue = section?.Value; if (configValue != null && TryConvertValue(type, configValue, section?.Path, out object?convertedValue, out Exception? error)) { if (error != null) { throw error; } // Leaf nodes are always reinitialized bindingPoint.TrySetValue(convertedValue); return; } if (config != null && config.GetChildren().Any()) { // for arrays, collections, and read-only list-like interfaces, we concatenate on to what is already there if (type.IsArray || IsArrayCompatibleInterface(type)) { if (!bindingPoint.IsReadOnly) { bindingPoint.SetValue(BindArray(type, (IEnumerable?)bindingPoint.Value, config, options)); } return; } // for sets and read-only set interfaces, we clone what's there into a new collection. if (TypeIsASetInterface(type)) { if (!bindingPoint.IsReadOnly) { object?newValue = BindSet(type, (IEnumerable?)bindingPoint.Value, config, options); if (newValue != null) { bindingPoint.SetValue(newValue); } } return; } // For other mutable interfaces like ICollection<>, IDictionary<,> and ISet<>, we prefer copying values and setting them // on a new instance of the interface over populating the existing instance implementing the interface. // This has already been done, so there's not need to check again. if (TypeIsADictionaryInterface(type)) { if (!bindingPoint.IsReadOnly) { object?newValue = BindDictionaryInterface(bindingPoint.Value, type, config, options); if (newValue != null) { bindingPoint.SetValue(newValue); } } return; } // If we don't have an instance, try to create one if (bindingPoint.Value is null) { // if the binding point doesn't let us set a new instance, there's nothing more we can do if (bindingPoint.IsReadOnly) { return; } // For other mutable interfaces like ICollection<> and ISet<>, we prefer copying values and setting them // on a new instance of the interface over populating the existing instance implementing the interface. // This has already been done, so there's not need to check again. For dictionaries, we fill the existing // instance if there is one (which hasn't happened yet), and only create a new instance if necessary. bindingPoint.SetValue(CreateInstance(type, config, options)); } // At this point we know that we have a non-null bindingPoint.Value, we just have to populate the items // using the IDictionary<> or ICollection<> interfaces, or properties using reflection. Type?dictionaryInterface = FindOpenGenericInterface(typeof(IDictionary <,>), type); if (dictionaryInterface != null) { BindConcreteDictionary(bindingPoint.Value !, dictionaryInterface, config, options); } else { Type?collectionInterface = FindOpenGenericInterface(typeof(ICollection <>), type); if (collectionInterface != null) { BindCollection(bindingPoint.Value !, collectionInterface, config, options); } else { BindProperties(bindingPoint.Value !, config, options); } } } }
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options) { // We don't support set only, non public, or indexer properties if (property.GetMethod == null || (!options.BindNonPublicProperties && !property.GetMethod.IsPublic) || property.GetMethod.GetParameters().Length > 0) { return; } var propertyBindingPoint = new BindingPoint( initialValueProvider: () => property.GetValue(instance), isReadOnly: property.SetMethod is null || (!property.SetMethod.IsPublic && !options.BindNonPublicProperties)); BindInstance( property.PropertyType, propertyBindingPoint, config.GetSection(GetPropertyName(property)), options); if (propertyBindingPoint.HasNewValue) { property.SetValue(instance, propertyBindingPoint.Value); } }