跟我一起学.NetCore之Options实例演示及分析
前言
來啦!來啦!上一節一堆代碼,是不是感覺甚是無味啊?沒關系,這里結合上一節內容專注舉例演示,絕不廢話!走起~~~~~
正文
老規矩,一個WebApi項目走起,項目結構如下:
上一節中提到,Options是基于依賴注入的,所以我們需要將相關服務進行注冊,如下:
注冊完成之后就可以直接用啦,這里新建了一個ComputerController進行測試:
運行走起:
通常在使用的時候,相關服務的配置會放到配置文件中,比如數據庫連接字符串等,所以在注冊的時候將初始化的內容從配置中獲取即可,如下:
跑起來~~~
通過以上的實例演示,可能會好奇為啥不直接用Configuration,我是這樣理解的:通過Options的話,服務不限制使用初始化方式,根據需求選擇,如果使用配置文件取值,服務也和配置沒有直接關系,從而使得服務的使用沒有過多限制;另外服務內部使用也比較便捷,就單純操作Options對象;
哎呀,又來這一套,使用太簡單了,來點真貨唄,此時應該有小伙伴按捺不住了問:Options的相關服務是怎么注冊的,初始化和上一節講的初始化有啥關系??來來來,不急,以下慢慢品~~~
那從哪開始呢??
上一小節說的TOptions對象創建的三大步,創建內部直接New了(忘了的可以回顧一下上一節:跟我一起學.NetCore之選項(Options)核心類型簡介),那這里就從注冊初始化的地方下手:
Services.Configure方法中肯定有事,不然一句代碼咋就讓后面使用如此便捷呢,來,直接看源代碼:
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options;namespace Microsoft.Extensions.DependencyInjection {/// <summary>/// Extension methods for adding configuration related options services to the DI container./// </summary>public static class OptionsConfigurationServiceCollectionExtensions{// 挨著的這三個方法都是IServiceCollection 的擴展方法public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class=>?services.Configure<TOptions>(Options.Options.DefaultName,?config);public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class=>?services.Configure<TOptions>(name,?config,?_?=>?{?});?public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config, Action<BinderOptions> configureBinder)where TOptions : class=>?services.Configure<TOptions>(Options.Options.DefaultName,?config,?configureBinder);// 關鍵方法public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)where TOptions : class{if (services == null){throw new ArgumentNullException(nameof(services));}if (config == null){throw new ArgumentNullException(nameof(config));}//?注冊Options相關服務,這是關鍵方法services.AddOptions();// IOptionsChangeTokenSource 注冊,后續用于通知services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));//?注冊從配置系統中獲取配置值的服務return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));}} }上面代碼中services.AddOptions()是關鍵方法,再翻代碼瞅瞅:
public static class OptionsServiceCollectionExtensions{//?恍然大悟吧,就是這個擴展方法,把服務都注冊好了,所以使用的時候才直接注入即可public static IServiceCollection AddOptions(this IServiceCollection services){if (services == null){throw new ArgumentNullException(nameof(services));}//?使用TryAdd的注入方式,為了對應服務類型只注冊一次//?注冊IOptions<>,實現類是OptionsManager<>services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));// 注冊IOptionsSnapshot<>,實現類是OptionsManager<>services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));// 注冊IOptionsMonitor<>,實現類是OptionsMonitor<>services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));//?注冊IOptionsFactory<>?,實現類是OptionsFactory<>services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));//?注冊IOptionsMonitorCache<>,?實現類是OptionsCache<>services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));return services;}//?對應創建TOption?初始化的兩小步,步驟1Configurepublic static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)where TOptions : class{if (services == null){throw new ArgumentNullException(nameof(services));}if (configureOptions == null){throw new ArgumentNullException(nameof(configureOptions));}services.AddOptions();services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));return services;}//?對應創建TOption?初始化的兩小步,步驟2 PostConfigurepublic static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)where TOptions : class{if (services == null){throw new ArgumentNullException(nameof(services));}if (configureOptions == null){throw new ArgumentNullException(nameof(configureOptions));}services.AddOptions();services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(name, configureOptions));return services;}......省略了其他方法....}看了以上代碼,是不是豁然開朗了,Options的相關服務注冊都是在AddOptions擴展方法中完成的,至于其中的Configure和PostConfigure實則對應著上一節的創建Options初始化的過程;以上注冊的核心類型的生命周期需要了解一下,后面舉例會關聯到:
IOptions<>:Singleton - 單例模式
IOptionsSnapshot<>:Scoped - 作用域范圍模式
IOptionsMonitor<>:?Singleton - 單例模式
Configure是注冊時進行初始化,而PostConfigure一般會用在讀取到配置數據之后對數據進行加工,如下:
接下來說熱更新,也就是配置改變,服務對應的Options能獲取最新的值,代碼稍微優化一下:
第一次訪問,第二次訪問前進行配置文件修改,然后在訪問,運行結果如下:
可以看到,配置數據改變時,IOptionsSnapshot<>和IOptionsMonitor<>都能獲取到最新的值,IOptions<>沒有獲取到,簡單分析一下:
IOptons<>和IOptionsSnapshot<>其實內部是一樣的,只是注入的生命周期不一樣,前者是單例,后者是Scoped,這樣就使得后者每次請求都是不同的IOptionsSnapshot對象,從而就能獲取最新的值;而單例IOptions對象一致不變;那為什么單例的IOptionsMonitor類型能改變呢,那是因為IOptionsMonitor提供了監聽改變的功能,上一節有簡單說明;
如果在配置數據改變時,需要通知Option怎么辦呢?通過IOptionsMonitor<TOptions>的OnChange監聽改變,如下:
運行看結果,首先請求一次,然后修改配置文件,就能看到實時監控了:
最后來說說Options驗證(也就是創建TOptions的第三步),主要是避免不合法的配置,導致程序在運行時業務邏輯出錯才能發現錯誤,從而導致程序迭代周期頻繁,用戶體驗差;驗證有以下三種方式:
直接注冊驗證函數
實現IValidateOptions<TOptions>進行驗證
使用注解(Microsoft.Extensions.Options.DataAnnotations)
這里用三種方式分別依次驗證Name,Cores,MemorySize 三個配置項,如下:
直接注冊驗證函數
實現IValidateOptions<TOptions>進行驗證
使用注解(Microsoft.Extensions.Options.DataAnnotations)
三種驗證方式是相互獨立的,可以單獨使用,也可以組合使用;以上每一種方式對應圖片內容;
運行訪問不合法就報錯,這樣就避免配置不合法,導致業務邏輯不合法,如下
總結
Options(選項)的常規用法暫時就說這么多了,結合上一小節是不是感覺清晰多了~~~下一節說說日志(ILogger)
總結
以上是生活随笔為你收集整理的跟我一起学.NetCore之Options实例演示及分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 听说用 C# 写 TensorFlow
- 下一篇: 大数据下的质量体系建设