.NET Core 选项系统的主要实现在 Microsoft.Extensions.Options 和 Microsoft.Extensions.Options.ConfigurationExtensions 两个 Nuget 包。对于一个框架的源码进行解读,我们可以从我们常用的框架中的类或方法入手,这些类或方法就是我们解读的入口。

从上面对选项系统的介绍中,大家也可以看出,日常对选项系统的使用涉及到的主要有 Configure 方法,有 IOptions、IOptionsSnapshot、IOptionMonitor 等接口。

Configure

首先看选项注册,也就是 Configure 方法,注册相关的方法都是扩展方法,上面也讲到 Configure 方法有多个扩展来源,其中最常用的是 OptionsConfigurationServiceCollectionExtensions 中的 Configure 方法,该方法用于从配置信息中读取配置并绑定为选项,如下,这里将相应的方法单独摘出来了。

点击查看代码 OptionsConfigurationServiceCollectionExtensions.Configure
  1. public static class OptionsConfigurationServiceCollectionExtensions
  2. {
  3. /// <summary>
  4. /// Registers a configuration instance which TOptions will bind against.
  5. /// </summary>
  6. /// <typeparam name="TOptions">The type of options being configured.</typeparam>
  7. /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
  8. /// <param name="config">The configuration being bound.</param>
  9. /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
  10. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
  11. public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
  12. => services.Configure<TOptions>(Options.Options.DefaultName, config);
  13. /// <summary>
  14. /// Registers a configuration instance which TOptions will bind against.
  15. /// </summary>
  16. /// <typeparam name="TOptions">The type of options being configured.</typeparam>
  17. /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
  18. /// <param name="name">The name of the options instance.</param>
  19. /// <param name="config">The configuration being bound.</param>
  20. /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
  21. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
  22. public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class
  23. => services.Configure<TOptions>(name, config, _ => { });
  24. /// <summary>
  25. /// Registers a configuration instance which TOptions will bind against.
  26. /// </summary>
  27. /// <typeparam name="TOptions">The type of options being configured.</typeparam>
  28. /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
  29. /// <param name="config">The configuration being bound.</param>
  30. /// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
  31. /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
  32. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
  33. public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config, Action<BinderOptions> configureBinder)
  34. where TOptions : class
  35. => services.Configure<TOptions>(Options.Options.DefaultName, config, configureBinder);
  36. /// <summary>
  37. /// Registers a configuration instance which TOptions will bind against.
  38. /// </summary>
  39. /// <typeparam name="TOptions">The type of options being configured.</typeparam>
  40. /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
  41. /// <param name="name">The name of the options instance.</param>
  42. /// <param name="config">The configuration being bound.</param>
  43. /// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
  44. /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
  45. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
  46. public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)
  47. where TOptions : class
  48. {
  49. if (services == null)
  50. {
  51. throw new ArgumentNullException(nameof(services));
  52. }
  53. if (config == null)
  54. {
  55. throw new ArgumentNullException(nameof(config));
  56. }
  57. services.AddOptions();
  58. services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
  59. return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
  60. }
  61. }

其中 IOptionsChangeTokenSource 接口是用来监听配置变化的服务,这个后面讲。

另外还有 OptionsServiceCollectionExtensions 中的 Configure 方法,用于直接通过委托对选项类进行配置。

点击查看代码 OptionsServiceCollectionExtensions.Configure
  1. public static class OptionsServiceCollectionExtensions
  2. {
  3. public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
  4. => services.Configure(Options.Options.DefaultName, configureOptions);
  5. public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
  6. where TOptions : class
  7. {
  8. if (services == null)
  9. {
  10. throw new ArgumentNullException(nameof(services));
  11. }
  12. if (configureOptions == null)
  13. {
  14. throw new ArgumentNullException(nameof(configureOptions));
  15. }
  16. services.AddOptions();
  17. services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
  18. return services;
  19. }
  20. }

