.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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • .NET Core 3.0之创建基于Consul的Configuration扩展组件

    经过前面三篇关于.NET Core Configuration的文章之后,本篇文章主要讨论如何扩展一个Configuration组件出来. 了解了Configuration的源码后,再去扩展一个组件就会比较简单,接下来我们将在.NET Core 3.0-preview5的基础上创建一个基于Consul的配置组件. 相信大家对Consul已经比较了解了,很多项目都会使用Consul作为配置中心,此处也不做其他阐述了,主要是讲一下,创建Consul配置扩展的一些思路.使用Consul配置功能时,我们

  • .Net Core3.0 配置Configuration的实现

    准备 .NET core和.NET项目配置上有了很大的改变,支持的也更加丰富了比如命令行,环境变量,内存中.NET对象,设置文件等等..NET项目我们常常把配置信息放到webConfig 或者appConfig中.配置相关的源码https://github.com/aspnet/Extensions:如果打开源码项目如果遇到以下错误,未遇到直接跳过. 错误提示:error : The project file cannot be opened by the project system, bec

  • 详解ASP.NET Core实现强类型Configuration读取配置数据

    前言 实现读取JSON文件几种方式,在项目中采取老办法简单粗暴,结果老大过来一看,恩,这样不太可取,行吧那我就用.NET Core中最新的方式诺,切记,适合的才是最好的,切勿懒. .NET Core读取JSON文件通过读取文件方式 当我将VS2015项目用VS2017打开后再添加控制器,此时会报错如下: 此时我们应该在该项目中的.csproj中添加如下这一句才能解决此问题: <ItemGroup> <DotNetCliToolReference Include="Microso

  • 如何在ASP.NET Core 的任意类中注入Configuration

    遇到的问题 我已经通读了 MSDN 上关于 Configuration 的相关内容,文档说可实现在 application 的任意位置访问 Configuration . 下面是 Startup.cs 的模板代码. public class Startup {     public Startup(IHostingEnvironment env)     {         var builder = new ConfigurationBuilder()             .SetBase

  • .Net Core配置Configuration具体实现

    目录 核心类 构建 ConfigurationBuilder IConfigurationSource ConfigurationProvider ConfigurationRoot 查询 索引器 GetSection DBConfiguration示例 最近又研究了一下.NetCore配置选项的源码实现,又学习到了不少东西.这篇文章先写一下IConfiguration的学习成果,Options的后面补上 核心类 ConfigurationBuilder:IConfigurationBuilde

  • ASP.NET Core配置设置之Configuration包

    ASP.NET Core 中提供了一个Configuration 包,用以应用配置基于配置提供程序建立的键值对.这里以json文件配置的方式,简单的介绍一下它的用法. 首先定义一个配置文件appsettings.json: { "key1": "value1", "key2": -1, "subsection": { "key1": "value2", "key2":

  • .Net Core配置与自动更新的实现方法

    .Net Core 将之前Web.Config中的配置迁移到了appsettings.json文件中,并使用ConfigurationBuilder来读取这个配置文件.并可设置在配置文件变化以后,自动重新加载,这样可不用重启你的程序. var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, rel

  • ASP.NET Core 配置和使用环境变量的实现

    前言 通常在应用程序开发到正式上线,在这个过程中我们会分为多个阶段,通常会有 开发.测试.以及正式环境等.每个环境的参数配置我们会使用不同的参数,因此呢,在ASP.NET Core中就提供了相关的环境API,方便我们更好的去做这些事情. 环境 ASP.NET Core使用ASPNETCORE_ENVIRONMENT来标识运行时环境. ASP.NET Core预设环境 Development:开发环境 Staging:暂存环境(测试环境) Production:正式环境 要取得系统变量ASPNET

  • ASP.NET Core配置和管理Web主机

    目录 1.前言 2.设置主机 2.1执行下列任务 2.2重写和增强定义的配置 3.主机配置值 3.1应用程序键(名称) 3.2捕获启动错误 3.3内容根 3.4详细错误 3.5环境 3.6HTTPS端口 3.7服务器(Kestrel) URL 4.重写配置 5.管理主机 6.IHostingEnvironment接口 7.IApplicationLifetime接口 8.作用域验证 1.前言 ASP.NET Core应用程序可以配置和启动主机(Host).主机负责应用程序启动和生命周期管理,配置

  • .NET Core配置TLS Cipher(套件)的详细过程

    目录 前言 .NET Core Cipher(套件)配置 密码学基础 前言 前不久我发表了一篇关于TLS协议配置被我钻了空子,经过第三方合作伙伴验证,针对此TLS协议存在不安全套件,急催速速解决,那么我们本篇开始继续整活!第三方合作伙伴对平台安全严苛要求,我们已连续发版十几次进行处理,在此过程中使得我对安全有了进一步认识,具体认识则是在技术解决方案和密码学盲点两方面.下面我们来了解两个方面,可能没有完全深入,至少对作为开发者的我们而言,应已基本足够 .NET Core Cipher(套件)配置

  • Asp.Net Core配置多环境log4net配置文件的全过程

    目录 前言 配置log4net 总结 前言 在之前的文章中有讲到AspNetCore多环境配置文件的应用,我们根据自己多种环境分别配置多个appsettings.$EnvironmentName.json文件. 在实际的开发中我们可能会遇到不只一个配置文件,如当我们使用log4net日志库时,喜欢使用单独的log4net.config配置文件.并且我们还遇到不同环境下的配置文件还存在差异.这时我们可能可以效仿appsettings.json多环境配置的风格实现多环境配置文件. 配置log4net

  • .NET Core配置连接字符串和获取数据库上下文实例

    假设数据库就两个表:User.Blogs, 模型类如下 public class User { public int Id { get; set; } public string Name { get; set; } public string Number { get; set; } public string Email { get; set; } } public class Blogs { public int Id { get; set; } public string BolgNam

  • 聊聊.Net,Core配置Nlog.md的问题

    首先在你的项目中用Nuget安装以下两个类库NLog.Extensions.Logging和NLog.Web.AspNetCore然后新建NLog的配置文件Nlog.config,内容如下 : 然后在Startup>Configure中引入Nlog public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { LogManager.LoadConfig

随机推荐