我最近遇到了一个有趣的 Bug 让我调试了半天,这个 Bug 的现象是我的好多个模块都因为读取不到配置信息而炸掉,开始我没有定位到具体的问题,以为是我的配置服务器挂掉了。经过了半天的调试,才找到了是我新加入的使用 COIN 配置库的 ReadonlyCoinConfiguration 类型导致的,此 ReadonlyCoinConfiguration 类型继承 IConfigurationProvider 接口,但是我对 IConfigurationProvider 的 GetChildKeys 方法的理解不对,实现错了 GetChildKeys 方法,导致在枚举应用内的所有配置时,配置都会 ReadonlyCoinConfiguration 过滤掉,导致模块读取不到配置。本文将告诉大家 IConfigurationProvider 的 GetChildKeys 方法用途和如何正确实现他

在开始之前,先感谢两位大佬的博客:

要不是有这两篇博客,我还没有反应过来是我对 GetChildKeys 的理解不对

故事是: 我在使用 COIN 配置库对接 Microsoft.Extensions.Configuration 的时候,我需要写一个中间类型,让这个中间类型对接 COIN 和 Microsoft.Extensions.Configuration 配置。这个中间类型就需要实现 IConfigurationSource 和 IConfigurationProvider 接口

这个 COIN 配置库是我所在的团队开源的高性能配置库,主打稳定和高性能,特别适合客户端应用做对接配置文件使用,详细请看 https://github.com/dotnet-campus/dotnetCampus.Configurations/

对接的时候,我将此中间的类型称为 ReadonlyCoinConfiguration 类型,此中间的类型就是从 COIN 配置里面读取出配置,提供只读的方式,不允许将配置设置回 COIN 配置

让 ReadonlyCoinConfiguration 类型同时继承 IConfigurationSource 和 IConfigurationProvider 类型,从而可以所有代码都写到一个类型里面

先实现 IConfigurationSource 的 Build 方法,返回自身

  1. class ReadonlyCoinConfiguration : IConfigurationSource, IConfigurationProvider
  2. {
  3. public IConfigurationProvider Build(IConfigurationBuilder builder)
  4. {
  5. return this;
  6. }
  7. // 忽略其他代码
  8. }

接下来就是对 IConfigurationProvider 的实现。先集中到本文的主题,也就是 GetChildKeys 函数上。我出现问题的代码是采用如下定义

  1. public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath)
  2. {
  3. return Array.Empty<string>();
  4. }

以上的代码的实现是不符合预期的,如果我在配置初始化完成之后,在业务端调用 Configuration 的 AsEnumerable() 方法,将拿到空列表

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Configuration.AddInMemoryCollection().Add(new ReadonlyCoinConfiguration());
  3. builder.Services.AddControllers();
  4. var app = builder.Build();
  5. var keyValuePairs = app.Configuration.AsEnumerable().ToList();

以上代码的 keyValuePairs 的元素是 0 个

在框架里面,设计的 GetChildKeys 函数的功能是有两个方面考虑:

  1. 对其他的 IConfigurationProvider 的结果进行过滤
  2. 返回给框架层,此 IConfigurationProvider 提供的配置项

在 Microsoft.Extensions.Configuration 里是支持多个配置的,也就是支持多个 IConfigurationProvider 同时工作,且后加入的 IConfigurationProvider 的优先级或者说是权重更高的。例如 Microsoft.Extensions.Configuration 里同时传入 JSON 和 XML 和 Ini 和命令行作为配置,且命令行的配置期望是高优先级的。也就是在命令行里面的配置可以覆盖其他的配置信息

另外,由于一些业务是对配置项的顺序是敏感的,也就是配置项的顺序是会影响业务的逻辑的。例如配置里面有 Foo1 和 Foo2 这两项,在获取所有配置的时候,如果返回的顺序是 Foo1 在先然后是 Foo2 的顺序,和 Foo2 在先然后是 Foo1 的顺序也许将会影响业务的执行逻辑。于是在 Microsoft.Extensions.Configuration 里也期望能够让 IConfigurationProvider 可以控制配置项的顺序

在这两个需求的前提下,就设计了 GetChildKeys 方法