可以看出,其实选项系统中的选项都是命名模式的,默认名称为 Options.Options.DefaultName,实际就是 string.Empty。当我们调用 Configure 方法对选项进行配置的时候,实际上时调用了 AddOptions 方法,并且往容器中添加了一个单例的实现了 IConfigureOptions 接口的实现。

IConfigureOptions、IConfigureNamedOptions、IPostConfigureOptions

其中 IConfigureOptions 是选项配置行为服务接口,ConfigureOptions 是它的默认实现,该类的内容很简单,它的内部主要就是保存了一个委托,用于记录使用者对选项的配置操作。

点击查看代码 ConfigureOptions
  1. public class ConfigureOptions<TOptions> : IConfigureOptions<TOptions> where TOptions : class
  2. {
  3. /// <summary>
  4. /// Constructor.
  5. /// </summary>
  6. /// <param name="action">The action to register.</param>
  7. public ConfigureOptions(Action<TOptions> action)
  8. {
  9. Action = action;
  10. }
  11. /// <summary>
  12. /// The configuration action.
  13. /// </summary>
  14. public Action<TOptions> Action { get; }
  15. /// <summary>
  16. /// Invokes the registered configure <see cref="Action"/>.
  17. /// </summary>
  18. /// <param name="options">The options instance to configure.</param>
  19. public virtual void Configure(TOptions options)
  20. {
  21. if (options == null)
  22. {
  23. throw new ArgumentNullException(nameof(options));
  24. }
  25. Action?.Invoke(options);
  26. }
  27. }

IConfigureNamedOptions 继承了 IConfigureNamedOptions 接口,默认实现是 ConfigureNamedOptions ,作用一样,只不过多了一个方法用于应对命名选项模式。它有多个重载泛型重载,也是之前的文章ASP.NET Core - 选型系统之选型配置 中讲到的“使用DI服务配置选项”的具体实现。

点击查看代码 ConfigureNamedOptions

```csharp
public class ConfigureNamedOptions : IConfigureNamedOptions where TOptions : class
{
///

/// Constructor.
///

/// The name of the options.
/// The action to register.
public ConfigureNamedOptions(string name, Action action)
{
Name = name;
Action = action;
}

  1. /// <summary>
  2. /// The options name.
  3. /// </summary>
  4. public string Name { get; }
  5. /// <summary>
  6. /// The configuration action.
  7. /// </summary>
  8. public Action<TOptions> Action { get; }
  9. /// <summary>
  10. /// Invokes the registered configure <see cref="Action"/> if the <paramref name="name"/> matches.
  11. /// </summary>
  12. /// <param name="name">The name of the options instance being configured.</param>
  13. /// <param name="options">The options instance to configure.</param>
  14. public virtual void Configure(string name, TOptions options)
  15. {
  16. if (options == null)
  17. {
  18. throw new ArgumentNullException(nameof(options));
  19. }
  20. // Null name is used to configure all named options.
  21. if (Name == null || name == Name)
  22. {
  23. Action?.Invoke(options);
  24. }
  25. }
  26. /// <summary>
  27. /// Invoked to configure a <typeparamref name="TOptions"/> instance with the <see cref="Options.DefaultName"/>.
  28. /// </summary>
  29. /// <param name="options">The options instance to configure.</param>
  30. public void Configure(TOptions options) => Configure(Options.DefaultName, options);

}

  1. </details>
  2. 而 NamedConfigureFromConfigurationOptions<TOptions> 类是 IConfigureNamedOptions<TOptions> 的另一个实现,继承了ConfigureNamedOptions<TOptions> 类,重写了一些行为,最终是通过之前讲到的 ConfigurationBuilder的 Bind 方法将配置绑定到选项类而已。
  3. <details>
  4. <summary>点击查看代码 NamedConfigureFromConfigurationOptions<TOptions></summary>
  5. ```csharp
  6. public class NamedConfigureFromConfigurationOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions> : ConfigureNamedOptions<TOptions>
  7. where TOptions : class
  8. {
  9. /// <summary>
  10. /// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
  11. /// </summary>
  12. /// <param name="name">The name of the options instance.</param>
  13. /// <param name="config">The <see cref="IConfiguration"/> instance.</param>
  14. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
  15. public NamedConfigureFromConfigurationOptions(string name, IConfiguration config)
  16. : this(name, config, _ => { })
  17. { }
  18. /// <summary>
  19. /// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
  20. /// </summary>
  21. /// <param name="name">The name of the options instance.</param>
  22. /// <param name="config">The <see cref="IConfiguration"/> instance.</param>
  23. /// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
  24. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
  25. public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
  26. : base(name, options => BindFromOptions(options, config, configureBinder))
  27. {
  28. if (config == null)
  29. {
  30. throw new ArgumentNullException(nameof(config));
  31. }
  32. }
  33. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
  34. Justification = "The only call to this method is the constructor which is already annotated as RequiresUnreferencedCode.")]
  35. private static void BindFromOptions(TOptions options, IConfiguration config, Action<BinderOptions> configureBinder) => config.Bind(options, configureBinder);
  36. }

