.NetCore的配置选项建议结合在一起学习,不了解.NetCore 配置Configuration的同学可以看下我的上一篇文章 [.Net Core配置Configuration源码研究]

由代码开始

定义一个用户配置选项

public class UserOptions
{
private string instanceId;
private static int index = 0;
public UserOptions()
{
instanceId = (++index).ToString("00");
Console.WriteLine($"Create UserOptions Instance:{instanceId}");
}
public string Name { get; set; }
public int Age { get; set; }
public override string ToString() => $"Name:{Name} Age:{Age} Instance:{instanceId} ";
}
public class UserOptions2
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString() => $" Name:{Name} Age:{Age}";
}

定义json配置文件:myconfig.json

{
"UserOption": {
"Name": "ConfigName-zhangsan",
"Age": 666
}
}

创建ServiceCollection

services = new ServiceCollection();
var configBuilder = new ConfigurationBuilder().AddInMemoryCollection().AddJsonFile("myconfig.json", true, true);
var iconfiguration = configBuilder.Build();
services.AddSingleton<IConfiguration>(iconfiguration);

示例代码

services.Configure<UserOptions>(x => { x.Name = "张三"; x.Age = new Random().Next(1, 10000); });
services.AddOptions<UserOptions2>().Configure<IConfiguration>((x, config) => { x.Name = config["UserOption:Name"]; x.Age = 100; }); ;
services.PostConfigure<UserOptions>(x => { x.Name = x.Name + "Post"; x.Age = x.Age; });
services.Configure<UserOptions>("default", x => { x.Name = "Default-张三"; x.Age = new Random().Next(1, 10000); });
services.Configure<UserOptions>("config", configuration.GetSection("UserOption"));
using (var provider = services.BuildServiceProvider())
{
using (var scope1 = provider.CreateScope())
{
PrintOptions(scope1, "Scope1");
} //修改配置文件
Console.WriteLine(string.Empty);
Console.WriteLine("修改配置文件");
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "myconfig.json");
File.WriteAllText(filePath, "{\"UserOption\": { \"Name\": \"ConfigName-lisi\", \"Age\": 777}}");
//配置文件的change回调事件需要一定时间执行
Thread.Sleep(300);
Console.WriteLine(string.Empty); using (var scope2 = provider.CreateScope())
{
PrintOptions(scope2, "Scope2");
} Console.WriteLine(string.Empty); using (var scope3 = provider.CreateScope())
{
PrintOptions(scope3, "Scope3");
}
} static void PrintOptions(IServiceScope scope, string scopeName)
{
var options1 = scope.ServiceProvider.GetService<IOptions<UserOptions>>();
Console.WriteLine($"手动注入读取,IOptions,{scopeName}-----{ options1.Value}"); var options2 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>();
Console.WriteLine($"配置文件读取,IOptionsSnapshot,{scopeName}-----{ options2.Value}");
var options3 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>();
Console.WriteLine($"配置文件根据名称读取,IOptionsSnapshot,{scopeName}-----{ options3.Get("config")}"); var options4 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>();
Console.WriteLine($"配置文件读取,IOptionsMonitor,{scopeName}-----{ options4.CurrentValue}");
var options5 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>();
Console.WriteLine($"配置文件根据名称读取,IOptionsMonitor,{scopeName}-----{options5.Get("config")}"); var options6 = scope.ServiceProvider.GetService<IOptions<UserOptions2>>();
Console.WriteLine($"Options2-----{options6.Value}");
}

代码运行结果