在 GetChildKeys 方法传入的两个参数的含义分别是:

  • earlierKeys: 在此 IConfigurationProvider 之前的其他的 IConfigurationProvider 提供的配置项
  • parentPath: 当前期望获取的配置的前缀路径,例如 Logging:Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider:FormatterOptions 前缀等

返回值是期望获取到可供输出的配置项。可供输出的意思就是将传入的 earlierKeys 其他 IConfigurationProvider 提供的配置项,再加上本 IConfigurationProvider 提供的配置项组合过滤之后的配置项列表

也就是说如果我需要在 IConfigurationProvider 实现过滤某些配置项的功能,那我只需要在返回的时候,将 earlierKeys 进行过滤之后返回即可

如果我只是期望追加一些新的配置,那我只需要将我的新的配置追加到 earlierKeys 一起返回即可。这是比较常用的方法,通过 Concat 的方式配合组装为 IEnumerable 返回,如下面代码,追加了三个配置项

  1. public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath)
  2. {
  3. return new string[] { "Foo.F1", "Foo.F2", "Foo.F3" }.Concat(earlierKeys);
  4. }

如果我是期望返回的时候进行排序,那我只需要在最终返回之前进行排序即可

另外,在追加的时候,也是包含顺序的,例如上面代码的追加,最终拿到的配置项列表大概如下

  1. [WTTSTDIO, C:\Program Files (x86)\Windows Kits\10\Hardware Lab Kit\Studio\]
  2. [windir, C:\Windows]
  3. ...
  4. [APPDATA, C:\Users\lindexi\AppData\Roaming]
  5. [ALLUSERSPROFILE, C:\ProgramData]
  6. [AllowedHosts, *]
  7. [, ]
  8. [:ASPNETCORE_BROWSER_TOOLS, true]
  9. [:Foo.F3, ]
  10. [:Foo.F2, ]
  11. [:Foo.F1, ]
  12. [Foo.F3, ]
  13. [Foo.F2, ]
  14. [Foo.F1, ]

而如果我是这样写的:

  1. return earlierKeys.Concat(new string[] { "Foo.F1", "Foo.F2", "Foo.F3" });

那么拿到的配置列表大概如下

  1. [Foo.F3, ]
  2. [Foo.F2, ]
  3. [Foo.F1, ]
  4. [WTTSTDIO, C:\Program Files (x86)\Windows Kits\10\Hardware Lab Kit\Studio\]
  5. [windir, C:\Windows]
  6. ...
  7. [APPDATA, C:\Users\lindexi\AppData\Roaming]
  8. [ALLUSERSPROFILE, C:\ProgramData]
  9. [AllowedHosts, *]
  10. [, ]
  11. [:Foo.F3, ]
  12. [:Foo.F2, ]
  13. [:Foo.F1, ]
  14. [:ASPNETCORE_BROWSER_TOOLS, true]

这里有一点需要注意的是,返回的时候,需要根据 parentPath 的内容,过滤掉当前 IConfigurationProvider 能提供的配置,才能和 earlierKeys 组合后返回

  1. public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath)
  2. {
  3. // 这个方法的作用其实有两个:
  4. // 1. 对其他的 IConfigurationProvider 的结果进行过滤
  5. // 2. 返回给框架层,此 IConfigurationProvider 提供的配置项
  6. // 自己测试:
  7. // 1. 什么都不做,返回的是 earlierKeys 的内容
  8. // 2. 直接返回 Array.Empty<string>();
  9. // 3. 拼接出新的列表,如
  10. // 例如这个类型提供的配置里面,包含的是 Foo.F1=123; Foo.F2=123; Foo.F3=123 三个值内容
  11. // 传入的 父路径(parentPath) 是叫做 `Foo` 那么就应该将 `Foo.F1` 和 `Foo.F2` 和 `Foo.F3` 三个 Key 项合并 earlierKeys 进行返回
  12. // 默认都是采用 Concat(earlierKeys) 的方式进行返回的
  13. // 那什么情况下不是采用直接 Concat(earlierKeys) 的方式?嗯,需要过滤掉,或者是需要进行重新排序
  14. if (string.IsNullOrEmpty(parentPath)) // 加上一些判断逻辑
  15. {
  16. /*
  17. [WTTSTDIO, C:\Program Files (x86)\Windows Kits\10\Hardware Lab Kit\Studio\]
  18. [windir, C:\Windows]
  19. ...
  20. [APPDATA, C:\Users\lindexi\AppData\Roaming]
  21. [ALLUSERSPROFILE, C:\ProgramData]
  22. [AllowedHosts, *]
  23. [, ]
  24. [:ASPNETCORE_BROWSER_TOOLS, true]
  25. [:Foo.F3, ]
  26. [:Foo.F2, ]
  27. [:Foo.F1, ]
  28. [Foo.F3, ]
  29. [Foo.F2, ]
  30. [Foo.F1, ]
  31. */
  32. return new string[] { "Foo.F1", "Foo.F2", "Foo.F3" }.Concat(earlierKeys);
  33. }
  34. return earlierKeys;
  35. }

