翼度科技»论坛 编程开发 .net 查看内容

Options选项

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
选项用来提供对相关设置的强类型访问,读取配置首选使用选项模式。选项无法脱离容器使用,依赖容器,实现了选项不同的访问方式。选项模式使用了泛型包装器,因此具备了如下优点:

  • 不需要显示注册选项具体类型,只需要将泛型包装器注入到容器中;
  • 对于选项实例的评估推迟到获取IOptions.Value时进行,而不是在注入时进行,这样就可以获取不同生命周期的选项;
  • 可以对选项进行泛型约束;
选项注入

选项模式向容器中注入了三种类型的选项泛型包装器:IOptions,IOptionsSnapshot,IOptionsMonitor。其中IOptionsSnapshot被注册为Scoped。注入了IOptionsFactory泛型选项工厂,用来创建选项实例。注入了IOptionsMonitorCache,由IOptionsMonitor用于缓存泛型实例。
  1. public static IServiceCollection AddOptions(this IServiceCollection services)
  2. {
  3.     if (services == null)
  4.         throw new ArgumentNullException(nameof(services));
  5.     services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
  6.     services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
  7.     services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
  8.     services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
  9.     services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
  10.     return services;
  11. }
复制代码
选项不同生命周期

不同类型的选项接口功能不同:

  • IOptions:实现类为UnnamedOptionsManager,其中TOptions字段保存选项实例(不支持命名选项)。在首次获取Value时,会调用工厂创建选项实例。因为被注册为单例,因此无法识别配置修改。
  • IOptionsSnapshot:实现类为OptionsManager,其中OptionsCache字段(私有,非容器获取)保存选项实例(支持命名选项)。被注册为范围,在范围内首次获取Value时,会调用工厂创建选项实例,并将其保存到私有的OptionsCache中,在范围内选项值不变,不同范围内选项值根据获取时配置文件的不同而不同。
  • IOptionsMonitor:实现类为OptionsMonitor,其中IOptionsMonitorCache字段,该字段的值是从容器中解析的,用来缓存选项实例。OptionsMonitor还注入了IOptionsChangeTokenSource列表,可以监听配置源的修改。当监听到修改时,调用工厂重新创建选项以刷新选项值。
  1. internal sealed class UnnamedOptionsManager<[DynamicallyAccessedMembers(
  2.     Options.DynamicallyAccessedMembers)] TOptions> : IOptions<TOptions>
  3.     where TOptions : class
  4. {
  5.     private readonly IOptionsFactory<TOptions> _factory;
  6.     private volatile object _syncObj;
  7.     private volatile TOptions _value;
  8.     public UnnamedOptionsManager(IOptionsFactory<TOptions> factory) => _factory = factory;
  9.     public TOptions Value
  10.     {
  11.         get
  12.         {
  13.             if (_value is TOptions value)
  14.                 return value;
  15.             lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
  16.             {
  17.                 return _value ??= _factory.Create(Options.DefaultName);
  18.             }
  19.         }
  20.     }
  21. }
复制代码
  1. public class OptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions>
  2.     : IOptions<TOptions>, IOptionsSnapshot<TOptions>
  3.     where TOptions : class
  4. {
  5.     private readonly IOptionsFactory<TOptions> _factory;
  6.     private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();
  7.         
  8.     public OptionsManager(IOptionsFactory<TOptions> factory)
  9.     {
  10.         _factory = factory;
  11.     }
  12.     public TOptions Value => Get(Options.DefaultName);
  13.     public virtual TOptions Get(string name)
  14.     {
  15.         name = name ?? Options.DefaultName;
  16.         if (!_cache.TryGetValue(name, out TOptions options))
  17.         {
  18.             IOptionsFactory<TOptions> localFactory = _factory;
  19.             string localName = name;
  20.             options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
  21.         }
  22.         return options;
  23.     }
  24. }
