1. 程式人生 > 程式設計 >.Net Core配置Configuration具體實現

.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()
        {
        }
    }
    
    //onConfigurationSource繼承自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.Pa
    th.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;
     pripWoXSvate 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,keyhttp://www.cppcns.com)];
      }
      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 DBDataP程式設計客棧rovider();
         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內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!