其他的 IPostConfigureOptions 接口也是一样套路,当我们通过相应的方法传入委托对选项类进行配置的时候,会向容器中注入一个单例服务,将配置行为保存起来。

接着往下看 AddOptions 方法,AddOptions 方法有两个重载:

点击查看代码 AddOptions

```csharp
public static class OptionsServiceCollectionExtensions
{
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

  1. services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
  2. services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
  3. services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
  4. services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
  5. services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
  6. return services;
  7. }
  8. public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services, string name)
  9. where TOptions : class
  10. {
  11. if (services == null)
  12. {
  13. throw new ArgumentNullException(nameof(services));
  14. }
  15. services.AddOptions();
  16. return new OptionsBuilder<TOptions>(services, name);
  17. }

}

  1. </details>
  2. 这里可以看出两者的返回值不同,而且第二个方法也调用了第一个方法,第一个方法中主要就是向容器中添加我们常用的IOptions<TOptions>、IOptionsSnapshot<TOptions>、IOptionsMonitor<TOptions> 服务接口,这里也可以看到不同服务接口对于的生命周期。除此之外还有工厂服务IOptionsFactory<>和缓存服务IOptionsMonitorCache<>,这两个就是选项体系的关键。每个选项进行配置的时候都会同时注入这些服务,所以每一个选项我们都能使用三个不同接口去解析。
  3. # OptionsBuilder
  4. 上面第二个 AddOptions 方法返回 OptionsBuilder<TOptions> 对象。之前讲过 OptionsBuilder<TOptions> 类中也有 Configure 方法,其实不止 Configure 方法,其他的 PostConfigure 方法等也有,它其实就是最终的选项系统配置类,我们所有的选项配置其实都可以通过调用第二个 AddOptions 方法,再通过 OptionsBuilder<TOptions> 对象中的方法来完成配置。其他各个扩展方法的配置方式不过是进行了使用简化而已。
  5. <details>
  6. <summary>点击查看代码 OptionsBuilder<TOptions></summary>
  7. ```csharp
  8. public class OptionsBuilder<TOptions> where TOptions : class
  9. {
  10. private const string DefaultValidationFailureMessage = "A validation error has occurred.";
  11. public string Name { get; }
  12. public IServiceCollection Services { get; }
  13. public OptionsBuilder(IServiceCollection services, string name)
  14. {
  15. Services = services;
  16. Name = name ?? Options.DefaultName;
  17. }
  18. public virtual OptionsBuilder<TOptions> Configure(Action<TOptions> configureOptions)
  19. {
  20. Services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(Name, configureOptions));
  21. return this;
  22. }
  23. public virtual OptionsBuilder<TOptions> PostConfigure(Action<TOptions> configureOptions)
  24. {
  25. Services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(Name, configureOptions));
  26. return this;
  27. }
  28. public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation)
  29. => Validate(validation: validation, failureMessage: DefaultValidationFailureMessage);
  30. public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation, string failureMessage)
  31. {
  32. Services.AddSingleton<IValidateOptions<TOptions>>(new ValidateOptions<TOptions>(Name, validation, failureMessage));
  33. return this;
  34. }
  35. }

