前言

在asp .net core 中我们会看到一个appsettings.json 文件,它就是我们在服务中的各种配置,是至关重要的一部门。

不管是官方自带的服务,还是我们自己编写的服务都是用它来实现自己服务的动态配置,这就是约定。

配置文件之所以会成为约定,最主要的原因就是好用,不然可能第三方的配置文件管理的就会出来替代官方的配置文件管理系统,官方也提供了对应的接口来让第三方接入。

正文

官方提供了 Microsoft.Extensions.Configuration.Abstration接口。

同时提供了Microsoft.Extensions.Configuration 来作为实现。

所以我们如果想自己写第三方的包,那么就可以对 Microsoft.Extensions.Configuration.Abstration 的某一部分或者全部实现。

初学.net core的时候,陷入了一个误区,当时认为配置系统就是从json中进行读取,实际上配置系统是可以从命令行中获取、从环境变量中获取,只要他们符合key-value这种字符串键值对的方式。

配置文件系统有四个主要的接口,也可以理解为四个主要的模块功能。后面用代码解释一下这几个的作用。

  1. IConfiguration

  2. IConfigurationRoot

  3. IConfigurationSection

  4. IConfigurationBuilder

配置扩展点:

1.IConfigurationSource

2.IConfigurationProvider

扩展上诉两个可以帮助扩展不同配置来源。

static void Main(string[] args)
{
IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(new Dictionary<string,string>()
{
{"key1","value1"},
{"key2","value2"},
});
IConfigurationRoot configurationRoot = builder.Build(); Console.WriteLine(configurationRoot["key1"]);
Console.WriteLine(configurationRoot["key2"]);
}

看下ConfugurationBuilder:

/// <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>
/// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/>
/// and the registered <see cref="IConfigurationProvider"/>s.
/// </summary>
public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>(); /// <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 (IConfigurationSource source in Sources)
{
IConfigurationProvider provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}
}

在看一个东西的功能的时候一定要看下顶部这句话:

/// <summary>
/// Used to build key/value based configuration settings for use in an application.
/// </summary>
public class ConfigurationBuilder : IConfigurationBuilder

翻译过来就是用于构建在应用程序中使用的基于键/值的配置设置。

那么这个时候我们可以重点看下build,毕竟是用来构建的。

从上述语义中,大概知道是IConfigurationSource 转换为 IConfigurationProvider。

那么重点看下这两个。

先看IConfigurationSource 接口。

/// <summary>
/// Represents a source of configuration key/values for an application.
/// </summary>
public interface IConfigurationSource
{
/// <summary>
/// Builds the <see cref="IConfigurationProvider"/> for this source.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
/// <returns>An <see cref="IConfigurationProvider"/></returns>
IConfigurationProvider Build(IConfigurationBuilder builder);
}

表示一个配置文件的来源。里面只有一个方法就是Provider,这时候猜想IConfigurationProvider就是用于统一获取值的方式的。

看下Provider:

/// <summary>
/// Provides configuration key/values for an application.
/// </summary>
public interface IConfigurationProvider
{
/// <summary>
/// Tries to get a configuration value for the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns><c>True</c> if a value for the specified key was found, otherwise <c>false</c>.</returns>
bool TryGet(string key, out string value); /// <summary>
/// Sets a configuration value for the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
void Set(string key, string value); /// <summary>
/// Returns a change token if this provider supports change tracking, null otherwise.
/// </summary>
/// <returns>The change token.</returns>
IChangeToken GetReloadToken(); /// <summary>
/// Loads configuration values from the source represented by this <see cref="IConfigurationProvider"/>.
/// </summary>
void Load(); /// <summary>
/// Returns the immediate descendant configuration keys for a given parent path based on this
/// <see cref="IConfigurationProvider"/>s data and the set of keys returned by all the preceding
/// <see cref="IConfigurationProvider"/>s.
/// </summary>
/// <param name="earlierKeys">The child keys returned by the preceding providers for the same parent path.</param>
/// <param name="parentPath">The parent path.</param>
/// <returns>The child keys.</returns>
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
}

查看头部:

/// <summary>
/// Provides configuration key/values for an application.
/// </summary>
public interface IConfigurationProvider

为应用提供key value配置。

那么这时候猜想ConfigurationRoot就是来整合配置的。

先看我们上文一组IConfigurationSource,IConfigurationProvider的具体实现MemoryConfigurationSource 和 MemoryConfigurationProvider。

MemoryConfigurationSource :

/// <summary>
/// Represents in-memory data as an <see cref="IConfigurationSource"/>.
/// </summary>
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);
}
}