Create UserOptions Instance:01
手动注入读取,IOptions,Scope1----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:02
配置文件读取,IOptionsSnapshot,Scope1----- Name:张三Post Age:835 Instance:02
Create UserOptions Instance:03
配置文件根据名称读取,IOptionsSnapshot,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:03
Create UserOptions Instance:04
配置文件读取,IOptionsMonitor,Scope1----- Name:张三Post Age:1669 Instance:04
Create UserOptions Instance:05
配置文件根据名称读取,IOptionsMonitor,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:05
Options2----- Name:ConfigName-zhangsan Age:100 修改配置文件
Create UserOptions Instance:06 手动注入读取,IOptions,Scope2----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:07
配置文件读取,IOptionsSnapshot,Scope2----- Name:张三Post Age:5460 Instance:07
Create UserOptions Instance:08
配置文件根据名称读取,IOptionsSnapshot,Scope2----- Name:ConfigName-lisi Age:777 Instance:08
配置文件读取,IOptionsMonitor,Scope2----- Name:张三Post Age:1669 Instance:04
配置文件根据名称读取,IOptionsMonitor,Scope2----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100 手动注入读取,IOptions,Scope3----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:09
配置文件读取,IOptionsSnapshot,Scope3----- Name:张三Post Age:5038 Instance:09
Create UserOptions Instance:10
配置文件根据名称读取,IOptionsSnapshot,Scope3----- Name:ConfigName-lisi Age:777 Instance:10
配置文件读取,IOptionsMonitor,Scope3----- Name:张三Post Age:1669 Instance:04
配置文件根据名称读取,IOptionsMonitor,Scope3----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100

通过运行代码得到的结论

  • Options可通过手动初始化配置项配置(可在配置时读取依赖注入的对象)、或通过IConfiguration绑定配置
  • PostConfiger可在Configer基础上继续配置
  • 可通过IOptionsSnapshot或IOptionsMonitor根据配置名称读取配置项,未指定名称读取第一个注入的配置
  • IOptions和IOptionsMonitor生命周期为Singleton,IOptionsSnapshot生命周期为Scope
  • IOptionsMonitor可监听到配置文件变动去动态更新配置项

问题

  • IOptions,IOptionsSnapshot,IOptionsMonitor 如何/何时注入、初始化
  • Options指定名称时内部是如何设置的
  • Options如何绑定的IConfiguration
  • IOptionsMonitor是如何同步配置文件变动的

配合源码解决疑惑

Configure注入

public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
{
return services.Configure(Microsoft.Extensions.Options.Options.DefaultName, configureOptions);
} public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) where TOptions : class
{
services.AddOptions();
services.AddSingleton((IConfigureOptions<TOptions>)new ConfigureNamedOptions<TOptions>(name, configureOptions));
return services;
} public static IServiceCollection AddOptions(this IServiceCollection 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;
}

通过上面的源码可以发现,Options相关类是在AddOptions中注入的,具体的配置项在Configure中注入。

如果不指定Configure的Name,也会有个默认的Name=Microsoft.Extensions.Options.Options.DefaultName

那么我们具体的配置项存到哪里去了呢,在ConfigureNamedOptions这个类中,在Configer函数调用时,只是把相关的配置委托存了起来:

public ConfigureNamedOptions(string name, Action<TOptions> action)
{
Name = name;
Action = action;
}

OptionsManager

private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);

public TOptions Value => Get(Options.DefaultName);

public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
return _cache.GetOrAdd(name, () => _factory.Create(name));
}

OptionsManager实现相对较简单,在查询时需要执行Name,如果为空就用默认的Name,如果缓存没有,就用Factory创建一个,否则就读缓存中的选项。

IOptionsIOptionsSnapshot的实现类都是OptionsManager,只是生命周期不同。

OptionsFactory

那么OptionsFactory又是如何创建Options的呢?我们看一下他的构造函数,构造函数将所有ConfigurePostConfigure的初始化委托都通过构造函数保存在内部变量中

public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
{
_setups = setups;
_postConfigures = postConfigures;
}

接下来看Create(有删改,与本次研究无关的代码没有贴出来):

	public TOptions Create(string name)
{
//首先创建对应Options的实例
TOptions val = Activator.CreateInstance<TOptions>();
//循环所有的配置项,依次执行,如果对同一个Options配置了多次,最后一次的赋值生效
foreach (IConfigureOptions<TOptions> setup in _setups)
{
var configureNamedOptions = setup as IConfigureNamedOptions<TOptions>;
if (configureNamedOptions != null)
{
//Configure中会判断传入Name的值与本身的Name值是否相同,不同则不执行Action
//这解释了我们一开始的示例中,注入了三个UserOptions,但是在IOptionsSnapshot.Value中获取到的是第一个没有名字的
//因为Value会调用OptionsManager.Get(Options.DefaultName),进而调用Factory的Create(Options.DefaultName)
configureNamedOptions.Configure(name, val);
}
else if (name == Options.DefaultName)
{
setup.Configure(val);
}
} //PostConfigure没啥可多说了,名字判断逻辑与Configure一样
foreach (var postConfigure in _postConfigures)
{
postConfigure.PostConfigure(name, val);
} return val;
}

