ASP.NET Core 配置源:实时生效
在之前的文章 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.json
和 appsettings.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 种使用方式,分别是:IOptions
、IOptionsSnapshot
、IOptionsMonitor
。
使用方式及表现
首先在 appsettings.json
中添加如下配置:
{..."UserOption": {"Name": "beck"}
}
然后在 startup.cs
的 ConfigureServices
注册配置服务:
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);}
}
分布使用 IOptions
、IOptionsSnapshot
、IOptionsMonitor
(需改为 _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;
}
从代码中可以明显的看出 IOptions
、IOptionsSnapshot
对应的实现都是OptionsManager
,IOptions
的生命周期是 Singleton
模式,IOptionsSnapshot
的生命周期是 Scoped
模式,所以这个就比较好解释为什么 IOptions
方式下配置调整后需要重启才能生效,而 IOptionsSnapshot
是每次请求内不变,重新请求会变化的原因了。
另外 IOptionsMonitor
的具体实现是 OptionsMonitor
,生命周期是 Singleton
模式。同时还包含了 IOptionsFactory
和 IOptionsMonitorCache
两个服务的注册,它们也是 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
同时是 IOptions
和 IOptionsSnapshot
的实现,内部通过 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));}
}
OptionsMonitor
是 IOptionsMonitor
的实现,内部通过 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 配置源:实时生效相关推荐
- 关于Asp.net core配置信息读取的源码分析梳理
概述 我们都知道asp.net core配置信息的读取离不开IConfigurationSource和IConfigurationProvider这两个类,ConfigurationSource可以提 ...
- ASP.NET Core 框架源码地址
ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet ...
- ASP.NET Core 配置 - 创建自定义配置提供程序
ASP.NET Core 配置 - 创建自定义配置提供程序 在本文中,我们将创建一个自定义配置提供程序,从数据库读取我们的配置.我们已经了解了默认配置提供程序的工作方式,现在我们将实现我们自己的自定义 ...
- ASP.NET Core MVC 源码学习:MVC 启动流程详解
前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...
- ASP.NET Core配置Kestrel 网址Urls
ASP.NET Core中如何配置Kestrel Urls呢,大家可能都知道使用UseUrls() 方法来配置. 今天给介绍全面的ASP.NET Core 配置 Urls,使用多种方式配置Urls. ...
- 以正确的方式下载和配置 ASP.NET Core 官方源码
我们可以在Github上面直接查看ASP.NETCore 3.x的源代码,但是我们也可以把源代码下载下来进行查看. 而下载源代码进行查看有很多好处: 任意的导航源代码 内置了一个示例项目 直接调试源代 ...
- Do you kown Asp.Net Core -- 配置Kestrel端口
Kestrel介绍 在Asp.Net Core中,我们的web application 其实是运行在Kestrel服务上,它是一个基于libuv开源的跨平台可运行 Asp.Net Core 的web服 ...
- ASP.NET Core MVC 源码学习:详解 Action 的匹配
前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...
- ASP.NET Core MVC 源码学习:Routing 路由
前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...
最新文章
- SAP PM 入门系列14 – PM模块与其它模块的集成
- Spring中的AOP(三)——基于Annotation的配置方式(一)
- 基于数据库的分布式锁实现
- VMware推出TrustPoint产品,完善终端用户计算方案
- 145. 二叉树的后序遍历
- Python调用C语言
- python元类的使用_python中元类用法实例
- ADO.NET、ODP.NET、Linq to SQL、ADO.NET Entity 、NHibernate在Oracle下的性能比较
- C语言 ,嵌入式 ,数据结构 面试题目(3)
- C++学习——string
- 局域网聊天软件 设计文档怎么写?
- java queue 清空_java swing清除事件队列
- Subscription expires on 2017/2/25. Usage of PhpStorm will no longer be possible.
- 2020华为软挑热身赛 个人总结
- 深入理解Web Components
- 【活动报名】NEO 区块链公开课(1): NEO 区块链开发入门
- python写窗体程序_python写窗口
- MongoDB Tailable Cursors
- iOS 绘制股票K线图
- 被查封7周之后,全球最大BT网站“海盗湾”又重新活过来了【36kr】