IValidateOptions

我们除了可以对选项进行配置绑定之外,还可以对选项进行验证。验证规则是通过上面的第二个 AddOptions 方法返回的 OptionsBuilder 方法进行添加的。

验证规则配置有三种方式,最后其实都是通过 IValidateOptions 的实现类来完成。我们自己实现的自定义验证类就不用说了,最后我们会将其注入到容器中,而从上面的代码中可以看到,当我们通过委托的方式自定义验证规则的时候,它会被构建成一个 ValidateOptions 类对象,并注入到容器中作为一个服务。

ValidateOptions 是 IValidateOptions 的一个实现类,构造函数中接收委托,通过委托返回的 bool 结构判断验证是否通过。

点击查看代码 ValidateOptions
  1. public class ValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
  2. {
  3. /// <summary>
  4. /// Constructor.
  5. /// </summary>
  6. /// <param name="name">Options name.</param>
  7. /// <param name="validation">Validation function.</param>
  8. /// <param name="failureMessage">Validation failure message.</param>
  9. public ValidateOptions(string name, Func<TOptions, bool> validation, string failureMessage)
  10. {
  11. Name = name;
  12. Validation = validation;
  13. FailureMessage = failureMessage;
  14. }
  15. /// <summary>
  16. /// The options name.
  17. /// </summary>
  18. public string Name { get; }
  19. /// <summary>
  20. /// The validation function.
  21. /// </summary>
  22. public Func<TOptions, bool> Validation { get; }
  23. /// <summary>
  24. /// The error to return when validation fails.
  25. /// </summary>
  26. public string FailureMessage { get; }
  27. /// <summary>
  28. /// Validates a specific named options instance (or all when <paramref name="name"/> is null).
  29. /// </summary>
  30. /// <param name="name">The name of the options instance being validated.</param>
  31. /// <param name="options">The options instance.</param>
  32. /// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
  33. public ValidateOptionsResult Validate(string name, TOptions options)
  34. {
  35. // null name is used to configure all named options
  36. if (Name == null || name == Name)
  37. {
  38. if ((Validation?.Invoke(options)).Value)
  39. {
  40. return ValidateOptionsResult.Success;
  41. }
  42. return ValidateOptionsResult.Fail(FailureMessage);
  43. }
  44. // ignored if not validating this instance
  45. return ValidateOptionsResult.Skip;
  46. }
  47. }

我们可以通过重载方法传入相应的验证失败提醒文本。

Options、UnnamedOptionsManager

接下来看选项使用相关的内容,其中 IOptions 中的选项类一经创建一直保持不变,默认实现类 UnnamedOptionsManager。

点击查看代码 UnnamedOptionsManager
  1. internal sealed class UnnamedOptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
  2. 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. {
  15. return value;
  16. }
  17. lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
  18. {
  19. return _value ??= _factory.Create(Options.DefaultName);
  20. }
  21. }
  22. }
  23. }

IOptions 接口只有一个 Value 属性,实现类中通过锁确保创建的 Value 值不会因为线程问题导致不同,且该服务被注册为单例生命周期,所以对象不销毁,后续一直会读取内存中的 Value 值。具体选项类对象的创建由工厂服务负责。

IOptionsSnapshot、OptionsManager

IOptionsSnapshot 的实现类是 OptionsManager,该类中有一个私有的 OptionsCache 属性,每次对选项类进行读取的时候,都是先尝试从缓存读取,如果没有才创建。而由于 IOptionsSnapshot 被注册为请求域生命周期,所以单次请求内相应对象不会销毁,缓存不会清空,会一直保持一个。

