请选择 进入手机版 | 继续访问电脑版

[ASP.NET] .Net Core 中选项Options的具体 实现

  [复制链接]
查看302 | 回复81 | 2021-9-15 07:54:05 | 显示全部楼层 |阅读模式
目次

.NetCore的设置 选项建议联合 在一起学习,不相识 .NetCore 设置 Configuration的同砚 可以看下我的上一篇文章 [.Net Core设置 Configuration详细 实现]

由代码开始

定义一个用户设置 选项

  1. public class UserOptions
  2. {
  3. private string instanceId;
  4. private static int index = 0;
  5. public UserOptions()
  6. {
  7. instanceId = (++index).ToString("00");
  8. Console.WriteLine($"Create UserOptions Instance:{instanceId}");
  9. }
  10. public string Name { get; set; }
  11. public int Age { get; set; }
  12. public override string ToString() => $"Name:{Name} Age:{Age} Instance:{instanceId} ";
  13. }
  14. public class UserOptions2
  15. {
  16. public string Name { get; set; }
  17. public int Age { get; set; }
  18. public override string ToString() => $" Name:{Name} Age:{Age}";
  19. }
复制代码

定义json设置 文件:myconfig.json

  1. {
  2. "UserOption": {
  3. "Name": "ConfigName-zhangsan",
  4. "Age": 666
  5. }
  6. }
复制代码

创建ServiceCollection

  1. services = new ServiceCollection();
  2. var configBuilder = new ConfigurationBuilder().AddInMemoryCollection().AddJsonFile("myconfig.json", true, true);
  3. var iconfiguration = configBuilder.Build();
  4. services.AddSingleton<IConfiguration>(iconfiguration);
复制代码

示例代码

  1. services.Configure<UserOptions>(x => { x.Name = "张三"; x.Age = new Random().Next(1, 10000); });
  2. services.AddOptions<UserOptions2>().Configure<IConfiguration>((x, config) => { x.Name = config["UserOption:Name"]; x.Age = 100; }); ;
  3. services.PostConfigure<UserOptions>(x => { x.Name = x.Name + "Post"; x.Age = x.Age; });
  4. services.Configure<UserOptions>("default", x => { x.Name = "Default-张三"; x.Age = new Random().Next(1, 10000); });
  5. services.Configure<UserOptions>("config", configuration.GetSection("UserOption"));
  6. using (var provider = services.BuildServiceProvider())
  7. {
  8. using (var scope1 = provider.CreateScope())
  9. {
  10. PrintOptions(scope1, "Scope1");
  11. }
  12. //修改配置文件
  13. Console.WriteLine(string.Empty);
  14. Console.WriteLine("修改配置文件");
  15. var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "myconfig.json");
  16. File.WriteAllText(filePath, "{"UserOption": { "Name": "ConfigName-lisi", "Age": 777}}");
  17. //配置文件的change回调事件需要一定时间执行
  18. Thread.Sleep(300);
  19. Console.WriteLine(string.Empty);
  20. using (var scope2 = provider.CreateScope())
  21. {
  22. PrintOptions(scope2, "Scope2");
  23. }
  24. Console.WriteLine(string.Empty);
  25. using (var scope3 = provider.CreateScope())
  26. {
  27. PrintOptions(scope3, "Scope3");
  28. }
  29. }
  30. static void PrintOptions(IServiceScope scope, string scopeName)
  31. {
  32. var options1 = scope.ServiceProvider.GetService<IOptions<UserOptions>>();
  33. Console.WriteLine($"手动注入读取,IOptions,{scopeName}-----{ options1.Value}");
  34. var options2 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>();
  35. Console.WriteLine($"配置文件读取,IOptionsSnapshot,{scopeName}-----{ options2.Value}");
  36. var options3 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>();
  37. Console.WriteLine($"配置文件根据名称读取,IOptionsSnapshot,{scopeName}-----{ options3.Get("config")}");
  38. var options4 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>();
  39. Console.WriteLine($"配置文件读取,IOptionsMonitor,{scopeName}-----{ options4.CurrentValue}");
  40. var options5 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>();
  41. Console.WriteLine($"配置文件根据名称读取,IOptionsMonitor,{scopeName}-----{options5.Get("config")}");
  42. var options6 = scope.ServiceProvider.GetService<IOptions<UserOptions2>>();
  43. Console.WriteLine($"Options2-----{options6.Value}");
  44. }