MemoryConfigurationProvider:

/// <summary>
/// In-memory implementation of <see cref="IConfigurationProvider"/>
/// </summary>
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 (KeyValuePair<string, string> 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);
} /// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return Data.GetEnumerator();
} /// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

所以我们如果要扩展的来源的话,需要实现IConfigurationSource、IConfigurationProvider即可。

那么有时候我们会在配置文件中看到:

{
"section1:key3":"value3"
}

是否这个解释含义是section1:key3作为key然后value3作为value呢?

其实不是,这是为了能够让配置分组。

static void Main(string[] args)
{
IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(new Dictionary<string,string>()
{
{"key1","value1"},
{"key2","value2"},
{"section1:key3","values3"}
});
IConfigurationRoot configurationRoot = builder.Build(); Console.WriteLine(configurationRoot["key1"]);
Console.WriteLine(configurationRoot["key2"]);
Console.WriteLine(configurationRoot["section1:key3"]);
Console.WriteLine(configurationRoot.GetSection("section1")["key3"]);
}

可以看到configurationRoot 既可以把section1:key3 当作key,同时也可以把section1:key3,当作以section1为分组下面的key3。

这里我们就走进configurationRoot,看下它是如何让我们通过索引的方式获取值的。

configurationRoot 下的索引:

public string this[string key]
{
get
{
for (int i = _providers.Count - 1; i >= 0; i--)
{
IConfigurationProvider provider = _providers[i]; if (provider.TryGet(key, out string value))
{
return value;
}
} return null;
}
set
{
if (!_providers.Any())
{
throw new InvalidOperationException(SR.Error_NoSources);
} foreach (IConfigurationProvider provider in _providers)
{
provider.Set(key, value);
}
}
}

这个其实就是遍历我们的providers。那么来看下getsection部分。

public IConfigurationSection GetSection(string key)
=> new ConfigurationSection(this, key);

查看ConfigurationSection的主要部分:

public ConfigurationSection(IConfigurationRoot root, string path)
{
if (root == null)
{
throw new ArgumentNullException(nameof(root));
} if (path == null)
{
throw new ArgumentNullException(nameof(path));
} _root = root;
_path = path;
}
public string this[string key]
{
get
{
return _root[ConfigurationPath.Combine(Path, key)];
} set
{
_root[ConfigurationPath.Combine(Path, key)] = value;
}
}

实例化主要是记录section的值,并且记录IConfigurationRoot来源。

然后其索引方式还是调用了IConfigurationRoot,只是setion的值和key值做了一些处理,这个处理是ConfigurationPath来完成的。

看下ConfigurationPath:

/// <summary>
/// The delimiter ":" used to separate individual keys in a path.
/// </summary>
public static readonly string KeyDelimiter = ":"; /// <summary>
/// Combines path segments into one path.
/// </summary>
/// <param name="pathSegments">The path segments to combine.</param>
/// <returns>The combined path.</returns>
public static string Combine(params string[] pathSegments)
{
if (pathSegments == null)
{
throw new ArgumentNullException(nameof(pathSegments));
}
return string.Join(KeyDelimiter, pathSegments);
}

从上诉看,ConfigurationSection的原理还是很简单的。

getSetion 部分把前缀记录下来,然后和key值做一个拼接,还是调用ConfigurationRoot的索引部分。

经过简单的分析,我们完全可以玩套娃模式。

static void Main(string[] args)
{
IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(new Dictionary<string,string>()
{
{"key1","value1"},
{"key2","value2"},
{"section1:key3","values3"},
{"section2:section3:key4","values4"}
});
IConfigurationRoot configurationRoot = builder.Build();
var section2 = configurationRoot.GetSection("section2");
var section3 = section2.GetSection("section3");
Console.WriteLine(section3["key4"]);
}

虽然不提倡,但是可以这么干。

下一节,配置文件之军令(命令行)

以上只是个人整理,如有错误,望请指出,谢谢。

