在之前的文章 ASP.NET Core 自定义配置源 和 ASP.NET Core etcd 配置源 中主要是介绍如何实现自定义的配置源,但不论内置的和自定义的配置源,都会面临如何使配置修改后实时生效的问题(修改配置后在不重启服务的情况下能马上生效)。在 ASP.NET Core etcd 配置源 的最后部分其实有用到 IOptionsSnapshot Options 快照的方式获取到最新配置,但其实这里依然不是实时数据,所以本文将继续深入介绍配置使用方式及内部处理机制。(以下测试代码基于 ASP.NET Core 3.1)。

Configuration 模式

在 ASP.NET Core Web API 应用程序中 IConfiguration 服务默认已以单例模式注入到 services 中,含以下 ConfigurationProvider 中的配置信息,同时对 appsettings.jsonappsettings.Development.json 已设置 reloadOnChange 为 true,即文件内容有变化将自动更新到 IConfiguration 的对象中:

代码如下:

public class TestController : ControllerBase
{private readonly IConfiguration _configuration;public TestController(IConfiguration configuration){_configuration = configuration;}[HttpGet]public string Get(){return _configuration["Name"];}
}

在不重启服务的情况下修改 Name 的值,重新调用接口将会马上获取到最新值,所以通过从 IConfiguration 中获取配置可以做到实时生效,这主要是因为在 JsonConfigurationProvider 的实现中当设置 reloadOnChange 为 true,则会监控文件的变化,一旦有变则重新加载当前 Provider 的配置信息,整个原理和 ASP.NET Core etcd 配置源 的实现类似,JsonConfigurationProvider 源码[1]

Options 模式

Options 模式 也是比较常用的一种配置使用方式,通过将某个 Section 的配置信息以对象的方式注入到服务中,在程序中使用将更加方便与形象,在 Options 模式 主要有 3 种使用方式,分别是:IOptionsIOptionsSnapshotIOptionsMonitor

使用方式及表现

首先在 appsettings.json 中添加如下配置:

{..."UserOption": {"Name": "beck"}
}

然后在 startup.csConfigureServices 注册配置服务:

public void ConfigureServices(IServiceCollection services)
{services.Configure<UserOption>(Configuration.GetSection("UserOption"));services.AddControllers();
}

使用方式如下:

public class TestController : ControllerBase
{private readonly IOptions<UserOption> _options;public TestController(IOptions<UserOption> options){_options = options;}[HttpGet]public void Get(){Console.WriteLine(_options.Value.Name);Thread.Sleep(5000); // 等待过程中,手动修改配置文件Console.WriteLine(_options.Value.Name);}
}

分布使用 IOptionsIOptionsSnapshotIOptionsMonitor(需改为 _options.CurrentValue) 进行测试,每种测试对配置文件中 Name 的值进行调整,具体表现结果如下:

  • IOptions:本次请求内修改不会生效,重新请求也不会生效,需重启服务;

  • IOptionsSnapshot:本次请求内修改不会生效,重新请求生效;

  • IOptionsMonitor:实时生效;

处理机制分析

基于以上表现结果,我们接下来通过 Options 源码[2] 来分析其本质原因。

首先在服务启动阶段 HostBuilder 的 Build 方法中调用 services.AddOptions() 进行 Options 相关服务的注册,代码如下:

public static IServiceCollection AddOptions(this IServiceCollection services)
{if (services == null){throw new ArgumentNullException(nameof(services));}services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));return services;
}

从代码中可以明显的看出 IOptionsIOptionsSnapshot 对应的实现都是OptionsManagerIOptions 的生命周期是 Singleton 模式,IOptionsSnapshot 的生命周期是 Scoped 模式,所以这个就比较好解释为什么 IOptions 方式下配置调整后需要重启才能生效,而 IOptionsSnapshot 是每次请求内不变,重新请求会变化的原因了。

另外 IOptionsMonitor 的具体实现是 OptionsMonitor,生命周期是 Singleton 模式。同时还包含了 IOptionsFactoryIOptionsMonitorCache 两个服务的注册,它们也是 Options 模式 下核心部分。

这几个服务之间的关系如下:

IOptionsFactory 主要负责创建 TOptions 类型的具体对象,核心代码如下:

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{public TOptions Create(string name){var options = new TOptions();foreach (var setup in _setups){if (setup is IConfigureNamedOptions<TOptions> namedSetup){namedSetup.Configure(name, options);}else if (name == Options.DefaultName){setup.Configure(options);}}foreach (var post in _postConfigures){post.PostConfigure(name, options);}if (_validations != null){var failures = new List<string>();foreach (var validate in _validations){var result = validate.Validate(name, options);if (result.Failed){failures.Add(result.FailureMessage);}}if (failures.Count > 0){throw new OptionsValidationException(name, typeof(TOptions), failures);}}return options;}
}

其中 _setups 的来源即最开始 services.Configure 注册的配置服务,_postConfigures 的来源是以 services.PostConfigure 方式注册的配置服务(本例中未使用到),它们的区别是 PostConfigure 注册的服务将在 Configure 之后执行。_validations 是参数合法性验证集合,如果有需要,可以在服务注册时指定。

OptionsManager 同时是 IOptionsIOptionsSnapshot 的实现,内部通过 OptionsCache 缓存 IOptionsFactory 创建的具体 TOptions 对象,区别在于创建出的具体对象生命周期不一样,核心代码如下:

public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{public TOptions Value{get{return Get(Options.DefaultName);}}public virtual TOptions Get(string name){name = name ?? Options.DefaultName;return _cache.GetOrAdd(name, () => _factory.Create(name));}
}

