.Net Core配置Configuration具体实现

目录
  • 核心类
  • 构建
    • configurationbuilder
    • iconfigurationsource
    • configurationprovider
    • configurationroot
  • 查询
    • 索引器
    • getsection
  • dbconfiguration示例

    最近又研究了一下.netcore配置选项的源码实现,又学习到了不少东西。这篇文章先写一下iconfiguration的学习成果,options的后面补上

    核心类

    • configurationbuilder:iconfigurationbuilder  (构建iconfiguration)
    • iconfigurationsource (配置数据来源)
    • iconfigurationprovider (将配置源的原始结构转为为idictionary<string, string>)
    • configurationroot:iconfigurationroot:iconfiguration  (配置根节点)

    构建

    configurationbuilder

    下面是configurationbuilder中的主要代码

    可以看到configurationbuilder的主要功能就是配置数据源到集合中

    在build时依次调用iconfigurationsource的build函数,并将返回的iconfigurationprovider加入到list中

    最后用iconfigurationprovider的集合构建一个configurationroot对象

    public ilist<iconfigurationsource> sources = new list<iconfigurationsource>();
    
    public iconfigurationbuilder add(iconfigurationsource source)
    {
        sources.add(source);
        return this;
    }
    
    public iconfigurationroot build()
    {
        list<iconfigurationprovider> list = new list<iconfigurationprovider>();
        foreach (iconfigurationsource source in sources)
        {
            iconfigurationprovider item = source.build(this);
            list.add(item);
        }
    
        return new configurationroot(list);
    }
    
    

    iconfigurationsource

    public class environmentvariablesconfigurationsource : iconfigurationsource
    {
        public string prefix;
        public iconfigurationprovider build(iconfigurationbuilder builder)
        {
            return new environmentvariablesconfigurationprovider(prefix);
        }
        public environmentvariablesconfigurationsource()
        {
        }
    }
        
       
    public class commandlineconfigurationsource : iconfigurationsource
    {
        public idictionary<string, string> switchmappings;
        public ienumerable<string> args;
        public iconfigurationprovider build(iconfigurationbuilder builder)
        {
            return new commandlineconfigurationprovider(args, switchmappings);
        }
        public commandlineconfigurationsource()
        {
        }
    }
    
    //jsonconfigurationsource继承自fileconfigurationsource,我这里将其合为一个了
    public abstract class jsonconfigurationsource : iconfigurationsource
    {
     public ifileprovider fileprovider { get; set; }
     public string path { get; set; }
     public bool optional { get; set; }
     public bool reloadonchange { get; set; }
     public int reloaddelay { get; set; } = 250;
    
     public action<fileloadexceptioncontext> onloadexception { get; set; }
        
     public iconfigurationprovider build(iconfigurationbuilder builder)
     {
      fileprovider = fileprovider ?? builder.getfileprovider();
      onloadexception = onloadexception ?? builder.getfileloadexceptionhandler();
      return new jsonconfigurationprovider(this);
     }
        
     public void resolvefileprovider()
     {
      if (fileprovider == null && !string.isnullorempty(path) && system.io.path.ispathrooted(path))
      {
       string directoryname = system.io.path.getdirectoryname(path);
       string text = system.io.path.getfilename(path);
       while (!string.isnullorempty(directoryname) && !directory.exists(directoryname))
       {
        text = system.io.path.combine(system.io.path.getfilename(directoryname), text);
        directoryname = system.io.path.getdirectoryname(directoryname);
       }
       if (directory.exists(directoryname))
       {
        fileprovider = new physicalfileprovider(directoryname);
        path = text;
       }
      }
     }
    }
    
    

    上面展示了比较常用的三种configurationsource,代码都比较简单。

    也很容易看出来configurationsource的作用就是配置数据源,并不解析数据。

    解析数据源的功能由 iconfigurationprovider完成

    configurationprovider

    下面为iconfigurationprovider接口定义的5个函数

    public interface iconfigurationprovider
    {
     bool tryget(string key, out string value);
    
     void set(string key, string value);
    
     ichangetoken getreloadtoken();
    
     void load();
    
     ienumerable<string> getchildkeys(ienumerable<string> earlierkeys, string parentpath);
    }
    
    

    configurationprovider是一个抽象类,继承了iconfigurationprovider接口

    在新建provider时一般都会选择直接继承configurationprovider,接下来看一下configurationprovider的几个核心方法

    public abstract class configurationprovider : iconfigurationprovider
    {
     private configurationreloadtoken _reloadtoken = new configurationreloadtoken();
        
     protected idictionary<string, string> data= new dictionary<string, string>(stringcomparer.ordinalignorecase);
    
     public virtual bool tryget(string key, out string value)=>data.trygetvalue(key, out value);
    
     public virtual void set(string key, string value)=>data[key] = value;
    
     public virtual void load(){}
    
     public ichangetoken getreloadtoken()
     {
      return _reloadtoken;
     }
     
     protected void onreload()
     {
     configurationreloadtoken configurationreloadtoken = interlocked.exchange(ref _reloadtoken, new configurationreloadtoken());
      configurationreloadtoken.onreload();
     }
    
    

    可以推测出:

    • load函数负责从源数据读取数据然后给字典data赋值
    • configurationprovider将数据存储在字典data中,增加修改都是对字典的操作
    • 每个configurationprovider都会生成一个ichangetoken,在onreload函数被调用时生成新的token,并调用原token的onreload函数

    configurationroot

    在configurationbuilder的build函数中,我们生成了一个configurationroot,并给他传递了所有的configrationprovider列表,下面我们看看他用我们的provider都做了啥吧

    private configurationreloadtoken _changetoken = new configurationreloadtoken();
    
    public configurationroot(ilist<iconfigurationprovider> providers)
    {
        _providers = providers;
        _changetokenregistrations = new list<idisposable>(providers.count);
        foreach (iconfigurationprovider p in providers)
        {
            p.load();
            changetoken.onchange(p.getreloadtoken, 
               delegate{
         var oldtoken=interlocked.exchange(ref _changetoken, new configurationreloadtoken());
                     oldtoken.onreload();
                    })
        }
    }
    
    public ichangetoken getreloadtoken()=>_changetoken;
    
    

    上面的代码也对部分地方进行了简化。可以看到configurationroot在生成时主要就做了两件事

    • 1.调用provider的load函数,这会给provider的data赋值
    • 2.读取provider的reloadtoken,每个provider的reload事件都会触发configurationroot自己的reloadtoken的reload事件

    至此配置的数据源构建这块就分析完啦!

    查询

    常规的配置查询有两种基本方式 :索引器和getsection(string key)

    其余的getvalue等等都是一些扩展方法,本篇文章不对此进行展开研究

    索引器

    索引器的查询执行的方式是倒叙查询所有的provider,然后调用provider的tryget函数,在查询时重名的key,最后加入的会生效。

    赋值则是依次调用每个provider的set函数

    public string this[string key]
    {
     get
     {
      for (int num = _providers.count - 1; num >= 0; num--)
      {
       if (_providers[num].tryget(key, out var value))
       {
        return value;
       }
      }
      return null;
     }
     set
     {
      foreach (iconfigurationprovider provider in _providers)
      {
       provider.set(key, value);
      }
     }
    }
    
    

    getsection

    public iconfigurationsection getsection(string key)
    {
     return new configurationsection(this, key);
    }
    
    public class configurationsection : iconfigurationsection, iconfiguration
    {
     private readonly iconfigurationroot _root;
     private readonly string _path;
     private string _key;
     public string value
     {
      get
      {
       return _root[path];
      }
      set
      {
       _root[path] = value;
      }
     }
     
        //configurationpath.combine = string.join(":",paramlist);
     public string this[string key]
     {
      get
      {
       return _root[configurationpath.combine(path, key)];
      }
      set
      {
       _root[configurationpath.combine(path, key)] = value;
      }
     }
    
     public configurationsection(iconfigurationroot root, string path)
     {
      _root = root;
      _path = path;
     }
    
     public iconfigurationsection getsection(string key)
     {
      return _root.getsection(configurationpath.combine(path, key));
     }
    
     public ienumerable<iconfigurationsection> getchildren()
     {
      return _root.getchildrenimplementation(path);
     }
    
     public ichangetoken getreloadtoken()
     {
      return _root.getreloadtoken();
     }
    }
    
    

    可以看到getsection会生成一个configurationsection对象

    而configurationsection在读取/设置值时实际上就是对查询的key用:拼接,然后调用iconfigurationroot(_root)的赋值或查询函数

    关于configuration的配置和读取的知识点大概就是以上这些了,还有更深入的涉及到对象的绑定这一块get<> bind<> getchildren()等,比较难读,要一行一行代码看,以后有时间可能再研究一下

    最后贴上一个从数据加载配置源并动态更新的小例子

    dbconfiguration示例

     public void run()
     {
         var builder = new configurationbuilder();
         var dataprovider = new dbdataprovider();
         builder.sources.add(new dbconfigurationsource() { dataprovider = dataprovider, reloadonchange = true, table = "config" });
         iconfigurationroot config = builder.build();
    
         console.writeline(config["time"]);
         task.run(() =>
                  {
                      while (true)
                      {
                          thread.sleep(2000);
                          dataprovider.update("config");
                          console.writeline($"读取配置时间:{config["time"]}");
                      }
                  });
         thread.sleep(20000);
     }
    public class dbconfigurationprovider : configurationprovider
    {
        private dbconfigurationsource source { get; }
        public dbconfigurationprovider(dbconfigurationsource source)
        {
            source = source;
        }
    
        public override void load()
        {
            if (source.reloadonchange)
            {
                changetoken.onchange(() => source.dataprovider.watch(source.table), loaddata);
            }
            loaddata();
        }
    
        private void loaddata()
        {
            var data = source.dataprovider.getdata(source.table);
            load(data);
            onreload();
        }
    
        public void load(dictionary<string, object> data)
        {
            var dic = new sorteddictionary<string, string>(stringcomparer.ordinalignorecase);
            foreach (var element in data)
            {
                dic.add(element.key, element.value?.tostring());
            }
            base.data = dic;
        }
    }
    
    public class dbconfigurationsource : iconfigurationsource
    {
        public dbdataprovider dataprovider { get; set; }
        public string table { get; set; }
        public bool reloadonchange { get; set; }
        public bool optional { get; set; }
    
        public dbconfigurationsource()
        {
        }
    
        public iconfigurationprovider build(iconfigurationbuilder builder)
        {
            return new dbconfigurationprovider(this);
        }
    }
    
    public class dbdataprovider
    {
        private concurrentdictionary<string, cancellationtokensource> tabletoken = new concurrentdictionary<string, cancellationtokensource>();
        public dbdataprovider()
        {
        }
    
        public dictionary<string, object> getdata(string table)
        {
            switch (table)
            {
                case "config":
                    return getconfig();
            }
            return new dictionary<string, object>();
        }
    
        public void update(string table)
        {
            console.writeline($"更新数据库数据table:{table}");
            if (tabletoken.trygetvalue(table, out cancellationtokensource cts))
            {
                var oldcts = cts;
                tabletoken[table] = new cancellationtokensource();
                oldcts.cancel();
            }
        }
    
        private dictionary<string, object> getconfig()
        {
            var valuedic = new dictionary<string, object>();
            valuedic.tryadd("time", datetime.now.tostring());
            valuedic.tryadd("weather", "windy");
            valuedic.tryadd("people_number:male", 100);
            valuedic.tryadd("people_number:female", 150);
            return valuedic;
        }
    
        public ichangetoken watch(string table)
        {
            var cts = tabletoken.getoradd(table, x => new cancellationtokensource());
            return new cancellationchangetoken(cts.token);
        }
    }
    

    到此这篇关于.net core配置configuration具体实现的文章就介绍到这了,更多相关.net core configuration内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

    (0)
    上一篇 2022年3月23日
    下一篇 2022年3月23日

    相关推荐