一 背景

相比.Net Framework , .NET Core的配置系统 ,有一些明显的优点 ,如:

1 支持更丰富的配置源

2 读取配置时, 可以用相同的方式读取, 并且非常方便

3 修改配置后,不用重启应用

本期对配置相关的源码简单梳理一下。 只说主逻辑 ,并且从用的角度说起 ,逐步深入。

二 配置读取

2.1 举个栗子

进入上代码环节 ,新建控制台应用 , 只需要引入 "Microsoft.Extensions.Configuration" 1个nuget包 ,就可以使用基本的配置了 。

(不得不说,相较于.NET Framework,.Net Core 的组件化设计实现的相当彻底 )

github地址

//数据源
var dicData = new Dictionary<string, string>() { { "Key1", "Value1" }, { "Key2", "Value2" } }; IConfigurationRoot configurationRoot = new ConfigurationBuilder()
  .Add(new MemoryConfigurationSource() { InitialData = dicData }) // 添加配置源
  .Build(); // 构建配置
Console.WriteLine("Key1=" + configurationRoot["Key1"]);//输出内容 Key1=Value1

我们好奇 configurationRoot["Key1"] 是怎么读取到 "Value1" 呢 ? 这就需要看源码了。 IConfigurationRoot到底是什么 , 以及 ["key1"] 的代码执行逻辑是怎样的 。

(注意: 为了方便了解主线逻辑 , 本文的中 .Net Core 源码有删减 )

2.2 IConfigurationRoot 相关源码

public interface IConfiguration
{
string this[string key] { get; set; }
} public interface IConfigurationRoot : IConfiguration
{
IEnumerable<IConfigurationProvider> Providers { get; }
} public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
private readonly IList<IConfigurationProvider> _providers; public IEnumerable<IConfigurationProvider> Providers => _providers; /// <summary>
/// 根据指定的Key获取或者设置对应的Value
/// </summary>
/// <param name="key">The configuration key.</param>
/// <returns>The configuration value.</returns>
public string this[string key]
{
get
{
for (var i = _providers.Count - 1; i >= 0; i--)
{
var provider = _providers[i]; if (provider.TryGet(key, out var value))
{
return value;
}
} return null;
}
set
{
if (!_providers.Any())
{
throw new InvalidOperationException(Resources.Error_NoSources);
} foreach (var provider in _providers)
{
provider.Set(key, value);
}
}
}

1 通过上面的代码发现 ,IConfigurationRoot 有个 Providers 属性 ;

2 读取配置是通过ConfigurationRoot类中的索引器实现的 , 更具体的说就是通过 遍历Providers ,调用其.TryGet(key, out var value) 获取的 。

3 细心的同学会发现,一旦获取到配置就结束并返回了。那么就有了从哪先获取的问题 ,我们发现遍历时是LIFO(后入先出)的顺序,所以后面添加的Provider有更高的优先级 。

到目前为止 ,我们只需要记住一个主知识点 :获取配置是由多个 Provider(供应者) provide (供应) 的 ,并且越后面的 Provider 优先级越高,具有覆盖性。

再想深入就得研究一下 : IConfigurationProvider是怎么回事 , 特别是其中的 TryGet 方法是怎么个逻辑 。

2.3 IConfigurationProvider 相关源码

public interface IConfigurationProvider
{
bool TryGet(string key, out string value);
void Load();
} public abstract class ConfigurationProvider : IConfigurationProvider
{
/// <summary>
/// The configuration key value pairs for this provider.
/// </summary>
protected IDictionary<string, string> Data { get; set; } /// <summary>
/// 根据指定的 键 获取对应的 值, 获取到返回true,否则返回 false
/// </summary>
public virtual bool TryGet(string key, out string value)
=> Data.TryGetValue(key, out value); /// <summary>
/// 加载数据
/// </summary>
public virtual void Load()
{
}
}

代码非常简单 ,每个Provider 维护了一个 Data (字典类型) 属性 , 配置就是从这个字典拿到的。 到此基本的读取配置逻辑我们已经知道了 ,给自己点个赞吧

2.4 使用便利性

这时 ,我们可以再看开头那个栗子,读取配置其实还有这种写法 :

configurationRoot.Providers.First().TryGet("Key1", out string Val);
Console.WriteLine("Key1=" + Val); //输出 Val=Value1

但是这种写法就感觉有些累赘了,还是 configurationRoot["Key1"] 更方便一些 , 有时我们甚至连"[" 和 "]" 都不想打 !

从使用的角度考虑,框架做了一些封装,并提供了不少扩展方法。但是基本的读取逻辑都是一样的

如果我们引入 "Microsoft.Extensions.Configuration.Binder" nuget包 ,读取配置操作时会发现一些新的方法。

比如配置和强模型绑定,并结合泛型使用 ,如:

public static T Get<T>(this IConfiguration configuration)

public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue)

三 配置写入

现在我们需要找Provider中的Data 属性是怎么写入的 ,还从开头的控制台程序入手 , 其中有这样一段代码 :

new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = dicData }).Build();

