Exemple #1
0
 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);
         }
     }
 }
Exemple #2
0
        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);
        }
Exemple #7
0
        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);
        }
Exemple #9
0
        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);
                }
            }
        }
Exemple #10
0
        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);
            }
        }
Exemple #11
0
        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);
        }
Exemple #12
0
        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);
        }
Exemple #13
0
        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)));
        }
Exemple #14
0
        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);
                    }
                }
            }
        }
Exemple #15
0
        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);
            }
        }