您现在的位置是:网站首页> 编程资料编程资料

.NET6 ConfigurationManager的实现及使用方式_实用技巧_

2023-05-24 294人已围观

简介 .NET6 ConfigurationManager的实现及使用方式_实用技巧_

前言

友情提示:建议阅读本文之前先了解下.Net Core配置体系相关,也可以参考本人之前的文章《.Net Core Configuration源码探究 》然后对.Net Core的Configuration体系有一定的了解,使得理解起来更清晰。

在.Net6中关于配置相关多出一个关于配置相关的类ConfigurationManager,如果大概了解过Minimal API中的WebApplicationBuilder类相信你肯定发现了,在Minimal API中的配置相关属性Configuration正是ConfigurationManager的对象。ConfigurationManager本身并没有引入新的技术,也不是一个体系,只是在原来的基础上进行了进一步的封装,使得配置体系有了一个新的外观操作,暂且可以理解为新瓶装旧酒。本文我们就来了解下ConfigurationManager类,来看下微软为何在.Net6中会引入这么一个新的操作。

使用方式

关于.Net6中ConfigurationManager的使用方式,我们先通过简单的示例演示一下

 ConfigurationManager configurationManager = new(); configurationManager.AddJsonFile("appsettings.json",true,reloadOnChange:true); string serviceName = configurationManager["ServiceName"]; Console.WriteLine(serviceName); 

当然,关于获取值得其他方式。比如GetSection、GetChildren相关方法还是可以继续使用的,或者使用Binder扩展包相关的Get()GetValue("nacos")类似的方法也照样可以使用。那它和之前的.Net Core上的配置使用起来有什么不一样呢,我们看一下之前配置相关的使用方式,如下所示

 IConfigurationBuilder configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); IConfiguration configuration = configurationBuilder.Build(); string serviceName = configuration["ServiceName"]; Console.WriteLine(serviceName); 

这里需要注意的是,如果你是使用ConfigurationManager或者是IConfiguration封装的Helper类相关,并没有通过框架体系默认注入的时候,一定要注意将其设置为单例模式。其实这个很好理解,先不说每次用的时候都去实例化带来的内存CPU啥的三高问题。读取配置文件本质不就是把数据读到内存中吗?内存中有一份缓存这就好了,每次都去重新实例去读本身就是一种不规范的方式。许多时候如果你实在不知道该定义成什么样的生命周期,可以参考微软的实现方式,以ConfigurationManager为例,我们可以参考WebApplicationBuilder类中对ConfigurationManager注册的生命周期[点击查看源码]

 public ConfigurationManager Configuration { get; } = new(); //这里注册为了单例模式 Services.AddSingleton(_ => Configuration); 

通过上面我们演示的示例可以看出在ConfigurationManager的时候注册配置和读取配置相关都只是使用了这一个类。而在之前的配置体系中,注册配置需要使用IConfigurationBuilder,然后通过Build方法得到IConfiguration实例,然后读取是通过IConfiguration实例进行的。本身操作配置的时候IConfigurationBuilder和IConfiguration是满足单一职责原则没问题,像读取配置这种基础操作,应该是越简单越好,所以微软才进一步封装了ConfigurationManager来简化配置相关的操作。

在.Net6中微软并没有放弃IConfigurationBuilder和IConfiguration,因为这是操作配置文件的基础类,微软只是借助了它们两个在上面做了进一层封装而已,这个是需要我们了解的。

源码探究

上面我们了解了新的ConfigurationManager的使用方式,这里其实我们有疑问了,为什么ConfigurationManager可以进行注册和读取操作。上面我提到过ConfigurationManager本身就是新瓶装旧酒,而且它只是针对原有的配置体系做了一个新的外观,接下来哦我们就从源码入手,看一下它的实现方式。

定义入手

首先来看一下ConfigurationManager的的定义,如下所示[点击查看源码]

 public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable { } 

其实只看它的定义就可以解答我们心中的大部分疑惑了,之所以ConfigurationManager能够满足IConfigurationBuilder和IConfigurationRoot这两个操作的功能是因为它本身就是实现了这两个接口,集它们的功能于一身了,IConfigurationRoot接口本身就集成自IConfiguration接口。因此如果给ConfigurationManager换个马甲的话你就会发现还是原来的配方还是原来的味道

 ConfigurationManager configurationManager = new(); IConfigurationBuilder configurationBuilder = configurationManager.AddJsonFile("appsettings.json", true, reloadOnChange: true); //尽管放心的调用Build完全不影响啥 IConfiguration configuration = configurationBuilder.Build(); string serviceName = configuration["ServiceName"]; Console.WriteLine(serviceName); 

这种写法只是为了更好的看清它的本质,如果真实操作这么写,确实有点画蛇添足了,因为ConfigurationManager本身就是为了简化我们的操作。

认识IConfigurationBuilder和IConfiguration