点击查看代码 OptionsManager
  1. public class OptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
  2. IOptions<TOptions>,
  3. IOptionsSnapshot<TOptions>
  4. where TOptions : class
  5. {
  6. private readonly IOptionsFactory<TOptions> _factory;
  7. private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache
  8. /// <summary>
  9. /// Initializes a new instance with the specified options configurations.
  10. /// </summary>
  11. /// <param name="factory">The factory to use to create options.</param>
  12. public OptionsManager(IOptionsFactory<TOptions> factory)
  13. {
  14. _factory = factory;
  15. }
  16. /// <summary>
  17. /// The default configured <typeparamref name="TOptions"/> instance, equivalent to Get(Options.DefaultName).
  18. /// </summary>
  19. public TOptions Value => Get(Options.DefaultName);
  20. /// <summary>
  21. /// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
  22. /// </summary>
  23. public virtual TOptions Get(string name)
  24. {
  25. name = name ?? Options.DefaultName;
  26. if (!_cache.TryGetValue(name, out TOptions options))
  27. {
  28. // Store the options in our instance cache. Avoid closure on fast path by storing state into scoped locals.
  29. IOptionsFactory<TOptions> localFactory = _factory;
  30. string localName = name;
  31. options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
  32. }
  33. return options;
  34. }
  35. }

IOptionsMonitor、OptionsMonitor

IOptionsMonitor 每次获取选项类都是最新的值,它实现类是 OptionsMonitor,实现类中使用了从容器中注入的单例缓存 IOptionsMonitorCache 来保存选项类,并且通过相应的 IOptionsChangeTokenSource 注册了选项类绑定内容的监听,例如上面讲到的 ConfigurationChangeTokenSource,在选项类配置内容改变的时候会触发事件,而在事件中会将缓存先情况并重新获取创建类,并且执行注册进来的额外的监听事件,可以看看下面的 InvokeChanged 方法。

点击查看代码 OptionsMonitor
  1. public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
  2. IOptionsMonitor<TOptions>,
  3. IDisposable
  4. where TOptions : class
  5. {
  6. private readonly IOptionsMonitorCache<TOptions> _cache;
  7. private readonly IOptionsFactory<TOptions> _factory;
  8. private readonly List<IDisposable> _registrations = new List<IDisposable>();
  9. internal event Action<TOptions, string> _onChange;
  10. /// <summary>
  11. /// Constructor.
  12. /// </summary>
  13. /// <param name="factory">The factory to use to create options.</param>
  14. /// <param name="sources">The sources used to listen for changes to the options instance.</param>
  15. /// <param name="cache">The cache used to store options.</param>
  16. public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
  17. {
  18. _factory = factory;
  19. _cache = cache;
  20. void RegisterSource(IOptionsChangeTokenSource<TOptions> source)
  21. {
  22. IDisposable registration = ChangeToken.OnChange(
  23. () => source.GetChangeToken(),
  24. (name) => InvokeChanged(name),
  25. source.Name);
  26. _registrations.Add(registration);
  27. }
  28. // The default DI container uses arrays under the covers. Take advantage of this knowledge
  29. // by checking for an array and enumerate over that, so we don't need to allocate an enumerator.
  30. if (sources is IOptionsChangeTokenSource<TOptions>[] sourcesArray)
  31. {
  32. foreach (IOptionsChangeTokenSource<TOptions> source in sourcesArray)
  33. {
  34. RegisterSource(source);
  35. }
  36. }
  37. else
  38. {
  39. foreach (IOptionsChangeTokenSource<TOptions> source in sources)
  40. {
  41. RegisterSource(source);
  42. }
  43. }
  44. }
  45. private void InvokeChanged(string name)
  46. {
  47. name = name ?? Options.DefaultName;
  48. _cache.TryRemove(name);
  49. TOptions options = Get(name);
  50. if (_onChange != null)
  51. {
  52. _onChange.Invoke(options, name);
  53. }
  54. }
  55. /// <summary>
  56. /// The present value of the options.
  57. /// </summary>
  58. public TOptions CurrentValue
  59. {
  60. get => Get(Options.DefaultName);
  61. }
  62. /// <summary>
  63. /// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
  64. /// </summary>
  65. public virtual TOptions Get(string name)
  66. {
  67. name = name ?? Options.DefaultName;
  68. return _cache.GetOrAdd(name, () => _factory.Create(name));
  69. }
  70. /// <summary>
  71. /// Registers a listener to be called whenever <typeparamref name="TOptions"/> changes.
  72. /// </summary>
  73. /// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param>
  74. /// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns>
  75. public IDisposable OnChange(Action<TOptions, string> listener)
  76. {
  77. var disposable = new ChangeTrackerDisposable(this, listener);
  78. _onChange += disposable.OnChange;
  79. return disposable;
  80. }
  81. /// <summary>
  82. /// Removes all change registration subscriptions.
  83. /// </summary>
  84. public void Dispose()
  85. {
  86. // Remove all subscriptions to the change tokens
  87. foreach (IDisposable registration in _registrations)
  88. {
  89. registration.Dispose();
  90. }
  91. _registrations.Clear();
  92. }
  93. internal sealed class ChangeTrackerDisposable : IDisposable
  94. {
  95. private readonly Action<TOptions, string> _listener;
  96. private readonly OptionsMonitor<TOptions> _monitor;
  97. public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
  98. {
  99. _listener = listener;
  100. _monitor = monitor;
  101. }
  102. public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
  103. public void Dispose() => _monitor._onChange -= OnChange;
  104. }
  105. }