NamedConfigureFromConfigurationOptions

IConfiguration配置Options的方式略有不同

对应Configure扩展方法最终调用的代码在Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions这个类中

public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class
{
services.AddOptions();
services.AddSingleton((IOptionsChangeTokenSource<TOptions>)new ConfigurationChangeTokenSource<TOptions>(name, config));
return services.AddSingleton((IConfigureOptions<TOptions>)new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}

扩展方法里又注入了一个IOptionsChangeTokenSource,这个类的作用是提供一个配置文件变动监听的Token

同时将IConfigureOptions实现类注册成了NamedConfigureFromConfigurationOptions

NamedConfigureFromConfigurationOptions继承了ConfigureNamedOptions,在构造函数中用IConfiguration.Bind实现了生成Options的委托

	public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
: base(name, (Action<TOptions>)delegate(TOptions options)
{
config.Bind(options, configureBinder);
})

所以在Factory的Create函数中,会调用IConfigurationBind函数

由于IOptionsSnapshot生命周期是Scope,在配置文件变动后新的Scope中会获取最新的Options

ValidateOptions

OptionsBuilder还包含了一个Validate函数,该函数要求传入一个Func<TOptions,bool>的委托,会注入一个单例的ValidateOptions对象。

OptionsFactory构建Options的时候会验证Options的有效性,验证失败会抛出OptionsValidationException异常

对于ValidateOptionsPostConfigureOptions都是构建Options实例时需要用到的主要模块,不过使用和内部实现都较为简单,应用场景也不是很多,本文就不对这两个类多做介绍了

结论

Configure扩展函数中会首先调用AddOptions函数

IOptions,IOptionsSnapshot,IOptionsMonitor都是在AddOptions函数中注入的

Configure配置的选项配置委托最终会保存到ConfigureNamedOptionsNamedConfigureFromConfigurationOptions

IOptionsIOptionsSnapshot的实现类为OptionsManager

OptionsManager通过OptionsFactory创建Options的实例,并会以Name作为键存到字典中缓存实例

OptionsFactory会通过反射创建Options的实例,并调用ConfigureNamedOptions中的委托给实例赋值

现在只剩下最后一个问题了,OptionsMonitor是如何动态更新选项的呢?

其实前面的讲解中已经提到了一个关键的接口IOptionsChangeTokenSource,这个接口提供一个IChangeToken,通过ChangeToken监听这个Token就可以监听到文件的变动,我们来看下OptionsMonitor是否是这样做的吧!

//构造函数
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
_factory = factory;
_sources = sources;
_cache = cache;
//循环属于TOptions的所有IChangeToken
foreach (IOptionsChangeTokenSource<TOptions> source in _sources)
{
ChangeToken.OnChange(() => source.GetChangeToken(), delegate(string name)
{ //清除缓存
name = name ?? Options.DefaultName;
_cache.TryRemove(name);
}, source.Name);
}
} public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
return _cache.GetOrAdd(name, () => _factory.Create(name));
}

果然是这样的吧!