通过上面我们了解到ConfigurationManager可以直接注册过配置文件就可以直接去操作配置文件里的内容,这一步是肯定通过转换得到的,毕竟之前的方式我们是通过IConfigurationBuilder的Build操作得到的IConfiguration的实例,那么我们就先来看下原始的方式是如何实现的。这里需要从IConfigurationBuilder的默认实现类ConfigurationBuilder说起,它的实现很简单[点击查看源码]

 public class ConfigurationBuilder : IConfigurationBuilder { ///  /// 添加的数据源被存放到了这里 ///  public IList Sources { get; } = new List(); public IDictionary Properties { get; } = new Dictionary(); ///  /// 添加IConfigurationSource数据源 ///  ///  public IConfigurationBuilder Add(IConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Sources.Add(source); return this; } public IConfigurationRoot Build() { //获取所有添加的IConfigurationSource里的IConfigurationProvider var providers = new List(); foreach (var source in Sources) { var provider = source.Build(this); providers.Add(provider); } //用providers去实例化ConfigurationRoot return new ConfigurationRoot(providers); } } 

这里我们来解释一下,其实我们注册配置相关的时候比如AddJsonFile()、AddEnvironmentVariables()、AddInMemoryCollection()等等它们其实都是扩展方法,本质就是添加IConfigurationSource实例,而IConfigurationBuilder的Build本质操作其实就是在IConfigurationSource集合中得到IConfigurationProvider集合,因真正从配置读取到的数据都是包含在IConfigurationProvider实例中的,ConfigurationRoot通过一系列的封装,让我们可以更便捷的得到配置里相关的信息。这就是ConfigurationBuilder的工作方式,也是配置体系的核心原理。
我们既然知道了添加配置的本质其实就是IConfigurationBuilder.Add(IConfigurationSource source)那么我就来看一下ConfigurationManager是如何实现这一步的。我们知道ConfigurationManager实现了IConfigurationBuilder接口,所以必然重写了IConfigurationBuilder的Add方法,找到源码位置[点击查看源码]

 private readonly ConfigurationSources _sources = new ConfigurationSources(this); ; IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) { _sources.Add(source ?? throw new ArgumentNullException(nameof(source))); return this; } 

这里返回了this也就是当前ConfigurationManager实例是为了可以进行链式编程,ConfigurationSources这个类是个新物种,原来的类叫ConfigurationSource,这里多了个s表明了这是一个集合类,我们就来看看它是个啥操作,找到源码位置[点击查看源码]

 ///  /// 本身是一个IConfigurationSource集合 ///  private class ConfigurationSources : IList { private readonly List _sources = new(); private readonly ConfigurationManager _config; ///  /// 因为是ConfigurationManager的内部类所以传递了当前ConfigurationManager实例 ///  ///  public ConfigurationSources(ConfigurationManager config) { _config = config; } ///  /// 根据索引获取其中一个IConfigurationSource实例 ///  ///  public IConfigurationSource this[int index] { get => _sources[index]; set { _sources[index] = value; _config.ReloadSources(); } } public int Count => _sources.Count; public bool IsReadOnly => false; ///  /// 这是重点添加配置源 ///  ///  public void Add(IConfigurationSource source) { //给自己的IConfigurationSource集合添加 _sources.Add(source); //调用了ConfigurationManager的AddSource方法 _config.AddSource(source); } ///  /// 实现IList清除操作 ///  public void Clear() { _sources.Clear(); //这里可以看到ConfigurationManager的ReloadSources方法很重要 //通过名字可以看出是刷新配置数据用的 _config.ReloadSources(); } public void Insert(int index, IConfigurationSource source) { _sources.Insert(index, source); _config.ReloadSources(); } public bool Remove(IConfigurationSource source) { var removed = _sources.Remove(source); _config.ReloadSources(); return removed; } public void RemoveAt(int index) { _sources.RemoveAt(index); _config.ReloadSources(); } //这里省略了实现了实现IList接口的其他操作 //ConfigurationSources本身就是IList } 

正如我们看到的那样ConfigurationSources本身就是一个IConfigurationSource的集合,在新的.Net体系中微软喜欢把集合相关的操作封装一个Collection类,这样的好处就是让大家能更清晰的了解它是功能实现类,而不在用一个数据结构的眼光去看待。通过源码我们还看到了Add方法里还调用了ConfigurationManager的AddSource方法,这究竟是一个什么操作我们来看下[点击查看源码]

 private readonly object _providerLock = new(); private readonly List _providers = new(); private readonly List _changeTokenRegistrations = new(); private void AddSource(IConfigurationSource source) { lock (_providerLock) { //在IConfigurationSource中得到IConfigurationProvider实例 var provider = source.Build(this); //添加到_providers集合中 //我们提到过从配置源得到的配置都是通过IConfigurationProvider得到的 _providers.Add(provider); //IConfigurationProvider的Load方法是从配置源中得到配置数据加载到程序内存中 provider.Load(); //注册更改令牌操作,使得配置可以进行动态刷新加载 _changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged())); } //添加新的配置源要刷新令牌操作 RaiseChanged(); } private ConfigurationReloadToken _changeToken = new(); private void RaiseChanged() { //每次对配置源进行更改操作需要得到新的更改令牌实例,用于可重复通知配置变更相关 var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken.OnReload(); } 

从上面的ConfigurationSources方法里我们可以看到动态的针对ConfigurationSources里的ConfigurationSource进行更改会每次都调用ReloadSources方法,我们来看一下它的实现[点击查看源码]

 private readonl
                
                

-六神源码网