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

.Net Core 中选项Options的具体实现_实用技巧_

2023-05-24 400人已围观

简介 .Net Core 中选项Options的具体实现_实用技巧_

.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); 

示例代码

 services.Configure(x => { x.Name = "张三"; x.Age = new Random().Next(1, 10000); }); services.AddOptions().Configure((x, config) => { x.Name = config["UserOption:Name"]; x.Age = 100; }); ; services.PostConfigure(x => { x.Name = x.Name + "Post"; x.Age = x.Age; }); services.Configure("default", x => { x.Name = "Default-张三"; x.Age = new Random().Next(1, 10000); }); services.Configure("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>(); Console.WriteLine($"手动注入读取,IOptions,{scopeName}-----{ options1.Value}"); var options2 = scope.ServiceProvider.GetService>(); Console.WriteLine($"配置文件读取,IOptionsSnapshot,{scopeName}-----{ options2.Value}"); var options3 = scope.ServiceProvider.GetService>(); Console.WriteLine($"配置文件根据名称读取,IOptionsSnapshot,{scopeName}-----{ options3.Get("config")}"); var options4 = scope.ServiceProvider.GetService>(); Console.WriteLine($"配置文件读取,IOptionsMonitor,{scopeName}-----{ options4.CurrentValue}"); var options5 = scope.ServiceProvider.GetService>(); Console.WriteLine($"配置文件根据名称读取,IOptionsMonitor,{scopeName}-----{options5.Get("config")}"); var options6 = scope.ServiceProvider.GetService>(); 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(this IServiceCollection services, Action configureOptions) where TOptions : class { return services.Configure(Microsoft.Extensions.Options.Options.DefaultName, configureOptions); } public static IServiceCollection Configure(this IServiceCollection services, string name, Action configureOptions) where TOptions : class { services.AddOptions(); services.AddSingleton((IConfigureOptions)new ConfigureNamedOptions(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 action) { Name = name; Action = action; } 

OptionsManager

 private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(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创建一个,否则就读缓存中的选项。

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

OptionsFactory

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

 public OptionsFactory(IEnumerable> setups, IEnumerable> postConfigures) { _setups = setups; _postConfigures = postConfigures; } 

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

 public TOptions Create(string name) { //首先创建对应Options的实例 TOptions val 
                
                

-六神源码网