我们重点看Add方法

3.1 IConfigurationBuilder 相关源码

public interface IConfigurationBuilder
{
IList<IConfigurationSource> Sources {get;} IConfigurationBuilder Add(IConfigurationSource source); IConfigurationRoot Build();
} /// <summary>
/// Used to build key/value based configuration settings for use in an application.
/// </summary>
public class ConfigurationBuilder : IConfigurationBuilder
{
/// <summary>
/// Returns the sources used to obtain configuration values.
/// </summary>
public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); /// <summary>
/// Adds a new configuration source.
/// </summary>
/// <param name="source">The configuration source to add.</param>
/// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>
public IConfigurationBuilder Add(IConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} Sources.Add(source);
return this;
} /// <summary>
/// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in
/// <see cref="Sources"/>.
/// </summary>
/// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>
public IConfigurationRoot Build()
{
var providers = new List<IConfigurationProvider>();
foreach (var source in Sources)
{
var provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}
}

Add就是把配置源 Source ,添加到内部的Sources列表。IConfigurationBuilder 除了Add方法还有一个 Build 方法。

IConfigurationBuilder 就像一个车间 ,Add一堆零件后 ,Build一下, 整出来一辆 汽车 (IConfigurationRoot) ,  很明显IConfigurationBuilder是一个构建者模式 。

我们继续探索 IConfigurationSource ,看 Build 方法的实现逻辑 .

3.2 IConfigurationSource 相关源码

    public interface IConfigurationSource
{
IConfigurationProvider Build(IConfigurationBuilder builder);
} public class MemoryConfigurationSource : IConfigurationSource
{
/// <summary>
/// The initial key value configuration pairs.
/// </summary>
public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; } /// <summary>
/// Builds the <see cref="MemoryConfigurationProvider"/> for this source.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
/// <returns>A <see cref="MemoryConfigurationProvider"/></returns>
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MemoryConfigurationProvider(this);
}
}

可以看到 ,IConfigurationSource 只干了一件事 :其 Build 方法返回一个Provider。

还拿开头的栗子来讲 ,MemoryConfigurationSource.Build 方法返回了 一个新的 MemoryConfigurationProvider 实例。

再看下 MemoryConfigurationProvider

 public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>
{
private readonly MemoryConfigurationSource _source; /// <summary>
/// Initialize a new instance from the source.
/// </summary>
/// <param name="source">The source settings.</param>
public MemoryConfigurationProvider(MemoryConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} _source = source; if (_source.InitialData != null)
{
foreach (var pair in _source.InitialData)
{
Data.Add(pair.Key, pair.Value);
}
}
} /// <summary>
/// Add a new key and value pair.
/// </summary>
/// <param name="key">The configuration key.</param>
/// <param name="value">The configuration value.</param>
public void Add(string key, string value)
{
Data.Add(key, value);
}
}

MemoryConfigurationProvider 的构造函数完成了数据的初始化 。

到此为止,开头的那个栗子我们就梳理完了。

也许是我们开头那个例子过于简单了 , 写配置这块有点虎头蛇尾的感觉 。

我们再看一下主角 IConfigurationProvider , 发现其中定义的 Load 方法 ,我们还知道 ConfigurationProvider 是一个虚拟类 ,其 Load 方法还是个虚方法。

我们在翻一下源码看有没有其它类继承它呢 ? 如果有的话 ,它们是不是覆盖了 Load 方法呢?

果然我们发现了 不少 Provider   , 如 JsonConfigurationProvider,XmlConfigurationProvider,EnvironmentVariablesConfigurationProvider 等。

为方便讲解,我们以EnvironmentVariablesConfigurationProvider 为例,并精简了代码。 Load 方法负责了负载配置的任务。男团其他成员也是如此。所以说 Provider们 实力担当。

public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
{
private readonly string _prefix; /// <summary>
/// Initializes a new instance.
/// </summary>
public EnvironmentVariablesConfigurationProvider() : this(string.Empty)
{ } /// <summary>
/// Initializes a new instance with the specified prefix.
/// </summary>
/// <param name="prefix">A prefix used to filter the environment variables.</param>
public EnvironmentVariablesConfigurationProvider(string prefix)
{
_prefix = prefix ?? string.Empty;
} /// <summary>
/// Loads the environment variables.
/// </summary>
public override void Load()
{
Load(Environment.GetEnvironmentVariables());
} internal void Load(IDictionary envVariables)
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var filteredEnvVariables = envVariables
.Cast<DictionaryEntry>()
.Where(entry => ((string)entry.Key).StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)); foreach (var envVariable in filteredEnvVariables)
{
var key = ((string)envVariable.Key).Substring(_prefix.Length);
data[key] = (string)envVariable.Value;
} Data = data;
}
}

但是 IConfigurationSource 呢 ,是不是可有可无 ?其实不是。IConfigurationSource 作用是提供原始配置,并将必要的信息传递给 Provider 。

我们现在大概知道 IConfigurationBuilder , IConfigurationProvider , IConfigurationRoot ,IConfigurationSource 之间的关系。我们总结一下,

