Options

OptionsOptions

Options are used to provide strongly typed access to related settings. Options mode is preferred for reading configurations. Options cannot be used without the container and rely on the container to implement different access methods for options. The options pattern uses a generic wrapper, so it has the following advantages:

  • There is no need to show the specific type of the registration option, only the generic wrapper needs to be injected into the container;
  • The evaluation of option instances is deferred until when IOptions.Value is obtained, rather than when injected, so that options with different life cycles can be obtained;
  • Generic constraints can be applied to options;

option injection

Options mode injects three types of option generic wrappers into the container: IOptions, IOptionsSnapshot, IOptionsMonitor. Among them, IOptionsSnapshot is registered as Scoped. Injected IOptionsFactory generic option factory, used to create option instances. Injected IOptionsMonitorCache, used by IOptionsMonitor to cache generic instances.

public static IServiceCollection AddOptions(this IServiceCollection services)
 {
     if (services == null)
         throw new ArgumentNullException(nameof(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)));
     return services;
 }
 

Options with different life cycles

Different types of option interfaces have different functions:

  • IOptions: The implementation class is UnnamedOptionsManager, in which the TOptions field saves option instances (named options are not supported). When the Value is first obtained, the factory is called to create an option instance. Because it is registered as a singleton, configuration modifications cannot be recognized.
  • IOptionsSnapshot: The implementation class is OptionsManager, in which the OptionsCache field (private, not obtained by the container) saves option instances (named options are supported). It is registered as a scope. When the Value is obtained for the first time in the scope, the factory will be called to create an option instance and save it to the private OptionsCache. The option value remains unchanged within the scope. The option value in different scopes depends on the configuration file when obtained. And different.
  • IOptionsMonitor: The implementation class is OptionsMonitor, in which the IOptionsMonitorCache field is parsed from the container and used to cache option instances. OptionsMonitor also injects the IOptionsChangeTokenSource list, which can monitor modifications to the configuration source. When a modification is detected, the factory is called to recreate the option to refresh the option value.
internal sealed class UnnamedOptionsManager : IOptions
     where TOptions : class
 {
     private readonly IOptionsFactory _factory;
     private volatile object _syncObj;
     private volatile TOptions _value;

     public UnnamedOptionsManager(IOptionsFactory factory) => _factory = factory;

     public TOptions Value
     {
         get
         {
             if (_value is TOptions value)
                 return value;

             lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
             {
                 return _value ??= _factory.Create(Options.DefaultName);
             }
         }
     }
 }
 
public class OptionsManager
     : IOptions, IOptionsSnapshot
     where TOptions : class
 {
     private readonly IOptionsFactory _factory;
     private readonly OptionsCache _cache = new OptionsCache();
        
     public OptionsManager(IOptionsFactory factory)
     {
         _factory = factory;
     }

     public TOptions Value => Get(Options.DefaultName);

     public virtual TOptions Get(string name)
     {
         name = name ?? Options.DefaultName;

         if (!_cache.TryGetValue(name, out TOptions options))
         {
             IOptionsFactory localFactory = _factory;
             string localName = name;
             options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
         }

         return options;
     }
 }
 
public class OptionsMonitor
     : IOptionsMonitor, IDisposable
     where TOptions : class
 {
     private readonly IOptionsMonitorCache _cache;
     private readonly IOptionsFactory _factory;
     private readonly List _registrations = new List();
     internal event Action _onChange;

     public OptionsMonitor(IOptionsFactory factory,
         IEnumerable<IOptionsChangeTokenSource> sources, IOptionsMonitorCache cache)
     {
         _factory = factory;
         _cache = cache;

         void RegisterSource(IOptionsChangeTokenSource source)
         {
             IDisposable registration = ChangeToken.OnChange(
                       () => source.GetChangeToken(),
                       (name) => InvokeChanged(name),
                       source.Name);

             _registrations.Add(registration);
         }
        
         //Abbreviation here
         foreach (IOptionsChangeTokenSource source in sources)
         {
             RegisterSource(source);
         }
     }

     private void InvokeChanged(string name)
     {
         name = name ?? Options.DefaultName;
         _cache.TryRemove(name);
         TOptions options = Get(name);
         if (_onChange != null)
             _onChange.Invoke(options, name);
     }

     public TOptions CurrentValue
     {
         get => Get(Options.DefaultName);
     }

     public virtual TOptions Get(string name)
     {
         name = name ?? Options.DefaultName;
         return _cache.GetOrAdd(name, () => _factory.Create(name));
     }

     public IDisposable OnChange(Action listener)
     {
         var disposable = new ChangeTrackerDisposable(this, listener);
         _onChange += disposable.OnChange;
         return disposable;
     }

     public void Dispose()
     {
         foreach (IDisposable registration in _registrations)
         {
             registration.Dispose();
         }

         _registrations.Clear();
     }
 }
 

