背景ASP.NET Core引入了Options模式,使用類來(lái)表示相關(guān)的設(shè)置組。簡(jiǎn)單的來(lái)說(shuō),就是用強(qiáng)類型的類來(lái)表達(dá)配置項(xiàng),這帶來(lái)了很多好處。 示例我們先從一小段代碼著手(TestOptions類只有一個(gè)字符串屬性Name,代碼略): 1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var builder = new ConfigurationBuilder(); 6 builder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); //注意最后一個(gè)參數(shù)值,true表示配置文件更改時(shí)會(huì)重新加載。 7 var configuration = builder.Build(); 8 var services = new ServiceCollection(); 9 services.AddOptions(); 10 services.Configure<TestOptions>(configuration); //這里通過(guò)配置文件綁定TestOptions 11 var provider = services.BuildServiceProvider(); 12 Console.WriteLine("修改前:"); 13 Print(provider); 14 15 Change(provider); //使用代碼修改Options值。 16 Console.WriteLine("使用代碼修改后:"); 17 Print(provider); 18 19 Console.WriteLine("請(qǐng)修改配置文件。"); 20 Console.ReadLine(); //等待手動(dòng)修改appsettings.json配置文件。 21 Console.WriteLine("修改appsettings.json文件后:"); 22 Print(provider); 23 } 24 25 static void Print(IServiceProvider provider) 26 { 27 using(var scope = provider.CreateScope()) 28 { 29 var sp = scope.ServiceProvider; 30 var options1 = sp.GetRequiredService<IOptions<TestOptions>>(); 31 var options2 = sp.GetRequiredService<IOptionsMonitor<TestOptions>>(); 32 var options3 = sp.GetRequiredService<IOptionsSnapshot<TestOptions>>(); 33 Console.WriteLine("IOptions值: {0}", options1.Value.Name); 34 Console.WriteLine("IOptionsMonitor值: {0}", options2.CurrentValue.Name); 35 Console.WriteLine("IOptionsSnapshot值: {0}", options3.Value.Name); 36 Console.WriteLine(); 37 } 38 } 39 40 static void Change(IServiceProvider provider) 41 { 42 using(var scope = provider.CreateScope()) 43 { 44 var sp = scope.ServiceProvider; 45 sp.GetRequiredService<IOptions<TestOptions>>().Value.Name = "IOptions Test 1"; 46 sp.GetRequiredService<IOptionsMonitor<TestOptions>>().CurrentValue.Name = "IOptionsMonitor Test 1"; 47 sp.GetRequiredService<IOptionsSnapshot<TestOptions>>().Value.Name = "IOptionsSnapshot Test 1"; 48 } 49 } 50 } appsettings.json文件: { "Name": "Test 0" } 上面的代碼,首先從appsettings.json文件讀取配置,然后向容器注冊(cè)依賴配置文件的TestOptions,接著分別打印IOptions<>,IOptionsMonitor<>和IOptionsSnapshot<>的值。 接著通過(guò)代碼來(lái)修改TestOptions的值,打印。 注意,我們僅注冊(cè)了一次TestOptions,卻可以分別通過(guò)IOptions<>,IOptionsMonitor<>和IOptionsSnapshot<>接口來(lái)獲取TestOptions的值。 如果我們把a(bǔ)ppsettings.json文件中Name的值修改為Test 2,那么上面這段代碼的輸出是這樣的: 分析我們可以看到第一次通過(guò)代碼修改IOptions<>和IOptionsMonitor<>的值后,再次打印都被更新了,但是IOptionsSnapshot<>沒(méi)有,為什么呢? var services = new ServiceCollection(); services.AddOptions(); 我們觀察AddOptions方法的實(shí)現(xiàn): 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<>和IOptionsMonitor<>被注冊(cè)為單例服務(wù),而IOptionsSnapshot<>被注冊(cè)為范圍服務(wù)。 我們繼續(xù)看第二次修改,第二次修改配置文件后IOptionsMonitor<>和IOptionsSnapshot<>的值更新了,而IOptions<>的值沒(méi)有更新。 public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache) { _factory = factory; _sources = sources; _cache = cache; foreach (var source in _sources) { var registration = ChangeToken.OnChange( () => source.GetChangeToken(), (name) => InvokeChanged(name), source.Name); _registrations.Add(registration); } } 原來(lái)OptionsMonitor的更新能力是從IOptionsChangeTokenSource<TOptions>而來(lái),但是這個(gè)接口的實(shí)例又是誰(shuí)呢? services.Configure<TestOptions>(configuration); 這是一個(gè)定義在Microsoft.Extensions.Options.ConfigurationExtensions.dll的擴(kuò)展方法,最后實(shí)際調(diào)用的是它的一個(gè)重載方法,代碼如下: 1 public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) 2 where TOptions : class 3 { 4 if (services == null) 5 { 6 throw new ArgumentNullException(nameof(services)); 7 } 8 9 if (config == null) 10 { 11 throw new ArgumentNullException(nameof(config)); 12 } 13 14 services.AddOptions(); 15 services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config)); 16 return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder)); 17 } 秘密就在上面的第15行,ConfigurationChangeTokenSource,它引用了代表配置文件的對(duì)象config,所以配置文件更新,IOptionsMonitor就會(huì)跟著更新。 結(jié)論IOptions<>是單例,因此一旦生成了,除非通過(guò)代碼的方式更改,它的值是不會(huì)更新的。 官方文檔是這樣介紹的:
IOptionsSnapshot<TOptions>在需要對(duì)每個(gè)請(qǐng)求重新計(jì)算選項(xiàng)的場(chǎng)景中非常有用。 所以你應(yīng)該根據(jù)你的實(shí)際使用場(chǎng)景來(lái)選擇到底是用這三者中的哪一個(gè)。 services.Configure<TestOptions>(opt => opt.Name = "Test 0"); IOptions<>最簡(jiǎn)單,也許是一個(gè)不錯(cuò)的選擇,Configure擴(kuò)展方法還有其他重載可以滿足你的更多需求。 |
|