复制代码

代码运行效果

  1. Create UserOptions Instance:01
  2. 手动注入读取,IOptions,Scope1----- Name:张三Post Age:6575 Instance:01
  3. Create UserOptions Instance:02
  4. 配置文件读取,IOptionsSnapshot,Scope1----- Name:张三Post Age:835 Instance:02
  5. Create UserOptions Instance:03
  6. 配置文件根据名称读取,IOptionsSnapshot,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:03
  7. Create UserOptions Instance:04
  8. 配置文件读取,IOptionsMonitor,Scope1----- Name:张三Post Age:1669 Instance:04
  9. Create UserOptions Instance:05
  10. 配置文件根据名称读取,IOptionsMonitor,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:05
  11. Options2----- Name:ConfigName-zhangsan Age:100
  12. 修改配置文件
  13. Create UserOptions Instance:06
  14. 手动注入读取,IOptions,Scope2----- Name:张三Post Age:6575 Instance:01
  15. Create UserOptions Instance:07
  16. 配置文件读取,IOptionsSnapshot,Scope2----- Name:张三Post Age:5460 Instance:07
  17. Create UserOptions Instance:08
  18. 配置文件根据名称读取,IOptionsSnapshot,Scope2----- Name:ConfigName-lisi Age:777 Instance:08
  19. 配置文件读取,IOptionsMonitor,Scope2----- Name:张三Post Age:1669 Instance:04
  20. 配置文件根据名称读取,IOptionsMonitor,Scope2----- Name:ConfigName-lisi Age:777 Instance:06
  21. Options2----- Name:ConfigName-zhangsan Age:100
  22. 手动注入读取,IOptions,Scope3----- Name:张三Post Age:6575 Instance:01
  23. Create UserOptions Instance:09
  24. 配置文件读取,IOptionsSnapshot,Scope3----- Name:张三Post Age:5038 Instance:09
  25. Create UserOptions Instance:10
  26. 配置文件根据名称读取,IOptionsSnapshot,Scope3----- Name:ConfigName-lisi Age:777 Instance:10
  27. 配置文件读取,IOptionsMonitor,Scope3----- Name:张三Post Age:1669 Instance:04
  28. 配置文件根据名称读取,IOptionsMonitor,Scope3----- Name:ConfigName-lisi Age:777 Instance:06
  29. 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注入

  1. public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
  2. {
  3. return services.Configure(Microsoft.Extensions.Options.Options.DefaultName, configureOptions);
  4. }
  5. public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) where TOptions : class
  6. {
  7. services.AddOptions();
  8. services.AddSingleton((IConfigureOptions<TOptions>)new ConfigureNamedOptions<TOptions>(name, configureOptions));
  9. return services;
  10. }
  11. public static IServiceCollection AddOptions(this IServiceCollection services)
  12. {
  13. services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
  14. services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
  15. services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
  16. services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
  17. services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
  18. return services;
  19. }
复制代码

通过上面的源码可以发现,Options相干 类是在AddOptions中注入的,详细 的设置 项在Configure中注入。

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

那么我们详细 的设置 项存到那里 去了呢,在ConfigureNamedOptions这个类中,在Configer函数调用时,只是把相干 的设置 委托存了起来:

  1. public ConfigureNamedOptions(string name, Action<TOptions> action)
  2. {
  3. Name = name;
  4. Action = action;
  5. }
复制代码

OptionsManager

  1. private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);
  2. public TOptions Value => Get(Options.DefaultName);
  3. public virtual TOptions Get(string name)
  4. {
  5. name = name ?? Options.DefaultName;
  6. return _cache.GetOrAdd(name, () => _factory.Create(name));
  7. }
复制代码

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

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

OptionsFactory