复制代码
  1. public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions>
  2.     : IOptionsMonitor<TOptions>, IDisposable
  3.     where TOptions : class
  4. {
  5.     private readonly IOptionsMonitorCache<TOptions> _cache;
  6.     private readonly IOptionsFactory<TOptions> _factory;
  7.     private readonly List<IDisposable> _registrations = new List<IDisposable>();
  8.     internal event Action<TOptions, string> _onChange;
  9.     public OptionsMonitor(IOptionsFactory<TOptions> factory,
  10.         IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
  11.     {
  12.         _factory = factory;
  13.         _cache = cache;
  14.         void RegisterSource(IOptionsChangeTokenSource<TOptions> source)
  15.         {
  16.             IDisposable registration = ChangeToken.OnChange(
  17.                       () => source.GetChangeToken(),
  18.                       (name) => InvokeChanged(name),
  19.                       source.Name);
  20.             _registrations.Add(registration);
  21.         }
  22.         
  23.         // 此处简写
  24.         foreach (IOptionsChangeTokenSource<TOptions> source in sources)
  25.         {
  26.             RegisterSource(source);
  27.         }
  28.     }
  29.     private void InvokeChanged(string name)
  30.     {
  31.         name = name ?? Options.DefaultName;
  32.         _cache.TryRemove(name);
  33.         TOptions options = Get(name);
  34.         if (_onChange != null)
  35.             _onChange.Invoke(options, name);
  36.     }
  37.     public TOptions CurrentValue
  38.     {
  39.         get => Get(Options.DefaultName);
  40.     }
  41.     public virtual TOptions Get(string name)
  42.     {
  43.         name = name ?? Options.DefaultName;
  44.         return _cache.GetOrAdd(name, () => _factory.Create(name));
  45.     }
  46.     public IDisposable OnChange(Action<TOptions, string> listener)
  47.     {
  48.         var disposable = new ChangeTrackerDisposable(this, listener);
  49.         _onChange += disposable.OnChange;
  50.         return disposable;
  51.     }
  52.     public void Dispose()
  53.     {
  54.         foreach (IDisposable registration in _registrations)
  55.         {
  56.             registration.Dispose();
  57.         }
  58.         _registrations.Clear();
  59.     }
  60. }
复制代码
选项配置

选项模式提供了IConfigureOptions、IConfigureNamedOptions和IPostConfigureOptions接口,用来对选项进行配置。IConfigureNamedOptions继承了IConfigureOptions接口,增加了命名选项配置功能。这三个接口中都有一个对选项配置的方法,将接口注入到容器中,当调用工厂创建选项时,会调用接口中的配置方法对选项进行配置。首先会调用IConfigureOptions、IConfigureNamedOptions接口中的配置方法,然后调用IPostConfigureOptions接口中的配置方法。
  1. // OptionsFactory<>
  2. public TOptions Create(string name)
  3. {
  4.     TOptions options = CreateInstance(name);
  5.     foreach (IConfigureOptions<TOptions> setup in _setups)
  6.     {
  7.         if (setup is IConfigureNamedOptions<TOptions> namedSetup)
  8.             namedSetup.Configure(name, options);
  9.         else if (name == Options.DefaultName)
  10.             setup.Configure(options);
  11.     }
  12.     foreach (IPostConfigureOptions<TOptions> post in _postConfigures)
  13.     {
  14.         post.PostConfigure(name, options);
  15.     }
  16.     // 选项验证...
  17.     return options;
  18. }
  19. protected virtual TOptions CreateInstance(string name)
  20. {
  21.     return Activator.CreateInstance<TOptions>();
  22. }
复制代码
选项验证

选项模式提供了IValidateOptions接口,包含一个Validate方法对选项进行验证。将接口注入容器中,当调用工厂创建选项时,会调用接口中的Validate方法对选项进行验证。
  1. // OptionsFactory<>
  2. public TOptions Create(string name)
  3. {
  4.     TOptions options = CreateInstance(name);
  5.     // 选项配置...
  6.     if (_validations.Length > 0)
  7.     {
  8.         var failures = new List<string>();
  9.         foreach (IValidateOptions<TOptions> validate in _validations)
  10.         {
  11.             ValidateOptionsResult result = validate.Validate(name, options);
  12.             if (result is not null && result.Failed)
  13.                 failures.AddRange(result.Failures);
  14.         }
  15.         if (failures.Count > 0)
  16.             throw new OptionsValidationException(name, typeof(TOptions), failures);
  17.     }
  18.     return options;
  19. }
复制代码
如果需要为选项添加验证,实现IValidateOptions接口并注入到容器即可:
  1. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton
  2.     <IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());
复制代码
也可以添加DataAnnotations验证,调用ValidateDataAnnotations扩展方法即可,该方法定义在 Microsoft.Extensions.Options.DataAnnotations中。需要先调用AddOptions扩展方法创建OptionsBuilder:
  1. builder.Services.AddOptions<SettingsOptions>()
  2.     .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
  3.     .ValidateDataAnnotations();
复制代码
绑定配置

通过如下扩展方法将选项绑定到配置:
  1. builder.Services.Configure<SettingsOptions>(builder.Configuration.GetSection("Settings"));
复制代码
绑定到配置是通过IConfiguration.Bind()扩展方法实现的,同时,也添加了对配置修改的监听:
  1. public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class
  2. {
  3.     if (services == null)
  4.         throw new ArgumentNullException(nameof(services));
  5.     if (config == null)
  6.         throw new ArgumentNullException(nameof(config));
  7.     services.AddOptions();
  8.     services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(
  9.         new ConfigurationChangeTokenSource<TOptions>(name, config));
  10.     return services.AddSingleton<IConfigureOptions<TOptions>>(
  11.         new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
  12. }
复制代码
来源:https://www.cnblogs.com/louzixl/archive/2023/12/03/17872541.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具