OptionsMonitorIOptionsMonitor 的实现,内部通过 IOptionsMonitorCache 缓存 IOptionsFactory 创建的具体 TOptions 对象,同时对于采用 IConfiguration 作为数据源类型的,通过 ChangeToken.OnChange 监听变化并实时更新配置。

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : class, new()
{public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache){foreach (var source in _sources){ChangeToken.OnChange<string>(() => source.GetChangeToken(),(name) => InvokeChanged(name),source.Name);}}private void InvokeChanged(string name){name = name ?? Options.DefaultName;_cache.TryRemove(name);var options = Get(name);if (_onChange != null){_onChange.Invoke(options, name);}}public TOptions CurrentValue{get => Get(Options.DefaultName);}public virtual TOptions Get(string name){name = name ?? Options.DefaultName;return _cache.GetOrAdd(name, () => _factory.Create(name));}
}

自实现 OptionsMonitor

Configuration 对象默认提供了 GetReloadToken 方法,所以我们也可以通过监听 Token 的变化自己实现类似 OptionsMonitor 的效果,毕竟有时候并不会选择 Configuration 模式Options 模式,以下是在控制台程序中的使用,假设使用 Autofac 作为 DI 容器,SetUserOption 内将可重新注册 UserOption 对象。

private static UserOption userOption;static void Main(string[] args)
{var configurationRoot = GetRoot();ChangeToken.OnChange(() => configurationRoot.GetReloadToken(), () =>{SetUserOption(configurationRoot);Console.WriteLine(userOption.Name);});Console.WriteLine("started");Console.ReadKey();
}private static IConfigurationRoot GetRoot()
{var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", true, true);return builder.Build();
}private static void SetUserOption(IConfigurationRoot configuration)
{userOption = configuration.GetSection("UserOption").Get<UserOption>();
}

参考资料

[1]

JsonConfigurationProvider 源码: https://github.com/aspnet/Configuration/blob/master/src/Config.Json/JsonConfigurationProvider.cs

[2]

Options 源码: https://github.com/aspnet/Options/tree/master/src/Microsoft.Extensions.Options

ASP.NET Core 配置源:实时生效相关推荐

  1. 关于Asp.net core配置信息读取的源码分析梳理

    概述 我们都知道asp.net core配置信息的读取离不开IConfigurationSource和IConfigurationProvider这两个类,ConfigurationSource可以提 ...

  2. ASP.NET Core 框架源码地址

    ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet  ...

  3. ASP.NET Core 配置 - 创建自定义配置提供程序

    ASP.NET Core 配置 - 创建自定义配置提供程序 在本文中,我们将创建一个自定义配置提供程序,从数据库读取我们的配置.我们已经了解了默认配置提供程序的工作方式,现在我们将实现我们自己的自定义 ...

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

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

  5. ASP.NET Core配置Kestrel 网址Urls

    ASP.NET Core中如何配置Kestrel Urls呢,大家可能都知道使用UseUrls() 方法来配置. 今天给介绍全面的ASP.NET Core 配置 Urls,使用多种方式配置Urls. ...

  6. 以正确的方式下载和配置 ASP.NET Core 官方源码

    我们可以在Github上面直接查看ASP.NETCore 3.x的源代码,但是我们也可以把源代码下载下来进行查看. 而下载源代码进行查看有很多好处: 任意的导航源代码 内置了一个示例项目 直接调试源代 ...

  7. Do you kown Asp.Net Core -- 配置Kestrel端口

    Kestrel介绍 在Asp.Net Core中,我们的web application 其实是运行在Kestrel服务上,它是一个基于libuv开源的跨平台可运行 Asp.Net Core 的web服 ...

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

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

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

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

最新文章

  1. SAP PM 入门系列14 – PM模块与其它模块的集成
  2. Spring中的AOP(三)——基于Annotation的配置方式(一)
  3. 基于数据库的分布式锁实现
  4. VMware推出TrustPoint产品,完善终端用户计算方案
  5. 145. 二叉树的后序遍历
  6. Python调用C语言
  7. python元类的使用_python中元类用法实例
  8. ADO.NET、ODP.NET、Linq to SQL、ADO.NET Entity 、NHibernate在Oracle下的性能比较
  9. C语言 ,嵌入式 ,数据结构 面试题目(3)
  10. C++学习——string
  11. 局域网聊天软件 设计文档怎么写?
  12. java queue 清空_java swing清除事件队列
  13. Subscription expires on 2017/2/25. Usage of PhpStorm will no longer be possible.
  14. 2020华为软挑热身赛 个人总结
  15. 深入理解Web Components
  16. 【活动报名】NEO 区块链公开课(1): NEO 区块链开发入门
  17. python写窗体程序_python写窗口
  18. MongoDB Tailable Cursors
  19. iOS 绘制股票K线图
  20. 被查封7周之后,全球最大BT网站“海盗湾”又重新活过来了【36kr】

热门文章

  1. memcached 和 redis 的区别与选择
  2. 产品需求文档 PRD
  3. 【啊哈!算法】之二、插入排序
  4. php 打印对象详细信息,php打印显示数组与对象的函数详解
  5. 如何将Outgoing Webhook部署到中国版Azure
  6. vcenter 6.7 (vcsa)部署指南
  7. 机器视觉技术在表面缺陷检测方面的发展趋势
  8. jsp 连接access数据库
  9. JS基础入门篇( 一 )
  10. 用户反馈KB3189866累积更新出现卡在95%进度情况