OnChange 方法中传入的委托本来可以可以直接追加到事件中的,这里将其再包装多一层,是为了 OptionsMonitor 对象销毁的时候能够将相应的事件释放,如果不包装多一层的话,委托只在方法作用域中,对象释放的时候是获取不到的。

IOptionsMonitorCache、OptionsCache

OptionsCache 是 IOptionsMonitorCache 接口的的实现类,从上面可以看到 OptionsMonitor 和 OptionsSnapshot 都使用到了这个,OptionsSnapshot 通过内部创建的私有的缓存属性实现了请求域内选项类不变,而 OptionsMonitor 则通过它减少了每次都直接读取配置来源(如文件、数据库、配置中心api)的性能消耗,而是通过变更事件的方式进行更新。其实我们还可以在需要的时候注入IOptionsMonitorCache 服务自行对选项类进行更新。

OptionsCache 的具体实现比较简单,主要就是通过 ConcurrentDictionary<string, Lazy> 对象作为内存缓存,其中为了性能还再使用了 Lazy 方式。

IOptionsFactory、OptionsFactory

OptionsFactory 类实现 IOptionsFactory 接口,是选项类的实际创建配置之处,其实就是将之前注册到容器中与当前相关的各种配置、验证的行为配置类注入进来,再通过放射创建对象之后,将选项类对象传进去,逐一对相应的行为进行调用,最后得到一个成型的选项类。这里选项类的创建方式很简单,这也是要求选项类要有无参构造函数的原因。