1 各种配置原始数据( IConfigurationSource) 通过 IConfigurationProvider 转成键值对格式的配置数据

2 IConfigurationBuilder 内部维护多个 IConfigurationSource,IConfigurationBuilder 中的 Build方法 构建出 IConfigurationRoot

3 IConfigurationRoot 内部维护多个 IConfigurationProvider,并通过 IConfigurationProvider 读取配置数据

闯荡天下的天外流星全套剑谱奉上

限于篇幅 ,本篇介绍 配置读取配置写入 2个知识点。

下一篇会介绍 配置变更监控 ,自定义配置 2个知识点。

感觉写的还凑合就点个赞吧

.Net Core 配置源码学习 (一)的更多相关文章

  1. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  2. ASP.NET Core MVC 源码学习:Routing 路由

    前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...

  3. ASP.NET Core MVC 源码学习:详解 Action 的激活

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  4. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  5. 源码学习系列之SpringBoot自动配置(篇一)

    源码学习系列之SpringBoot自动配置源码学习(篇一) ok,本博客尝试跟一下Springboot的自动配置源码,做一下笔记记录,自动配置是Springboot的一个很关键的特性,也容易被忽略的属 ...

  6. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  7. 【目录】Spring 源码学习

    [目录]Spring 源码学习 jwfy 关注 2018.01.31 19:57* 字数 896 阅读 152评论 0喜欢 9 用来记录自己学习spring源码的一些心得和体会以及相关功能的实现原理, ...

  8. Asp.NetCore源码学习[2-1]:配置[Configuration]

    Asp.NetCore源码学习[2-1]:配置[Configuration] 在Asp. NetCore中,配置系统支持不同的配置源(文件.环境变量等),虽然有多种的配置源,但是最终提供给系统使用的只 ...

  9. 源码学习系列之SpringBoot自动配置(篇二)

    源码学习系列之SpringBoot自动配置(篇二)之HttpEncodingAutoConfiguration 源码分析 继上一篇博客源码学习系列之SpringBoot自动配置(篇一)之后,本博客继续 ...

  10. Vue2.0源码学习(4) - 合并配置

    合并配置 通过之前的源码学习,我们已经了解到了new Vue主要有两种场景,第一种就是在外部主动调用new Vue创建一个实例,第二个就是代码内部创建子组件的时候自行创建一个new Vue实例.但是无 ...

随机推荐

  1. Isaac SDK & Sim 环境

    Isaac 是 NVIDIA 开放的机器人平台.其 Isaac SDK 包括以下内容: Isaac Apps: 各种机器人应用示例,突出 Engine 特性或专注 GEM 功能 Isaac Engin ...

  2. Keras网络可视化方法

    Keras网络可视化方法 Keras模型可视化 Keras可视化依赖的两个包 参考链接 Keras模型可视化 代码: from keras.utils import plot_model plot_m ...

  3. ARC145~152 题解

    比赛标号从大到小排列 . 因为博主比较菜所以没有题解的题都是博主不会做的 /youl ARC144 以前的比赛懒得写了 . 目录 AtCoder Regular Contest 152 B. Pass ...

  4. 0停机迁移Nacos?Java字节码技术来帮忙

    摘要:本文介绍如何将Spring Cloud应用从开源Consul无缝迁移至华为云Nacos. 本文分享自华为云社区<0停机迁移Nacos?Java字节码技术来帮忙>,作者:华为云PaaS ...

  5. 什么是Auth模块?(全面了解)

    目录 一:Auth模块 1.什么是Auth模块? 2.Auth模块作用 二:引入Auth模块 1.其实我们在创建好一个Django项目之后直接执行数据库迁移命令会自动生成很多表 2.django在启动 ...

  6. 盘点现在用的SqlServer 5种分页方式和拉姆达表达式分页,进来看看吧。

    现在基本上大家都在使用各种轮子自带的分页,大家是否还记得sql分页怎么写? 今天我们就来盘一盘怎么写和用哪种方式写. 欢迎大家评论区讨论. 1.ROW_NUMBER() OVER()方式(SQL201 ...

  7. 【JVM】经典垃圾回收器

    本文已收录至Github,推荐阅读 Java随想录 微信公众号:Java随想录 CSDN: 码农BookSea 转载请在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益.让我们一起维护 ...

  8. cmd命令行ssh连接Linux服务器

    打开cmd工具 使用命令ssh连接服务器 ssh 用户名@ip地址 (不需要指定端口号,默认端口就是22) 输入密码即可

  9. 速记·python 123章

    第一.二.三章 初识python 1.1 Python的概述 开发环境:Python 开发工具:IDLE(Python自带) 1.python的特点:代码简单.开发速度快.容易学习:有丰富的库:&qu ...

  10. [OpenCV实战]32 使用OpenCV进行非真实感渲染

    目录 1 保边滤波的频域变换 1.1 保边滤波器Edge Preserving Filter 1.1.1 函数调用 1.1.2 edgePreservingFilter结果 1.2 细节增强 1.3 ...