重新整理 .net core 实践篇————配置系统之盟约[五]的更多相关文章

  1. 重新整理 .net core 实践篇—————配置系统之军令状[七](配置文件)

    前言 介绍一下配置系统中的配置文件,很多服务的配置都写在配置文件中,也是配置系统的大头. 正文 在asp .net core 提供了下面几种配置文件格式的读取方式. Microsoft.extensi ...

  2. 重新整理 .net core 实践篇—————配置系统之间谍[八](文件监控)

    前言 前文提及到了当我们的配置文件修改了,那么从 configurationRoot 在此读取会读取到新的数据,本文进行扩展,并从源码方面简单介绍一下,下面内容和前面几节息息相关. 正文 先看一下,如 ...

  3. 重新整理 .net core 实践篇—————配置系统之强类型配置[十]

    前言 前文中我们去获取value值的时候,都是通过configurationRoot 来获取的,如configurationRoot["key"],这种形式. 这种形式有一个不好的 ...

  4. 重新整理 .net core 实践篇————配置系统——军令(命令行)[六]

    前言 前文已经基本写了一下配置文件系统的一些基本原理.本文介绍一下命令行导入配置系统. 正文 要使用的话,引入Microsoft.extensions.Configuration.commandLin ...

  5. 重新整理 .net core 实践篇—————配置系统之简单配置中心[十一]

    前言 市面上已经有很多配置中心集成工具了,故此不会去实践某个框架. 下面链接是apollo 官网的教程,实在太详细了,本文介绍一下扩展数据源,和简单翻翻阅一下apollo 关键部分. apollo 服 ...

  6. 重新整理 .net core 实践篇————配置应用[一]

    前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...

  7. 重新整理 .net core 实践篇—————日志系统之战地记者[十五]

    前言 本节开始整理日志相关的东西.先整理一下日志的基本原理. 正文 首先介绍一下包: Microsoft.Extengsion.Logging.Abstrations 这个是接口包. Microsof ...

  8. 重新整理 .net core 实践篇—————日志系统之作用域[十七]

    前言 前面介绍了服务与日志之间的配置,那么我们服务会遇到下面的场景会被遇到一些打log的问题. 前面我提及到我们的log,其实是在一个队列里面,而我们的请求是在并发的,多个用户同时发送请求这个时候我们 ...

  9. 重新整理 .net core 实践篇—————日志系统之结构化[十八]

    前言 什么是结构化呢? 结构化,就是将原本没有规律的东西进行有规律话. 就比如我们学习数据结构,需要学习排序然后又要学习查询,说白了这就是一套,没有排序,谈如何查询是没有意义的,因为查询算法就是根据某 ...

随机推荐

  1. kubernetes 的负载均衡策略

    Kubernetes提供了两种负载分发策略: RoundRobin和SessionAffinity ◎ RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上. ◎ SessionAff ...

  2. 面试题:让你捉摸不透的 Go reslice

    面试题: package main func a() []int { a1 := []int{3} a2 := a1[1:] return a2 } func main() { a() } 看到这个题 ...

  3. BLE链路层空中包格式

    空中包格式 BLE链路层的空中包格式非常简单,它所有的空中包都遵循下图所示的格式: 有上图可见,BLE空中包由4个部分组成,他们分别是: 前导码(Preamble) 访问地址(Access Addre ...

  4. C/C++ 构建系统,我用 xmake

    XMake 是什么 XMake 是一个基于 Lua 的 现代化 C/C++ 构建系统. 它的语法简洁易上手,对新手友好,即使完全不会 lua 也能够快速入门,并且完全无任何依赖,轻量,跨平台. 同时, ...

  5. 逆向工程第004篇:跨越CM4验证机制的鸿沟(中)

    一.前言 在上一篇文章的最后,我已经找出了关键的CALL语句,那么这篇文章我就带领大家来一步一步地分析这个CALL.我会将我的思路完整地展现给大家,因此分析过程可能略显冗长,我会分为两篇文章进行讨论. ...

  6. 模板templates的使用

    目录 模板及其渲染 模板查找路径 DTL模板语法 常用的模板标签 DTL常用过滤器 模块结构优化 加载静态文件 模板及其渲染 视图函数只是直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面 ...

  7. Docker阿里云镜像存储服务

    阿里云镜像服务地址 https://cr.console.aliyun.com/cn-beijing/instances/repositories   免费免费免费 登陆之后可以免费创建,仓库.地址大 ...

  8. MAC地址格式

    随机配置一个mac地址,发现有的会报出Cannot assign requested address. 错误码是EADDRNOTAVAIL. 检查不是组播地址也不是全0地址. 组播地址就是第一个字节最 ...

  9. thymeleaf中[[${}]]与[(${})]的区别

    [[-]]会被转义,[(-)]不会. 假设在后台传入msg的值为 <b>AAA</b> 在前台这样使用 [[${msg}]]___[(${msg})] 展示效果 官方参考文档

  10. CCNA 第三章 TCP/IP简介

    1:DoD模型和OSI模型 2:TCP和UDP的重要特性 3:IP编址: (1):A类地址: 第一字节第一位必须为0,即:0xxxxxxx,取值范围:00000000-011111111:0-127, ...