netcore3.0 IConfiguration配置源码解析(三)
前面两篇文章主要讲到netcore的配置以及各种配置源。
本篇主要讲到把配置值转换成C#的实体类,体现在IConfiguration各种扩展方法:
public static class ConfigurationBinder { /// <summary> /// Attempts to bind the configuration instance to a new instance of type T. /// If this configuration section has a value, that will be used. /// Otherwise binding by matching property names against configuration keys recursively. /// </summary> /// <typeparam name="T">The type of the new instance to bind.</typeparam> /// <param name="configuration">The configuration instance to bind.</param> /// <returns>The new instance of T if successful, default(T) otherwise.</returns> public static T Get<T>(this IConfiguration configuration) => configuration.Get<T>(_ => { }); /// <summary> /// Attempts to bind the configuration instance to a new instance of type T. /// If this configuration section has a value, that will be used. /// Otherwise binding by matching property names against configuration keys recursively. /// </summary> /// <typeparam name="T">The type of the new instance to bind.</typeparam> /// <param name="configuration">The configuration instance to bind.</param> /// <param name="configureOptions">Configures the binder options.</param> /// <returns>The new instance of T if successful, default(T) otherwise.</returns> public static T Get<T>(this IConfiguration configuration, Action<BinderOptions> configureOptions) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } var result = configuration.Get(typeof(T), configureOptions); if (result == null) { return default(T); } return (T)result; } /// <summary> /// Attempts to bind the configuration instance to a new instance of type T. /// If this configuration section has a value, that will be used. /// Otherwise binding by matching property names against configuration keys recursively. /// </summary> /// <param name="configuration">The configuration instance to bind.</param> /// <param name="type">The type of the new instance to bind.</param> /// <returns>The new instance if successful, null otherwise.</returns> public static object Get(this IConfiguration configuration, Type type) => configuration.Get(type, _ => { }); /// <summary> /// Attempts to bind the configuration instance to a new instance of type T. /// If this configuration section has a value, that will be used. /// Otherwise binding by matching property names against configuration keys recursively. /// </summary> /// <param name="configuration">The configuration instance to bind.</param> /// <param name="type">The type of the new instance to bind.</param> /// <param name="configureOptions">Configures the binder options.</param> /// <returns>The new instance if successful, null otherwise.</returns> public static object Get(this IConfiguration configuration, Type type, Action<BinderOptions> configureOptions) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } var options = new BinderOptions(); configureOptions?.Invoke(options); return BindInstance(type, instance: null, config: configuration, options: options); } /// <summary> /// Attempts to bind the given object instance to the configuration section specified by the key by matching property names against configuration keys recursively. /// </summary> /// <param name="configuration">The configuration instance to bind.</param> /// <param name="key">The key of the configuration section to bind.</param> /// <param name="instance">The object to bind.</param> public static void Bind(this IConfiguration configuration, string key, object instance) => configuration.GetSection(key).Bind(instance); /// <summary> /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. /// </summary> /// <param name="configuration">The configuration instance to bind.</param> /// <param name="instance">The object to bind.</param> public static void Bind(this IConfiguration configuration, object instance) => configuration.Bind(instance, o => { }); /// <summary> /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. /// </summary> /// <param name="configuration">The configuration instance to bind.</param> /// <param name="instance">The object to bind.</param> /// <param name="configureOptions">Configures the binder options.</param> public static void Bind(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } if (instance != null) { var options = new BinderOptions(); configureOptions?.Invoke(options); BindInstance(instance.GetType(), instance, configuration, options); } } /// <summary> /// Extracts the value with the specified key and converts it to type T. /// </summary> /// <typeparam name="T">The type to convert the value to.</typeparam> /// <param name="configuration">The configuration.</param> /// <param name="key">The key of the configuration section's value to convert.</param> /// <returns>The converted value.</returns> public static T GetValue<T>(this IConfiguration configuration, string key) { return GetValue(configuration, key, default(T)); } /// <summary> /// Extracts the value with the specified key and converts it to type T. /// </summary> /// <typeparam name="T">The type to convert the value to.</typeparam> /// <param name="configuration">The configuration.</param> /// <param name="key">The key of the configuration section's value to convert.</param> /// <param name="defaultValue">The default value to use if no value is found.</param> /// <returns>The converted value.</returns> public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue) { return (T)GetValue(configuration, typeof(T), key, defaultValue); } /// <summary> /// Extracts the value with the specified key and converts it to the specified type. /// </summary> /// <param name="configuration">The configuration.</param> /// <param name="type">The type to convert the value to.</param> /// <param name="key">The key of the configuration section's value to convert.</param> /// <returns>The converted value.</returns> public static object GetValue(this IConfiguration configuration, Type type, string key) { return GetValue(configuration, type, key, defaultValue: null); } /// <summary> /// Extracts the value with the specified key and converts it to the specified type. /// </summary> /// <param name="configuration">The configuration.</param> /// <param name="type">The type to convert the value to.</param> /// <param name="key">The key of the configuration section's value to convert.</param> /// <param name="defaultValue">The default value to use if no value is found.</param> /// <returns>The converted value.</returns> public static object GetValue(this IConfiguration configuration, Type type, string key, object defaultValue) { var section = configuration.GetSection(key); var value = section.Value; if (value != null) { return ConvertValue(type, value, section.Path); } return defaultValue; } 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 object BindToCollection(TypeInfo typeInfo, IConfiguration config, BinderOptions options) { var type = typeof(List<>).MakeGenericType(typeInfo.GenericTypeArguments[0]); var instance = Activator.CreateInstance(type); BindCollection(instance, type, config, options); return instance; } // Try to create an array/dictionary instance to back various collection interfaces private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options) { var typeInfo = type.GetTypeInfo(); if (!typeInfo.IsInterface) { return null; } var 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) { var dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeInfo.GenericTypeArguments[0], typeInfo.GenericTypeArguments[1]); var instance = Activator.CreateInstance(dictionaryType); BindDictionary(instance, dictionaryType, config, options); return instance; } collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type); if (collectionInterface != null) { var 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; var 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 var 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 CreateInstance(Type type) { var typeInfo = type.GetTypeInfo(); if (typeInfo.IsInterface || typeInfo.IsAbstract) { throw new InvalidOperationException(Resources.FormatError_CannotActivateAbstractOrInterface(type)); } if (type.IsArray) { if (typeInfo.GetArrayRank() > 1) { throw new InvalidOperationException(Resources.FormatError_UnsupportedMultidimensionalArray(type)); } return Array.CreateInstance(typeInfo.GetElementType(), 0); } var hasDefaultConstructor = typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); if (!hasDefaultConstructor) { throw new InvalidOperationException(Resources.FormatError_MissingParameterlessConstructor(type)); } try { return Activator.CreateInstance(type); } catch (Exception ex) { throw new InvalidOperationException(Resources.FormatError_FailedToActivate(type), ex); } } private static void BindDictionary(object dictionary, Type dictionaryType, IConfiguration config, BinderOptions options) { var typeInfo = dictionaryType.GetTypeInfo(); // IDictionary<K,V> is guaranteed to have exactly two parameters var keyType = typeInfo.GenericTypeArguments[0]; var valueType = typeInfo.GenericTypeArguments[1]; var keyTypeIsEnum = keyType.GetTypeInfo().IsEnum; if (keyType != typeof(string) && !keyTypeIsEnum) { // We only support string and enum keys return; } var setter = typeInfo.GetDeclaredProperty("Item"); foreach (var child in config.GetChildren()) { var item = BindInstance( type: valueType, instance: null, config: child, options: options); if (item != null) { if (keyType == typeof(string)) { var key = child.Key; setter.SetValue(dictionary, item, new object[] { key }); } else if (keyTypeIsEnum) { var 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) { var typeInfo = collectionType.GetTypeInfo(); // ICollection<T> is guaranteed to have exactly one parameter var itemType = typeInfo.GenericTypeArguments[0]; var addMethod = typeInfo.GetDeclaredMethod("Add"); foreach (var section in config.GetChildren()) { try { var item = BindInstance( type: itemType, instance: null, config: section, options: options); if (item != null) { addMethod.Invoke(collection, new[] { item }); } } catch { } } } private static Array BindArray(Array source, IConfiguration config, BinderOptions options) { var children = config.GetChildren().ToArray(); var arrayLength = source.Length; var elementType = source.GetType().GetElementType(); var newArray = Array.CreateInstance(elementType, arrayLength + children.Length); // binding to array has to preserve already initialized arrays with values if (arrayLength > 0) { Array.Copy(source, newArray, arrayLength); } for (int i = 0; i < children.Length; i++) { try { var item = BindInstance( type: elementType, instance: null, config: children[i], options: options); if (item != null) { newArray.SetValue(item, arrayLength + i); } } catch { } } return newArray; } private static bool TryConvertValue(Type type, string value, string path, out object result, out Exception error) { error = null; result = null; if (type == typeof(object)) { result = value; return true; } if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { if (string.IsNullOrEmpty(value)) { return true; } return TryConvertValue(Nullable.GetUnderlyingType(type), value, path, out result, out error); } var converter = TypeDescriptor.GetConverter(type); if (converter.CanConvertFrom(typeof(string))) { try { result = converter.ConvertFromInvariantString(value); } catch (Exception ex) { error = new InvalidOperationException(Resources.FormatError_FailedBinding(path, type), ex); } return true; } return false; } private static object ConvertValue(Type type, string value, string path) { object result; Exception error; TryConvertValue(type, value, path, out result, out error); if (error != null) { throw error; } return result; } private static Type FindOpenGenericInterface(Type expected, Type actual) { var actualTypeInfo = actual.GetTypeInfo(); if(actualTypeInfo.IsGenericType && actual.GetGenericTypeDefinition() == expected) { return actual; } var interfaces = actualTypeInfo.ImplementedInterfaces; foreach (var interfaceType in interfaces) { if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == expected) { return interfaceType; } } return null; } private static IEnumerable<PropertyInfo> GetAllProperties(TypeInfo type) { var allProperties = new List<PropertyInfo>(); do { allProperties.AddRange(type.DeclaredProperties); type = type.BaseType.GetTypeInfo(); } while (type != typeof(object).GetTypeInfo()); return allProperties; } }
1、绑定简单类型
如果IConfiguration为IConfigurationSection类型,且Value不为null
如果绑定的类型为object,则直接返回Value
如果绑定类型为Nullable<>,则返回对应的类型
获取绑定的类型的TypeConverter,如果可以和字符串转换,则返回
2、如果IConfiguration为IConfigurationSection类型且GetChildren()有任意值
绑定集合:
如果类型实现IReadOnlyList<>,IReadOnlyCollection<>,ICollection<>,IEnumerable<>
绑定字典:
如果类型实现IReadOnlyDictionary,Dictionary<,>