.Net Core 中的选项Options的更多相关文章

  1. (13)ASP.NET Core 中的选项模式(Options)

    1.前言 选项(Options)模式是对配置(Configuration)的功能的延伸.在12章(ASP.NET Core中的配置二)Configuration中有介绍过该功能(绑定到实体类.绑定至对 ...

  2. ASP.NET Core 学习笔记 第五篇 ASP.NET Core 中的选项

    前言 还记得上一篇文章中所说的配置吗?本篇文章算是上一篇的延续吧.在 .NET Core 中读取配置文件大多数会为配置选项绑定一个POCO(Plain Old CLR Object)对象,并通过依赖注 ...

  3. asp.net core 3.0 选项模式1:使用

    本篇只是从应用角度来说明asp.net core的选项模式,下一篇会从源码来分析 1.以前的方式 以前我们使用web.config/app.config时是这样使用配置的 var count = Co ...

  4. .NET Core 2.x中使用Named Options处理多个强类型配置实例

    来源: Using multiple instances of strongly-typed settings with named options in .NET Core 2.x 作者: Andr ...

  5. [ASP.NET Core 3框架揭秘] Options[1]: 配置选项的正确使用方式[上篇]

    依赖注入不仅是支撑整个ASP.NET Core框架的基石,也是开发ASP.NET Core应用采用的基本编程模式,所以依赖注入十分重要.依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式 ...

  6. [ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]

    四.直接初始化Options对象 前面演示的几个实例具有一个共同的特征,即都采用配置系统来提供绑定Options对象的原始数据,实际上,Options框架具有一个完全独立的模型,可以称为Options ...

  7. 理解ASP.NET Core - 选项(Options)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 Options绑定 上期我们已经聊过了配置(IConfiguration),今天我们来聊一聊O ...

  8. 基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传

    在基于SqlSugar的开发框架的服务层中处理文件上传的时候,我们一般有两种处理方式,一种是常规的把文件存储在本地文件系统中,一种是通过FTP方式存储到指定的FTP服务器上.这种处理应该由程序进行配置 ...

  9. Asp.Net Core中服务的生命周期选项区别和用法

    在做一个小的Demo中,在一个界面上两次调用视图组件,并且在视图组件中都调用了数据库查询,结果发现,一直报错,将两个视图组件的调用分离,单独进行,却又是正常的,寻找一番,发现是配置依赖注入服务时,对于 ...

随机推荐

  1. vue点击复制功能

    复制功能,选中复制或者点击复制(不使用插件的情况下) 1.选中复制 这个比点击复制简单点 <template>   <div>     <el-button type=& ...

  2. 微信小程序对接显示阿里云数据库数据

    现实需求 在项目中需求,有时候现场设备发生故障,需要远程的人员知道.除了邮件方式,以微信小程序的方式也很好.今天进行尝试,并制作了一个demo版本. 1.微信小程序申请制作前端 1.1lists文件( ...

  3. Go通关03:控制结构,if、for、switch逻辑语句

    if 条件语句 func main() { i:=6 if i >10 { fmt.Println("i>10") } else if i>5 && ...

  4. VS Code的插件安装位置改变

    VS Code的相关配置 VS Code的插件安装位置改变 可以通过创建连接,将默认的extensions位置,改变到D盘 Windows 链接彻底解决 vscode插件安装位置问题 mklink / ...

  5. [考试总结]noip模拟33

    连炸两场... 伤心... 第一个题目首先因为有期望坐镇,然后跳过... 然后第二个题目发现题目挺绕的,然后转化了一句话题意,然后..... \(\huge{\text{转化错了!!!!}}\) 然而 ...

  6. swift文件调用oc分类时崩溃解决办法(可能全网唯一)

    背景 oc为基础创建的sdk混编工程,在被sdk关联的混编demo工程中swift文件调用时,会崩溃,提示找不到sdk中oc分类方法.常规的,在demo中设置-Objc和-all_load也还是会崩. ...

  7. Nginx 解析漏洞

    目录 漏洞复现 漏洞成因 修复方案 参考链接 该漏洞与Nginx.php版本无关,属于用户配置不当造成的解析漏洞. 漏洞复现 访问http://172.17.0.1/uploadfiles/nginx ...

  8. 用 区间判断(if)来猜价格的高低

    1 #include <stdio.h> 2 #include <stdlib.h> 3 int main() 4 { 5 int price = 150; 6 int gue ...

  9. VueApp 自动更新解决plus is not defined问题

    一,今天用VueApp 做自动更新调用按照网上列子直接 Plus 打包编译后出现了plus is not defined 发现需要引用document.addEventListener("p ...

  10. Linux远程访问控制

    目录 一.SSH远程管理 1.1.定义 1.2.配置OpenSSH服务端 二.sshd服务支持两种验证方式 2.1.密码验证 2.2.使用SSH客户端程序 ssh远程登录 scp远程复制 sftp安全 ...