option configuration

Option mode provides IConfigureOptions, IConfigureNamedOptions and IPostConfigureOptions interfaces for configuring options. IConfigureNamedOptions inherits the IConfigureOptions interface and adds the named option configuration function. Each of these three interfaces has a method for configuring options. The interface is injected into the container. When the factory is called to create options, the configuration method in the interface is called to configure the options. First, the configuration methods in the IConfigureOptions and IConfigureNamedOptions interfaces are called, and then the configuration methods in the IPostConfigureOptions interface are called.

// OptionsFactory
 public TOptions Create(string name)
 {
     TOptions options = CreateInstance(name);
     foreach (IConfigureOptions setup in _setups)
     {
         if (setup is IConfigureNamedOptions namedSetup)
             namedSetup.Configure(name, options);
         else if (name == Options.DefaultName)
             setup.Configure(options);
     }
     foreach (IPostConfigureOptions post in _postConfigures)
     {
         post.PostConfigure(name, options);
     }

     // Option validation...

     return options;
 }

 protected virtual TOptions CreateInstance(string name)
 {
     return Activator.CreateInstance();
 }
 

option verification

The options mode provides the IValidateOptions interface, which contains a Validate method to verify the options. Inject the interface into the container. When the factory is called to create an option, the Validate method in the interface will be called to verify the option.

// OptionsFactory
 public TOptions Create(string name)
 {
     TOptions options = CreateInstance(name);
     //Option configuration...

     if (_validations.Length > 0)
     {
         var failures = new List();
         foreach (IValidateOptions 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;
 }
 

If you need to add verification for options, implement the IValidateOptions interface and inject it into the container:

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton
     <IValidateOptions, ValidateSettingsOptions>());
 

You can also add DataAnnotations validation by calling the ValidateDataAnnotations extension method, which is defined in Microsoft.Extensions.Options.DataAnnotations. You need to first call the AddOptions extension method to create OptionsBuilder:

builder.Services.AddOptions()
     .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
     .ValidateDataAnnotations();
 

binding configuration

Bind options to the configuration through the following extension methods:

builder.Services.Configure(builder.Configuration.GetSection("Settings"));
 

Binding to the configuration is achieved through the IConfiguration.Bind() extension method. At the same time, monitoring of configuration modifications is also added:

public static IServiceCollection Configure(this IServiceCollection services, string name, IConfiguration config, Action configureBinder) where TOptions : class
 {
     if (services == null)
         throw new ArgumentNullException(nameof(services));

     if (config == null)
         throw new ArgumentNullException(nameof(config));

     services.AddOptions();
     services.AddSingleton<IOptionsChangeTokenSource>(
         new ConfigurationChangeTokenSource(name, config));
     return services.AddSingleton<IConfigureOptions>(
         new NamedConfigureFromConfigurationOptions(name, config, configureBinder));
 }
 

Please indicate the source when reprinting, and exchanges are welcome.ptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations();

binding configuration

Bind options to the configuration through the following extension methods:

builder.Services.Configure(builder.Configuration.GetSection("Settings"));
 

Binding to the configuration is achieved through the IConfiguration.Bind() extension method. At the same time, monitoring of configuration modifications is also added:

public static IServiceCollection Configure(this IServiceCollection services, string name, IConfiguration config, Action configureBinder) where TOptions : class
 {
     if (services == null)
         throw new ArgumentNullException(nameof(services));

     if (config == null)
         throw new ArgumentNullException(nameof(config));

     services.AddOptions();
     services.AddSingleton<IOptionsChangeTokenSource>(
         new ConfigurationChangeTokenSource(name, config));
     return services.AddSingleton<IConfigureOptions>(
         new NamedConfigureFromConfigurationOptions(name, config, configureBinder));
 }
 

Please indicate the source when reprinting, and exchanges are welcome.

This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/811149

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索