在大部分的情况下的返回值都是判断 parentPath 参数,通过此参数过滤当前的 IConfigurationProvider 能提供的配置。再使用 Concat 方法,和 earlierKeys 组合后返回

以及在某些情况下,将 earlierKeys 给进行一次过滤或排序之后再返回。如下面代码,对 earlierKeys 进行 Where 之后再一次组合

  1. return new string[] { "Foo.F1", "Foo.F2", "Foo.F3" }.Concat(earlierKeys.Where(t => !t.StartsWith("Foo")))

换句话说就是,大部分时候传入的 earlierKeys 参数是需要在返回值返回的,或者是参与了一定的计算之后再返回,而不是吞掉,直接返回一个自定义的列表

如果和本文开始的方法一样,返回了 Array.Empty<string>() 那就意味着在这个 IConfigurationProvider 里面,提供的功能是将所有的配置项都给过滤掉。于是各个需要枚举所有配置内容的业务都会影响找不到期望的配置而炸掉

可以看到 IConfigurationProvider 的 GetChildKeys 方法还是很强大的。从定义的设计上,既满足了支持过滤其他的 IConfigurationProvider 提供的配置,又支持加入自身的定制的配置。同时依靠 dotnet 提供的强大的 IEnumerable 能力,可以做到无大内存空间分配。如上面代码的 Concat 和 Where 等,本质都是延迟执行且无需重新申请数组空间,这部分知识详细请自行了解 dotnet 基础知识

另外,如果只是纯粹想多添加一些新的配置到应用,除了直接继承 IConfigurationProvider 之外,还可以继承 ConfigurationProvider 类型。继承 ConfigurationProvider 类型之后,可以给他添加新的配置,其他的琐杂的工作就都交给 ConfigurationProvider 处理

  1. class ReadonlyCoinConfiguration : ConfigurationProvider, IConfigurationSource
  2. {
  3. public IConfigurationProvider Build(IConfigurationBuilder builder)
  4. {
  5. return this;
  6. }
  7. public override void Load()
  8. {
  9. Set("Foo.F1", "123");
  10. Set("Foo.F2", "123");
  11. Set("Foo.F3", "123");
  12. }
  13. }

如以上的代码,可以看到,只需要重写 Load 方法,在此方法里面,将所能提供的配置项调用 Set 方法写入即可