点击查看代码 OptionsFactory
  1. public class OptionsFactory<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
  2. IOptionsFactory<TOptions>
  3. where TOptions : class
  4. {
  5. private readonly IConfigureOptions<TOptions>[] _setups;
  6. private readonly IPostConfigureOptions<TOptions>[] _postConfigures;
  7. private readonly IValidateOptions<TOptions>[] _validations;
  8. /// <summary>
  9. /// Initializes a new instance with the specified options configurations.
  10. /// </summary>
  11. /// <param name="setups">The configuration actions to run.</param>
  12. /// <param name="postConfigures">The initialization actions to run.</param>
  13. public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: Array.Empty<IValidateOptions<TOptions>>())
  14. { }
  15. /// <summary>
  16. /// Initializes a new instance with the specified options configurations.
  17. /// </summary>
  18. /// <param name="setups">The configuration actions to run.</param>
  19. /// <param name="postConfigures">The initialization actions to run.</param>
  20. /// <param name="validations">The validations to run.</param>
  21. public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
  22. {
  23. // The default DI container uses arrays under the covers. Take advantage of this knowledge
  24. // by checking for an array and enumerate over that, so we don't need to allocate an enumerator.
  25. // When it isn't already an array, convert it to one, but don't use System.Linq to avoid pulling Linq in to
  26. // small trimmed applications.
  27. _setups = setups as IConfigureOptions<TOptions>[] ?? new List<IConfigureOptions<TOptions>>(setups).ToArray();
  28. _postConfigures = postConfigures as IPostConfigureOptions<TOptions>[] ?? new List<IPostConfigureOptions<TOptions>>(postConfigures).ToArray();
  29. _validations = validations as IValidateOptions<TOptions>[] ?? new List<IValidateOptions<TOptions>>(validations).ToArray();
  30. }
  31. /// <summary>
  32. /// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
  33. /// </summary>
  34. public TOptions Create(string name)
  35. {
  36. TOptions options = CreateInstance(name);
  37. foreach (IConfigureOptions<TOptions> setup in _setups)
  38. {
  39. if (setup is IConfigureNamedOptions<TOptions> namedSetup)
  40. {
  41. namedSetup.Configure(name, options);
  42. }
  43. else if (name == Options.DefaultName)
  44. {
  45. setup.Configure(options);
  46. }
  47. }
  48. foreach (IPostConfigureOptions<TOptions> post in _postConfigures)
  49. {
  50. post.PostConfigure(name, options);
  51. }
  52. if (_validations != null)
  53. {
  54. var failures = new List<string>();
  55. foreach (IValidateOptions<TOptions> validate in _validations)
  56. {
  57. ValidateOptionsResult result = validate.Validate(name, options);
  58. if (result is not null && result.Failed)
  59. {
  60. failures.AddRange(result.Failures);
  61. }
  62. }
  63. if (failures.Count > 0)
  64. {
  65. throw new OptionsValidationException(name, typeof(TOptions), failures);
  66. }
  67. }
  68. return options;
  69. }
  70. /// <summary>
  71. /// Creates a new instance of options type
  72. /// </summary>
  73. protected virtual TOptions CreateInstance(string name)
  74. {
  75. return Activator.CreateInstance<TOptions>();
  76. }
  77. }

以上就是 .NET Core 下的选项系统,由于选项系统的源码不多,这里也就将大部分类都拿出来讲了一下,相当于把这个框架的流程思路都讲了一遍,不知不觉写得字数又很多了,希望有童鞋能够耐心地看到这里。

参考文章:

ASP.NET Core 中的选项模式 | Microsoft Learn

选项模式 - .NET | Microsoft Learn

面向 .NET 库创建者的选项模式指南 - .NET | Microsoft Learn

理解ASP.NET Core - 选项(Options)

ASP.NET Core 系列:

目录:ASP.NET Core 系列总结

上一篇:ASP.NET Core - 选项系统之选项验证

