• [Asp.net 5] Configuration-新一代的配置文件(神奇的Binder)


    关于配置文件的目录:[Asp.net 5] Configuration-新一代的配置文件

    之前看过MVC4.0的源码,里面就有Binder。作用是将前台页面传递过来的键值对/字典表绑定到特定的对象。此处的Binder几乎是同样的作用——将IConfiguration映射为特定的类型<T>.

    我们进入正文之前还是看一个例子比较好,这样能充分了解Binder。

    public class ComplexOptions
    {
        public ComplexOptions()
        {
            Virtual = "complex";
        }
        public int Integer { get; set; }
        public bool Boolean { get; set; }
        public virtual string Virtual { get; set; }
    }
    
    public class Test
    {
        public IConfiguration BuildConfiguration()
        {
            var dic = new Dictionary<string, string>
                     {
                         {"Integer", "-2"},
                         {"Boolean", "TRUe"},
                         {"Nested:Integer", "11"}
                    };
            var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
            return config = builder.Build();
        }
        public void BinderTest()
        {
            var config = BuildConfiguration();
            var options = ConfigurationBinder.Bind<ComplexOptions>(config);
            Assert.True(options.Boolean);
            Assert.Equal(-2, options.Integer);
        }
    }

    上面例子比较简单,实际上对象可能是复合对象(对象的属性还是对象),也可能是数组、枚举、结构体等。所以我们创建的过程中需要对属性遍历,如果是复合对象,还涉及到递归的过程,所以我把整个过程绘制成类似流程图的图标。如下图所示:

     

    下面我们正式介绍工程的源码,按照惯例还是上工程结构图(就那么一个文件,搞什么搞)

     

    整个工程只有ConfigurationBinder这一个类,调用过程上面“流程图”已经画出来了。

     public static class ConfigurationBinder
        {
            public static TModel Bind<TModel>(IConfiguration configuration) where TModel : new()
            {
                var model = new TModel();
                Bind(model, configuration);
                return model;
            }
    
            public static void Bind(object model, IConfiguration configuration)
            {
                if (model == null)
                {
                    return;
                }
    
                BindObjectProperties(model, configuration);
            }
    
            private static void BindObjectProperties(object obj, IConfiguration configuration)
            {
                foreach (var property in GetAllProperties(obj.GetType().GetTypeInfo()))
                {
                    BindProperty(property, obj, configuration);
                }
            }
    
            private static void BindProperty(PropertyInfo property, object propertyOwner, IConfiguration configuration)
            {
                configuration = configuration.GetConfigurationSection(property.Name);
    
                if (property.GetMethod == null || !property.GetMethod.IsPublic)
                {
                    // We don't support set only properties
                    return;
                }
    
                var propertyValue = property.GetValue(propertyOwner);
                var hasPublicSetter = property.SetMethod != null && property.SetMethod.IsPublic;
    
                if (propertyValue == null && !hasPublicSetter)
                {
                    // Property doesn't have a value and we cannot set it so there is no
                    // point in going further down the graph
                    return;
                }
    
                propertyValue = BindType(
                    property.PropertyType,
                    propertyValue,
                    configuration);
    
                if (propertyValue != null && hasPublicSetter)
                {
                    property.SetValue(propertyOwner, propertyValue);
                }
            }
    
            private static object BindType(Type type, object typeInstance, IConfiguration configuration)
            {
                var configValue = configuration.Get(null);
                var typeInfo = type.GetTypeInfo();
    
                if (configValue != null)
                {
                    // Leaf nodes are always reinitialized
                    return CreateValueFromConfiguration(type, configValue, configuration);
                }
                else
                {
                    var subkeys = configuration.GetConfigurationSections();
                    if (subkeys.Count() != 0)
                    {
                        if (typeInstance == null)
                        {
                            if (typeInfo.IsInterface || typeInfo.IsAbstract)
                            {
                                throw new InvalidOperationException(Resources.FormatError_CannotActivateAbstractOrInterface(type));
                            }
    
                            bool hasParameterlessConstructor = typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
                            if (!hasParameterlessConstructor)
                            {
                                throw new InvalidOperationException(Resources.FormatError_MissingParameterlessConstructor(type));
                            }
    
                            try
                            {
                                typeInstance = Activator.CreateInstance(type);
                            }
                            catch (Exception ex)
                            {
                                throw new InvalidOperationException(Resources.FormatError_FailedToActivate(type), ex);
                            }
                        }
    
                        var collectionInterface = GetGenericOpenInterfaceImplementation(typeof(IDictionary<,>), type);
                        if (collectionInterface != null)
                        {
                            // Dictionary
                            BindDictionary(typeInstance, collectionInterface, configuration);
                        }
                        else
                        {
                            collectionInterface = GetGenericOpenInterfaceImplementation(typeof(ICollection<>), type);
                            if (collectionInterface != null)
                            {
                                // ICollection
                                BindCollection(typeInstance, collectionInterface, configuration);
                            }
                            else
                            {
                                // Something else
                                BindObjectProperties(typeInstance, configuration);
                            }
                        }
                    }
                    return typeInstance;
                }
            }
    
            private static void BindDictionary(object dictionary, Type iDictionaryType, IConfiguration configuration)
            {
                var iDictionaryTypeInfo = iDictionaryType.GetTypeInfo();
    
                // It is guaranteed to have a two and only two parameters
                // because this is an IDictionary<K,V>
                var keyType = iDictionaryTypeInfo.GenericTypeArguments[0];
                var valueType = iDictionaryTypeInfo.GenericTypeArguments[1];
    
                if (keyType != typeof(string))
                {
                    // We only support string keys
                    return;
                }
    
                var addMethod = iDictionaryTypeInfo.GetDeclaredMethod("Add");
                var subkeys = configuration.GetConfigurationSections().ToList();
    
                foreach (var keyProperty in subkeys)
                {
                    var keyConfiguration = keyProperty.Value;
    
                    var item = BindType(
                        type: valueType,
                        typeInstance: null,
                        configuration: keyConfiguration);
                    if (item != null)
                    {
                        addMethod.Invoke(dictionary, new[] { keyProperty.Key, item });
                    }
                }
            }
    
            private static void BindCollection(object collection, Type iCollectionType, IConfiguration configuration)
            {
                var iCollectionTypeInfo = iCollectionType.GetTypeInfo();
    
                // It is guaranteed to have a one and only one parameter
                // because this is an ICollection<T>
                var itemType = iCollectionTypeInfo.GenericTypeArguments[0];
    
                var addMethod = iCollectionTypeInfo.GetDeclaredMethod("Add");
                var subkeys = configuration.GetConfigurationSections().ToList();
    
                foreach (var keyProperty in subkeys)
                {
                    var keyConfiguration = keyProperty.Value;
    
                    try
                    {
                        var item = BindType(
                            type: itemType,
                            typeInstance: null,
                            configuration: keyConfiguration);
                        if (item != null)
                        {
                            addMethod.Invoke(collection, new[] { item });
                        }
                    }
                    catch
                    {
                    }
                }
            }
    
            private static object CreateValueFromConfiguration(Type type, string value, IConfiguration configuration)
            {
                var typeInfo = type.GetTypeInfo();
    
                if (typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    return CreateValueFromConfiguration(Nullable.GetUnderlyingType(type), value, configuration);
                }
    
                var configurationValue = configuration.Get(key: null);
    
                try
                {
                    if (typeInfo.IsEnum)
                    {
                        return Enum.Parse(type, configurationValue);
                    }
                    else
                    {
                        return Convert.ChangeType(configurationValue, type);
                    }
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException(Resources.FormatError_FailedBinding(configurationValue, type), ex);
                }
            }
    
            private static Type GetGenericOpenInterfaceImplementation(Type expectedOpenGeneric, Type actual)
            {
                var interfaces = actual.GetTypeInfo().ImplementedInterfaces;
                foreach (var interfaceType in interfaces)
                {
                    if (interfaceType.GetTypeInfo().IsGenericType &&
                        interfaceType.GetGenericTypeDefinition() == expectedOpenGeneric)
                    {
                        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;
            }
        }
    ConfigurationBinder

    我们对其中几个方法,进行简单的说明:

    • GetAllProperties。系统获取属性的时候,不光要获取当前类的属性也要获取基类的属性。
    • 绑定属性时,将需要被绑定的对象作为参数传入进去,由于是引用类型,所以不用返回值也能更改其属性、类似的还有ArrayList等。
      • BindProperty(PropertyInfo property, object propertyOwner, IConfiguration configuration)。此处的propertyOwner值会被调用方法中修改。
    • 将字符串转换成枚举的方法:
      • Enum.Parse(type, configurationValue);、
    • 将对象转变类型的方法:
      • Convert.ChangeType(configurationValue, type);
    • 判断泛型的方法
      •   
        private static Type GetGenericOpenInterfaceImplementation(Type expectedOpenGeneric, Type actual)
                {
                    var interfaces = actual.GetTypeInfo().ImplementedInterfaces;
                    foreach (var interfaceType in interfaces)
                    {
                        if (interfaceType.GetTypeInfo().IsGenericType &&
                            interfaceType.GetGenericTypeDefinition() == expectedOpenGeneric)
                        {
                            return interfaceType;
                        }
                    }
        
                    return null;
                }
  • 相关阅读:
    Missing Ranges
    springboot整合quartz
    Quartz01
    springboot整合jap
    springboot集成redis
    springboot整合drui、mybatis、pagehelper
    springboot模板
    SpringBoot入门
    Java注解简介
    Git02(ssh key的配置和使用、idea配置并使用Git)
  • 原文地址:https://www.cnblogs.com/watermoon2/p/4530621.html
Copyright © 2020-2023  润新知