dotnet 理解 IConfigurationProvider 的 GetChildKeys 方法用途的更多相关文章

  1. 深度理解Jquery 中 offset() 方法

    参考原文:深度理解Jquery 中 offset() 方法

  2. Java反射理解(五)-- 方法反射的基本操作

    Java反射理解(五)-- 方法反射的基本操作 方法的反射 1. 如何获取某个方法 方法的名称和方法的参数列表才能唯一决定某个方法 2. 方法反射的操作 method.invoke(对象,参数列表) ...

  3. 深入理解javascript的getTime方法

    1.理解getTime getTime() 方法返回一个时间的格林威治时间数值. 可以使用这个方法把一个日期时间赋值给另一个Date 对象. 语法: dateObj.getTime() 参数: 无. ...

  4. DotNet生成随机数的一些方法

    在项目开发中,一般都会使用到“随机数”,但是在DotNet中的随机数并非真正的随机数,可在一些情况下生成重复的数字,现在总结一下在项目中生成随机数的方法. 1.随机布尔值: /// <summa ...

  5. 理解PagerAdapter的instantiateItem()方法

    在为ViewPager设置Adapter时肯定会用到PagerAdapter,Google Android文档对该类的定义如下: Base class providing the adapter to ...

  6. 深入理解为什么Java中方法内定义的内部类可以访问方法中的局部变量

    好文转载:http://blog.csdn.net/zhangjg_blog/article/details/19996629 开篇 在我的上一篇博客 深入理解Java中为什么内部类可以访问外部类的成 ...

  7. Android之Inflate()方法用途

    转自:http://blog.csdn.net/andypan1314/article/details/6715928 Inflate()作用就是将xml定义的一个布局找出来,但仅仅是找出来而且隐藏的 ...

  8. Java中finalize方法用途何在?

    package thinking.in.java.demo; /* * finalize的用途何在? * *本例的终止条件是L所有的Book对象在被当做垃圾回收前都应该被签入.但是在main方法中 * ...

  9. 理解jquery的.on()方法

    jquery在的.on()方法用来给元素绑定事件处理函数的,我经常用在两个地方: 给未来的元素绑定事件:我总是这样用:$(document).on('click','#div1',function() ...

  10. 读取本地文件理解FileReader对象的方法和事件以及上传按钮的美化。

    一.FileReader对象 用来把文件读入内存,并且读取文件中的数据.FileReader对象提供了异步API,使用该API可以在浏览器主线程中异步访问文件系统,读取文件中的数据. 浏览器支持情况, ...

随机推荐

  1. Sp效率分析和理解

    目录介绍 01.Sp简单介绍 1.1 Sp作用分析 1.2 案例分析思考 02.Sp初始化操作 2.1 如何获取sp 2.2 SharedPreferencesImpl构造 03.edit方法源码 0 ...

  2. 「AntV」景点轨迹数据获取与L7可视化

    1. 引言 L7 地理空间数据可视分析引擎是一种基于 WebGL 技术的地理空间数据可视化引擎,可以用于实现各种地理空间数据可视化应用.L7 引擎支持多种数据源和数据格式,包括 GeoJSON.CSV ...

  3. vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.是什么 权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源 而前端权限归根结底是请求的发起权,请求的发起可 ...

  4. 记录--前端换肤方案 - element+less无感换肤(无需页面刷新)

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前端换肤方案 - element+less无感换肤(无需页面刷新) 前言 前不久在改造一个迭代了一年多的项目时,增加了一个换肤功能.通过自 ...

  5. 百度文库内容复制 C# webbrowser+Nsoup

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. 金仓数据库kbcrypto 插件实现sm加密算法

    首先介绍一下sm4 算法 SM4 算法是对称加密算法,国标 GB/T 32907 对 SM4 对称加密算法进行了详细描述.SM4 算法密钥长度固定为128bit,加密解密采用相同的密钥,加解密速度较快 ...

  7. KingbaseES V8R6运维案例之---wal日志解析DML操作

    案例说明: 通过sys_waldump解析DML操作,获取DML操作的日志条目具体内容. 适用版本: KingbaseES V8R3/R6 一.DML事务操作对应的wal日志文件 # 查看当前onli ...

  8. 在idea/webstorm等terminal运行命令报错:Command rejected by the operating system没有权限【已解决】

    在idea/webstorm等编译器terminal窗口运行命令报错:Command rejected by the operating system没有权限[已解决] 1.修改terminal窗口 ...

  9. #构造,黑白染色#AT4378 [AGC027D] Modulo Matrix

    题目 构造一个 \(n*n(n\leq 500)\) 的矩阵,满足元素均为正整数,不超过 \(10^15\) 且互不相同, 并且相邻两数若较大的为 \(x\),较小的为 \(y\),那么任意相邻两数 ...

  10. 中文GPTS,字节中文扣子Coze使用全教程

    字节出自己的GPTS了,名字英文名叫coze,中文名叫"扣子".和OpenAI的GPTS类似.具有可定制性和完成特定任务的强大功能,它提供了一种新的GPT方式,可以让用户根据自己的 ...