第二部分主要记录Options 模型
OptionsConfigurationServiceCollectionExtensions类提供了对Options 模型与配置系统的Configure方法的扩展
在Starup ConfigService中经常会看到把一个拉姆达注册成配置项例如:.Configure<Profile>(it ->it.age = 18),我们称这个拉姆达为Configure Action,其实这是使用了一个包装类,包装你的Configure Action委托,并把这个类的实例注册到Service容器中。它实现IOptions与拉姆达如何映射的,这一切由OptionsServiceCollectionExtensions、OptionsFacotry等实现的。你也可以直接像下面这样使用
var profile = new Servicecollection ().Addoptions().Configure<Profile>(it ->it.age = 18).BuildServiceProvider().GetRequiredService<IOptions<Profile>>().Value;OptionsServiceCollectionExtensions 作为配置服务的扩展类下面有三种类型的扩展方法分别是Configure、PostConfigure、AddOptions,前两所对应的服务为IConfigureOptions与IPostConfigureOptions 区别仅仅是为了实现配置Configure Action的执行时机,IPostConfigureOptions会后执行,而AddOptions本质上还是注册前两种,注册成面上看起来AddOptions注册的Configure Action具有了参数可以访问其它DI内服务。
注意:就算你使用了三个注册方式注册一次或多次对同一个TOptions进行注册,他们其实是操作的同一个TOptions给你。这体现在OptionsFactory.Create上,也是我们想要的效果。
Configure、PostConfigure、扩展方法注册的 Configure Action会由IConfigureOptions与IPostConfigureOptions 接口对应的包装类进行包装。属性均是Action,Configure Action的执行是在Configure方法中。IConfigureOptions
值得注意的是ConfigureAll如果你此方法去注入一个Name 为null 的 Configure Action包装类逻辑体现在ConfigureNamedOptions.Configure/PostConfigureOptions.Configure方法上。,官方说法:“Configure ALL options instances, both named and default”翻译后扩展方法将配置应用于所有选项,包括命名实例和默认实例。
Configure -> ConfigureNamedOptions
PostConfigure -> PostConfigureOptions
public static class OptionsServiceCollectionExtensions{ ... 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 PostConfigure<TOptions>(this IServiceCollection services!!, string? name, Action<TOptions> configureOptions!!) where TOptions : class { services.AddOptions(); services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(name, configureOptions)); return services; } ...}public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions> where TOptions : class{ public ConfigureNamedOptions(string? name, Action<TOptions>? action) { Name = name; Action = action; } public virtual void Configure(string? name, TOptions options!!) { // Null name is used to configure all named options.// Name的过滤以及Configure 逻辑就是在这里体现的 if (Name == null || name == Name) { Action?.Invoke(options); } }}public class PostConfigureOptions<TOptions> : IPostConfigureOptions<TOptions> where TOptions : class{ public PostConfigureOptions(string? name, Action<TOptions>? action) { Name = name; Action = action; } public virtual void PostConfigure(string? name, TOptions options!!) { if (Name == null || name == Name) { Action?.Invoke(options); } }}AddOptions:此方法会帮你构建一个OptionsBuilder,并非向Service容器注入,而是利用其builder类的Configure方法向Service容器具体注入。其下面的大量重载 Configure方法会帮你创建基于IConfigureNamedOptions/IPostConfigureOptions不同数量的泛型类. 其目的就是为了解决在“ Configure Action”中使用其它服务。
设计思路很好可以参考整体思路大概是,先用AddOptions
扩展方法创建了一个OptionsBuilder 对象,然后调用它重载方法Configure<TService...>去创建具有多个泛型的ConfigureNamedOptions 对象。ConfigureNamedOptions的Configure在执行Action委托时会用serviceProvider获取到TService泛型服务。作为参数传入Action委托。这样委托在真正被执行时就会拿到对应的服务。
public static class OptionsServiceCollectionExtensions{ ... public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services!!, string? name) where TOptions : class { services.AddOptions(); return new OptionsBuilder<TOptions>(services, name); } ...}public class OptionsBuilder<TOptions> where TOptions : class{ ... public virtual OptionsBuilder<TOptions> Configure<TDep>(Action<TOptions, TDep> configureOptions!!) where TDep : class { Services.AddTransient<IConfigureOptions<TOptions>>(sp => new ConfigureNamedOptions<TOptions, TDep>(Name, sp.GetRequiredService<TDep>(), configureOptions)); return this; } ...}public class ConfigureNamedOptions<TOptions, TDep> : IConfigureNamedOptions<TOptions> { ... public ConfigureNamedOptions(string? name, TDep dependency, Action<TOptions, TDep>? action) { Name = name; Action = action; Dependency = dependency; } public virtual void Configure(string? name, TOptions options!!) { // Null name is used to configure all named options. if (Name == null || name == Name) { Action?.Invoke(options, Dependency); } } ...}| 注入到服务的扩展方法(OptionsServiceCollectionExtensions) | 服务类 | 服务实现类 | 使用 | 生命周期 | 备注 |
|---|---|---|---|---|---|
| Configure | IConfigureOptions | ConfigureNamedOptions | 被OptonsFactory使用 | Singleton | ConfigureAll,在IPostConfigureOptions前执行Configure Action |
| PostConfigure | IPostConfigureOptions | PostConfigureOptions | 被OptonsFactory使用 | Singleton | ConfigureAll 在IConfigureOptions后执行Configure Action |
| AddOptions | IConfigureOptions/IPostConfigureOptions | ConfigureNamedOptions/PostConfigureOptions | 被OptonsFactory使用 | Singleton | 辅助注入一个可以访问其它服务的Configure Action |
首先在固有想法上注入的服务直接会拿来使用。而在这里注入的均为IConfigureOptions/IPostConfigureOptions服务我们管他们叫Configure Action的包装类,而要使用这些服务是通过IOptions/IOptionsSnapshot/IOptionsMonitor去获得。我们称这三个服务为OptionsManger类。
这里整理出来IOptions/IOptionsSnapshot/IOptionsMonitor三种OptionsManger的区别。
服务类|服务实现类|使用|生命周期|备注|
---|:--??:--??:--??:--??--:
IOptions|UnnamedOptionsManager
IOptionsSnapshot|OptionsManager
IOptionsMonitor|OptionsMonitor
var source = new Dictionary<string, string>{ {"TestOptions:Key1" ,"TestOptions key1"},};var config = new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = source }).Build();ServiceCollection services = new ServiceCollection();services.AddOptions();services.Configure<TestOptions>(config.GetSection("TestOptions")); // Import the "Microsoft.Extensions.Options.ConfigurationExtensions" package.var serviceProvider = services.BuildServiceProvider();IOptions<TestOptions> options = serviceProvider.GetService<IOptions<TestOptions>>();Console.WriteLine(options.Value.Key1);Console.ReadLine();public class TestOptions{ public string Key1 { get; set; }}以第一个为例:UnnamedOptionsManager 的value属性的get 访问器其内部直接调用OptionsFactory
internal sealed class UnnamedOptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> : IOptions<TOptions>where TOptions : class { private volatile TOptions? _value; public UnnamedOptionsManager(IOptionsFactory<TOptions> factory) => _factory = factory; public TOptions Value { get { if (_value is TOptions value) { return value; } return _value ??= _factory.Create(Options.DefaultName); } } }public class OptionsFactory<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> : IOptionsFactory<TOptions> where TOptions : class{ ... public TOptions Create(string name) { // 创建Toptions 实例对象 TOptions options = CreateInstance(name); // 依次调用注册的 Configure Action // 先执行IConfigureOptions包装的Configure Action. foreach (IConfigureOptions<TOptions> setup in _setups) { if (setup is IConfigureNamedOptions<TOptions> namedSetup) { namedSetup.Configure(name, options); } else if (name == Options.DefaultName) { setup.Configure(options); } } // 在执行 IPostConfigureOptions包装的Configure Action. foreach (IPostConfigureOptions<TOptions> post in _postConfigures) { post.PostConfigure(name, options); } // 执行验证逻辑 if (_validations.Length > 0) { var failures = new List<string>(); foreach (IValidateOptions<TOptions> validate in _validations) { ValidateOptionsResult result = validate.Validate(name, options); if (result is not null && result.Failed) { failures.AddRange(result.Failures); } } if (failures.Count > 0) { throw new OptionsValidationException(name, typeof(TOptions), failures); } } return options; } ...}因实现逻辑基本与IOptons相同这里就不多做记录。
var source = new Dictionary<string, string>{ {"TestOptions:Key1" ,"TestOptions key1"},};var config = new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = source }).Build();ServiceCollection services = new ServiceCollection();services.AddOptions();services.Configure<TestOptions>("TestOptions", config.GetSection("TestOptions")); // Import the "Microsoft.Extensions.Options.ConfigurationExtensions" package.var serviceProvider = services.BuildServiceProvider();IOptionsSnapshot<TestOptions> optionsAccessor = serviceProvider.GetRequiredService<IOptionsSnapshot<TestOptions>>();Console.WriteLine(optionsAccessor.Get("TestOptions").Key1);Console.ReadLine();public class TestOptions{ public string Key1 { get; set; }}源码在part3 单独分析。
var configuration = new ConfigurationBuilder().AddJsonFile(path: "profile.json", optional: false, reloadOnChange: true).Build();new ServiceCollection().AddOptions().Configure<Profile>(configuration).BuildServiceProvider().GetRequiredService<IOptionsMonitor<Profile>>().OnChange(profile => Console.WriteLine($"changed: {profile.Age}"));Console.Read();public class Profile{ public int Age { get; set; }}如下两个demo分别演示了"Options模型"与“配置系统”结合的结合使用。
Demo1
var configuration = new ConfigurationBuilder ().AddJsonFile ("profile.json").Build ();var profile = new ServiceCollection().AddOptions().Configure<Profile>(configuration).BuildServiceProvider().GetRequiredService<IOptions<Profile>>().Value; Demo2
var source = new Dictionary<string, string>{ {"TestOptions:Key1" ,"TestOptions key1"}, {"TestOptions:Key2" ,"TestOptions key2"}, {"UserInfo:key1" ,"UserInfo"},};var config = new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = source }).Build();ServiceCollection services = new ServiceCollection();services.AddOptions();services.Configure<TestOpetion>(config.GetSection("TestOptions")); // Import the "Microsoft.Extensions.Options.ConfigurationExtensions" package.var serviceProvider = services.BuildServiceProvider();var options = serviceProvider.GetRequiredService<IOptions<TestOpetion>>();Console.WriteLine(options.Value.Key1);Console.ReadLine();public class TestOpetion{ public string Key1{ get; set; } public string Key2 { get; set; }}以上操作步骤为OptionsConfigurationServiceCollectionExtensions类定义了对 Configure的扩展,有三个参数string name、config(IConfiguration),configureBinder的委托,第一个参数是TOptions的name, 第二个表示配置系统的IConfiguration,第三个configureBinder 是配置系统在映射Toptions时候的一些配置
原理很简单,因为有了ServiceCollection的支持,那么就往里面帮我们注入一个类型为IConfigureOptions
关于configureBinder基本逻辑基本是根据TOptions的Type 对象反射出信息,然后第二个参数config(配置系统提供数据的接口)拿数据,在把对应的数据绑定在TOptions 对象上。
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services!!, string? name, IConfiguration config!!, Action<BinderOptions>? configureBinder) where TOptions : class{ services.AddOptions(); // 用于支持**“配置系统”**与 **"Options模型"**结合后当配置系统发生更新时回调options时注册的回调函数。后面会说到 services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config)); // 注册NamedConfigureFromConfigurationOptions return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));} /// Configures an option instance by using <see cref="ConfigurationBinder.Bind(IConfiguration, object)"/> against an <see cref="IConfiguration"/>. public class NamedConfigureFromConfigurationOptions<TOptions> : ConfigureNamedOptions<TOptions> where TOptions : class { public NamedConfigureFromConfigurationOptions(string? name, IConfiguration config!!, Action<BinderOptions>? configureBinder) : base(name, options => BindFromOptions(options, config, configureBinder)){ } private static void BindFromOptions(TOptions options, IConfiguration config, Action<BinderOptions>? configureBinder) => config.Bind(options, configureBinder); } public class BinderOptions { // true 会对TOptons的私有属性也赋值 public bool BindNonPublicProperties { get; set; } public bool ErrorOnUnknownConfiguration { get; set; } }Options 扩展方法注册Microsoft.Extensions.Options向Service容器注入认证服务,其原理是OptionsFactory.Create拿到所有注入的服务。将TOptons作为参数传入实例的验证方法。
services.AddOptions<DateTimeFormatOptions>().Configure(options =>options. DatePattern = datePattern;options.TimePattern = timePattern;).Validate(options => Validate (options.DatePattern) && Validate(options. TimePattern), "Invalid Date or Time pattern.");OptionsServiceCollectionExtensions Options 模型依赖的服务
public static IServiceCollection AddOptions(this IServiceCollection services){ services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>))); 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<>)));}文章中提到的代码,请在source.dot.net快速搜索预览
本文来自博客园,作者:一身大膘,转载请注明原文链接:https://www.cnblogs.com/hts92/p/15927329.html
如果该篇文章对您有帮助的话,可以点一下右下角的【推荐】