那么OptionsFactory又是怎样 创建Options的呢?我们看一下他的构造函数,构造函数将全部 Configure和PostConfigure的初始化委托都通过构造函数保存在内部变量中

  1. public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
  2. {
  3. _setups = setups;
  4. _postConfigures = postConfigures;
  5. }
复制代码

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

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

NamedConfigureFromConfigurationOptions

IConfiguration设置 Options的方式略有不同

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

  1. public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class
  2. {
  3. services.AddOptions();
  4. services.AddSingleton((IOptionsChangeTokenSource<TOptions>)new ConfigurationChangeTokenSource<TOptions>(name, config));
  5. return services.AddSingleton((IConfigureOptions<TOptions>)new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
  6. }
复制代码

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

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

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

  1. public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
  2. : base(name, (Action<TOptions>)delegate(TOptions options)
  3. {
  4. config.Bind(options, configureBinder);
  5. })
复制代码

以是 在Factory的Create函数中,会调用IConfiguration的Bind函数

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

ValidateOptions

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

在OptionsFactory构建Options的时间 会验证Options的有效 性,验证失败会抛出OptionsValidationException非常

对于ValidateOptions和PostConfigureOptions都是构建Options实例时必要 用到的紧张 模块,不过使用 和内部实现都较为简单,应用场景也不是很多,本文就不对这两个类多做先容 了

结论

在Configure扩展函数中会起首 调用AddOptions函数

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

Configure设置 的选项设置 委托终极 会保存到ConfigureNamedOptions或NamedConfigureFromConfigurationOptions

IOptions和IOptionsSnapshot的实现类为OptionsManager

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

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

如今 只剩下末了 一个题目 了,OptionsMonitor是怎样 动态更新选项的呢?

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

  1. //构造函数
  2. public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
  3. {
  4. _factory = factory;
  5. _sources = sources;
  6. _cache = cache;
  7. //循环属于TOptions的所有IChangeToken
  8. foreach (IOptionsChangeTokenSource<TOptions> source in _sources)
  9. {
  10. ChangeToken.OnChange(() => source.GetChangeToken(), delegate(string name)
  11. {
  12. //清除缓存
  13. name = name ?? Options.DefaultName;
  14. _cache.TryRemove(name);
  15. }, source.Name);
  16. }
  17. }
  18. public virtual TOptions Get(string name)
  19. {
  20. name = name ?? Options.DefaultName;
  21. return _cache.GetOrAdd(name, () => _factory.Create(name));
  22. }
复制代码

果然是如许 的吧!

到此这篇关于.Net Core 中选项Options的详细 实现的文章就先容 到这了,更多相干 .Net Core Options内容请搜索 脚本之家从前 的文章或继续欣赏 下面的相干 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 千山落月坝 | 2021-9-15 11:00:21 | 显示全部楼层
admin楼主会死的很有节奏的!
回复

使用道具 举报

avatar 凡尘莲花1 | 2021-9-15 20:20:30 | 显示全部楼层
不是惊喜,是惊吓!
回复

使用道具 举报

avatar 人生如梦总人q | 2021-9-16 02:54:24 | 显示全部楼层
最近压力山大啊!
回复

使用道具 举报

avatar 123457748 | 2021-9-16 14:16:20 | 显示全部楼层
世界末日我都挺过去了,看到admin楼主我才知道为什么上帝留我到现在!
回复

使用道具 举报

avatar 今生有你2017 | 2021-9-16 15:16:24 | 显示全部楼层
admin楼主是在找骂么?
回复

使用道具 举报

avatar Abraham30 | 2021-9-17 14:20:47 | 显示全部楼层
投admin楼主一票,不用谢哦!
回复

使用道具 举报

avatar 紫罗兰的叶栏 | 2021-9-18 23:16:06 | 显示全部楼层
楼上长在线啊?
回复

使用道具 举报

avatar 散粉的火把煌 | 2021-9-22 07:34:19 | 显示全部楼层
收藏了,改天让朋友看看!
回复

使用道具 举报

avatar 爸证欢 | 2021-9-23 15:20:43 | 显示全部楼层
楼上的心情不错啊!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则