实例化内层对象后,需要挂载到上级
大石头 authored at 2019-12-30 18:39:08
7.32 KiB
X
using System;
using System.Collections.Generic;
using System.Reflection;
using NewLife.Collections;
using NewLife.Reflection;

namespace NewLife.Configuration
{
    /// <summary>配置提供者</summary>
    /// <remarks>
    /// 建立扁平化配置数据体系,以分布式配置中心为核心,支持基于key的索引读写,也支持Load/Save/Bind的实体模型转换。
    /// key索引支持冒号分隔的多层结构,在配置中心中作为整个key存在,在文件配置中第一段表示不同文件。
    /// </remarks>
    public interface IConfigProvider
    {
        /// <summary>所有键</summary>
        ICollection<String> Keys { get; }

        /// <summary>获取 或 设置 配置值</summary>
        /// <param name="key">键</param>
        /// <returns></returns>
        String this[String key] { get; set; }

        /// <summary>获取模型实例</summary>
        /// <typeparam name="T">模型</typeparam>
        /// <param name="nameSpace">命名空间。映射时去掉</param>
        /// <returns>模型实例</returns>
        T Load<T>(String nameSpace = null) where T : new();

        /// <summary>保存模型实例</summary>
        /// <typeparam name="T">模型</typeparam>
        /// <param name="model">模型实例</param>
        /// <param name="nameSpace">命名空间。映射时加上</param>
        void Save<T>(T model, String nameSpace = null);

        /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
        /// <typeparam name="T">模型</typeparam>
        /// <param name="model">模型实例</param>
        /// <param name="nameSpace">命名空间。映射时去掉</param>
        void Bind<T>(T model, String nameSpace = null);
    }

    /// <summary>配置提供者基类</summary>
    /// <remarks>
    /// 同时也是基于Items字典的内存配置提供者。
    /// </remarks>
    public class ConfigProvider : IConfigProvider
    {
        #region 属性
        /// <summary>配置项集合</summary>
        public IDictionary<String, String> Items { get; set; } = new NullableDictionary<String, String>(StringComparer.OrdinalIgnoreCase);

        /// <summary>所有键</summary>
        public ICollection<String> Keys => Items.Keys;

        /// <summary>获取 或 设置 配置值</summary>
        /// <param name="key">键</param>
        /// <returns></returns>
        public virtual String this[String key] { get => Items[key]; set => Items[key] = value; }
        #endregion

        #region 加载/保存
        /// <summary>获取模型实例</summary>
        /// <typeparam name="T">模型</typeparam>
        /// <param name="nameSpace">命名空间。映射时去掉</param>
        /// <returns>模型实例</returns>
        public virtual T Load<T>(String nameSpace = null) where T : new()
        {
            var model = new T();
            MapTo(Items, model, nameSpace);

            return model;
        }

        /// <summary>映射字典到公有实例属性</summary>
        /// <param name="source"></param>
        /// <param name="model"></param>
        /// <param name="nameSpace"></param>
        protected virtual void MapTo(IDictionary<String, String> source, Object model, String nameSpace)
        {
            // 反射公有实例属性
            foreach (var pi in model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (!pi.CanRead || !pi.CanWrite) continue;
                if (pi.GetIndexParameters().Length > 0) continue;
                if (pi.Name.EqualIgnoreCase("ConfigFile", "IsNew")) continue;

                var name = pi.Name;
                if (!nameSpace.IsNullOrEmpty()) name = $"{nameSpace}:{pi.Name}";

                // 分别处理基本类型和复杂类型
                if (pi.PropertyType.GetTypeCode() != TypeCode.Object)
                {
                    if (source.TryGetValue(name, out var str)) pi.SetValue(model, str.ChangeType(pi.PropertyType), null);
                }
                else
                {
                    // 复杂类型需要递归处理
                    var val = pi.GetValue(model, null);
                    if (val == null)
                    {
                        // 如果有无参构造函数,则实例化一个
                        var ci = pi.PropertyType.GetConstructor(new Type[0]);
                        if (ci != null)
                        {
                            val = ci.Invoke(null);
                            pi.SetValue(model, val, null);
                        }
                    }

                    // 递归映射
                    if (val != null) MapTo(source, val, name);
                }
            }
        }

        /// <summary>保存模型实例</summary>
        /// <typeparam name="T">模型</typeparam>
        /// <param name="model">模型实例</param>
        /// <param name="nameSpace">命名空间。映射时加上</param>
        public virtual void Save<T>(T model, String nameSpace = null) => MapFrom(Items, model, nameSpace);

        /// <summary>从公有实例属性映射到字典</summary>
        /// <param name="source"></param>
        /// <param name="model"></param>
        /// <param name="nameSpace"></param>
        protected virtual void MapFrom(IDictionary<String, String> source, Object model, String nameSpace)
        {
            // 反射公有实例属性
            foreach (var pi in model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (!pi.CanRead || !pi.CanWrite) continue;
                if (pi.GetIndexParameters().Length > 0) continue;
                if (pi.Name.EqualIgnoreCase("ConfigFile", "IsNew")) continue;

                var name = pi.Name;
                if (!nameSpace.IsNullOrEmpty()) name = $"{nameSpace}:{pi.Name}";

                var val = pi.GetValue(model, null);

                // 分别处理基本类型和复杂类型
                if (pi.PropertyType.GetTypeCode() != TypeCode.Object)
                {
                    // 格式化为字符串,主要处理时间日期格式
                    source[name] = "{0}".F(val);
                }
                else
                {
                    // 递归映射
                    if (val != null) MapFrom(source, val, name);
                }
            }
        }

        /// <summary>合并源字典到目标字典</summary>
        /// <param name="source"></param>
        /// <param name="dest"></param>
        /// <param name="nameSpace"></param>
        protected virtual void Merge(IDictionary<String, String> source, IDictionary<String, String> dest, String nameSpace)
        {
            foreach (var item in source)
            {
                var name = item.Key;
                if (!nameSpace.IsNullOrEmpty()) name = $"{nameSpace}:{item.Key}";

                dest[name] = item.Value;
            }
        }
        #endregion

        #region 绑定
        /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
        /// <typeparam name="T">模型</typeparam>
        /// <param name="model">模型实例</param>
        /// <param name="nameSpace">命名空间。映射时去掉</param>
        public virtual void Bind<T>(T model, String nameSpace = null) => MapTo(Items, model, nameSpace);
        #endregion
    }
}