ASP.NET Core - 选项系统之源码介绍的更多相关文章

  1. 各类最新Asp .Net Core 项目和示例源码

    1.网站地址:http://www.freeboygirl.com2.网站Asp .Net Core 资料http://www.freeboygirl.com/blog/tag/asp%20net%2 ...

  2. ASP.NET CORE 启动过程及源码解读

    在这个特殊的春节,大家想必都在家出不了们,远看已经到了回城里上班的日子,但是因为一只蝙蝠的原因导致我们无法回到工作岗位,大家可能有的在家远程办公,有些在家躺着看书,有的是在家打游戏:在这个特殊无聊的日 ...

  3. ASP.Net Core Configuration 理解与源码分析

    Configuration 在ASP.NET Core开发过程中起着很重要的作用,这篇博客主要是理解configuration的来源,以及各种不同类型的configuration source是如何被 ...

  4. Nagios监控系统部署(源码)(四)

    Nagios监控系统部署(源码)   1. 概述2. 部署Nagios2.1 创建Nagios用户组2.2 下载Nagios和Nagios-plugin源码2.3 编译安装3. 部署Nagios-pl ...

  5. GGTalk——C#开源即时通讯系统源码介绍系列(一)

    坦白讲,我们公司其实没啥技术实力,之所以还能不断接到各种项目,全凭我们老板神通广大!要知道他每次的饭局上可都是些什么人物! 但是项目接下一大把,就凭咱哥儿几个的水平,想要独立自主.保质保量保期地一个个 ...

  6. centos 6x系统下源码安装mysql操作记录

    在运维工作中经常部署各种运维环境,涉及mysql数据库的安装也是时常需要的.mysql数据库安装可以选择yum在线安装,但是这种安装的mysql一般是系统自带的,版本方面可能跟需求不太匹配.可以通过源 ...

  7. 驾照理论模拟考试系统Android源码下载

    ‍‍‍驾照理论模拟考试系统Android源码下载 <ignore_js_op> 9.png (55.77 KB, 下载次数: 0) <ignore_js_op> 10.png ...

  8. 分布式任务调度系统xxl-job源码探究(一、客户端)

    前面讲了xxl-job的搭建,现在来粗略的解析下该分布式调度系统的源码,先来客户点代码 客户端源码 客户端开启的时候会向服务中心进行注册,其实现用的是jetty连接,且每隔半分钟会发送一次心跳,来告诉 ...

  9. Centos6 系统下源码方式安装Mysql 记录

    在运维工作中经常部署各种运维环境,涉及mysql数据库的安装也是时常需要的.mysql数据库安装可以选择yum在线安装,但是这种安装的mysql一般是系统自带的,版本方面可能跟需求不太匹配. #### ...

  10. ASP.NET Core Web开发学习笔记-1介绍篇

    ASP.NET Core Web开发学习笔记-1介绍篇 给大家说声报歉,从2012年个人情感破裂的那一天,本人的51CTO,CnBlogs,Csdn,QQ,Weboo就再也没有更新过.踏实的生活(曾辞 ...

随机推荐

  1. SpringBatch生成的DB表SQL

    SQL: -- Autogenerated: do not edit this file DROP TABLE IF EXISTS BATCH_STEP_EXECUTION_CONTEXT; DROP ...

  2. list与linkedlist(添加、删除元素)

    1)jdk1.8中list遍历过程中可以直接删除元素了(jdk1.7可以通过倒序遍历删除或iterator遍历删除元素) List<Integer> list = new ArrayLis ...

  3. delete、truncate、drop的区别

    delete:只删除数据,不删除结构.删除的数据存储在系统回滚段中,可以回滚.不会自动提交事务. 在InnoDB中,delete不会真的把数据删除,mysql实际上只是给删除的数据打了个标记为已删除, ...

  4. javaweb链接到数据库(mysql)操作

    准备:配置好数据库,下好mysql connect 第一步:将my connec文件和commons-dbutil(,jar)复制到webapp文件下WEB-INF的lib文件中,然后右键构建路径. ...

  5. springboot项目记录2用户注册功能

    七.注册-业务层 7.1规划异常 7.1.1用户在进行注册的时候,可能会产生用户名被占用的错误,抛出一个异常: RuntimeException异常,作为该异常的子类,然后再去定义具体的异常类型继承这 ...

  6. 30day_网络编程

    由于不同机器上的程序要通信,于是产生通信 C/S架构: Client与Server,客户端(只有用的时候再使用)与服务端(一直运行,等待服务) B/S架构: 浏览器端与服务器端 Browser浏览器, ...

  7. 归纳了一下AD的快捷键

    1:shift+s 键 切换单层显示 2:q     英寸和毫米 尺寸切换3:D+R进入布线规则设置.其中 Clearance 是设置最小安全线间距,覆铜时候间距的.比较常用4:CTRL+鼠标单击某个 ...

  8. nRF52832起来之后测试是上电还是休眠唤醒的方法

    void fu_state_machine_init(void) { /* NRF_POWER_RESETREAS_SREQ_MASK JLINK DOWNLOAD / POWER ON can ca ...

  9. ASP.NET WEBAPI oken验证

    看了下网上关于.net webAPI 的案例全是坑 验证成功了不被微信服务器接收 微信客服有找不到,提问也没人回 自己测试好几个小时 终于发现返回结果只要个string 双引号都不用加 public ...

  10. lambda表达式--箭头函数

    箭头函数(匿名函数):输入参数+->+函数结果(只有当函数需要执行多条语句时,才需要return关键字和花括号) 什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个&quo ...