3

ASP.NET Core - 选项系统之源码介绍 - 啊晚

 1 year ago
source link: https://www.cnblogs.com/wewant/p/17111746.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

Configure

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

点击查看代码 OptionsConfigurationServiceCollectionExtensions.Configure

public static class OptionsConfigurationServiceCollectionExtensions
{
	/// <summary>
	/// Registers a configuration instance which TOptions will bind against.
	/// </summary>
	/// <typeparam name="TOptions">The type of options being configured.</typeparam>
	/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
	/// <param name="config">The configuration being bound.</param>
	/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
		=> services.Configure<TOptions>(Options.Options.DefaultName, config);

	/// <summary>
	/// Registers a configuration instance which TOptions will bind against.
	/// </summary>
	/// <typeparam name="TOptions">The type of options being configured.</typeparam>
	/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
	/// <param name="name">The name of the options instance.</param>
	/// <param name="config">The configuration being bound.</param>
	/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class
		=> services.Configure<TOptions>(name, config, _ => { });

	/// <summary>
	/// Registers a configuration instance which TOptions will bind against.
	/// </summary>
	/// <typeparam name="TOptions">The type of options being configured.</typeparam>
	/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
	/// <param name="config">The configuration being bound.</param>
	/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
	/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config, Action<BinderOptions> configureBinder)
		where TOptions : class
		=> services.Configure<TOptions>(Options.Options.DefaultName, config, configureBinder);

	/// <summary>
	/// Registers a configuration instance which TOptions will bind against.
	/// </summary>
	/// <typeparam name="TOptions">The type of options being configured.</typeparam>
	/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
	/// <param name="name">The name of the options instance.</param>
	/// <param name="config">The configuration being bound.</param>
	/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
	/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] 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));
		}

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

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

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

点击查看代码 OptionsServiceCollectionExtensions.Configure

public static class OptionsServiceCollectionExtensions
{

	public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
		=> services.Configure(Options.Options.DefaultName, configureOptions);

	public 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;
	}
}

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

IConfigureOptions、IConfigureNamedOptions、IPostConfigureOptions

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

点击查看代码 ConfigureOptions

public class ConfigureOptions<TOptions> : IConfigureOptions<TOptions> where TOptions : class
{
	/// <summary>
	/// Constructor.
	/// </summary>
	/// <param name="action">The action to register.</param>
	public ConfigureOptions(Action<TOptions> action)
	{
		Action = action;
	}

	/// <summary>
	/// The configuration action.
	/// </summary>
	public Action<TOptions> Action { get; }

	/// <summary>
	/// Invokes the registered configure <see cref="Action"/>.
	/// </summary>
	/// <param name="options">The options instance to configure.</param>
	public virtual void Configure(TOptions options)
	{
		if (options == null)
		{
			throw new ArgumentNullException(nameof(options));
		}

		Action?.Invoke(options);
	}
}

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; }

/// <summary>
/// The options name.
/// </summary>
public string Name { get; }

/// <summary>
/// The configuration action.
/// </summary>
public Action<TOptions> Action { get; }

/// <summary>
/// Invokes the registered configure <see cref="Action"/> if the <paramref name="name"/> matches.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
	if (options == null)
	{
		throw new ArgumentNullException(nameof(options));
	}

	// Null name is used to configure all named options.
	if (Name == null || name == Name)
	{
		Action?.Invoke(options);
	}
}

/// <summary>
/// Invoked to configure a <typeparamref name="TOptions"/> instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
</details>

而 NamedConfigureFromConfigurationOptions<TOptions> 类是 IConfigureNamedOptions<TOptions> 的另一个实现,继承了ConfigureNamedOptions<TOptions> 类,重写了一些行为,最终是通过之前讲到的 ConfigurationBuilder的 Bind 方法将配置绑定到选项类而已。

<details>
<summary>点击查看代码 NamedConfigureFromConfigurationOptions<TOptions></summary>

```csharp
public class NamedConfigureFromConfigurationOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions> : ConfigureNamedOptions<TOptions>
	where TOptions : class
{
	/// <summary>
	/// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
	/// </summary>
	/// <param name="name">The name of the options instance.</param>
	/// <param name="config">The <see cref="IConfiguration"/> instance.</param>
	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
	public NamedConfigureFromConfigurationOptions(string name, IConfiguration config)
		: this(name, config, _ => { })
	{ }

	/// <summary>
	/// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
	/// </summary>
	/// <param name="name">The name of the options instance.</param>
	/// <param name="config">The <see cref="IConfiguration"/> instance.</param>
	/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
	public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
		: base(name, options => BindFromOptions(options, config, configureBinder))
	{
		if (config == null)
		{
			throw new ArgumentNullException(nameof(config));
		}
	}

	[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
		Justification = "The only call to this method is the constructor which is already annotated as RequiresUnreferencedCode.")]
	private static void BindFromOptions(TOptions options, IConfiguration config, Action<BinderOptions> configureBinder) => config.Bind(options, configureBinder);
}

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

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

点击查看代码 AddOptions ```csharp public static class OptionsServiceCollectionExtensions { 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;
}
public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services, string name)
	where TOptions : class
{
	if (services == null)
	{
		throw new ArgumentNullException(nameof(services));
	}

	services.AddOptions();
	return new OptionsBuilder<TOptions>(services, name);
}
</details>

这里可以看出两者的返回值不同,而且第二个方法也调用了第一个方法,第一个方法中主要就是向容器中添加我们常用的IOptions<TOptions>、IOptionsSnapshot<TOptions>、IOptionsMonitor<TOptions> 服务接口,这里也可以看到不同服务接口对于的生命周期。除此之外还有工厂服务IOptionsFactory<>和缓存服务IOptionsMonitorCache<>,这两个就是选项体系的关键。每个选项进行配置的时候都会同时注入这些服务,所以每一个选项我们都能使用三个不同接口去解析。

# OptionsBuilder

上面第二个 AddOptions 方法返回 OptionsBuilder<TOptions> 对象。之前讲过 OptionsBuilder<TOptions> 类中也有 Configure 方法,其实不止 Configure 方法,其他的 PostConfigure 方法等也有,它其实就是最终的选项系统配置类,我们所有的选项配置其实都可以通过调用第二个 AddOptions 方法,再通过 OptionsBuilder<TOptions> 对象中的方法来完成配置。其他各个扩展方法的配置方式不过是进行了使用简化而已。

<details>
<summary>点击查看代码 OptionsBuilder<TOptions></summary>

```csharp
public class OptionsBuilder<TOptions> where TOptions : class
{
	private const string DefaultValidationFailureMessage = "A validation error has occurred.";
	
	public string Name { get; }
	
	public IServiceCollection Services { get; }
	
	public OptionsBuilder(IServiceCollection services, string name)
	{
		Services = services;
		Name = name ?? Options.DefaultName;
	}
	
	public virtual OptionsBuilder<TOptions> Configure(Action<TOptions> configureOptions)
	{
		Services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(Name, configureOptions));
		return this;
	}
	
	public virtual OptionsBuilder<TOptions> PostConfigure(Action<TOptions> configureOptions)
	{
		Services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(Name, configureOptions));
		return this;
	}
	
	public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation)
		=> Validate(validation: validation, failureMessage: DefaultValidationFailureMessage);
		
	public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation, string failureMessage)
	{
		Services.AddSingleton<IValidateOptions<TOptions>>(new ValidateOptions<TOptions>(Name, validation, failureMessage));
		return this;
	}
}

IValidateOptions

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

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

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

点击查看代码 ValidateOptions

public class ValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
	/// <summary>
	/// Constructor.
	/// </summary>
	/// <param name="name">Options name.</param>
	/// <param name="validation">Validation function.</param>
	/// <param name="failureMessage">Validation failure message.</param>
	public ValidateOptions(string name, Func<TOptions, bool> validation, string failureMessage)
	{
		Name = name;
		Validation = validation;
		FailureMessage = failureMessage;
	}

	/// <summary>
	/// The options name.
	/// </summary>
	public string Name { get; }

	/// <summary>
	/// The validation function.
	/// </summary>
	public Func<TOptions, bool> Validation { get; }

	/// <summary>
	/// The error to return when validation fails.
	/// </summary>
	public string FailureMessage { get; }

	/// <summary>
	/// Validates a specific named options instance (or all when <paramref name="name"/> is null).
	/// </summary>
	/// <param name="name">The name of the options instance being validated.</param>
	/// <param name="options">The options instance.</param>
	/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
	public ValidateOptionsResult Validate(string name, TOptions options)
	{
		// null name is used to configure all named options
		if (Name == null || name == Name)
		{
			if ((Validation?.Invoke(options)).Value)
			{
				return ValidateOptionsResult.Success;
			}
			return ValidateOptionsResult.Fail(FailureMessage);
		}

		// ignored if not validating this instance
		return ValidateOptionsResult.Skip;
	}
}

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

Options、UnnamedOptionsManager

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

点击查看代码 UnnamedOptionsManager

internal sealed class UnnamedOptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
	IOptions<TOptions>
	where TOptions : class
{
	private readonly IOptionsFactory<TOptions> _factory;
	private volatile object _syncObj;
	private volatile TOptions _value;

	public UnnamedOptionsManager(IOptionsFactory<TOptions> 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);
			}
		}
	}
}

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

IOptionsSnapshot、OptionsManager

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

点击查看代码 OptionsManager

public class OptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
	IOptions<TOptions>,
	IOptionsSnapshot<TOptions>
	where TOptions : class
{
	private readonly IOptionsFactory<TOptions> _factory;
	private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache

	/// <summary>
	/// Initializes a new instance with the specified options configurations.
	/// </summary>
	/// <param name="factory">The factory to use to create options.</param>
	public OptionsManager(IOptionsFactory<TOptions> factory)
	{
		_factory = factory;
	}

	/// <summary>
	/// The default configured <typeparamref name="TOptions"/> instance, equivalent to Get(Options.DefaultName).
	/// </summary>
	public TOptions Value => Get(Options.DefaultName);

	/// <summary>
	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
	/// </summary>
	public virtual TOptions Get(string name)
	{
		name = name ?? Options.DefaultName;

		if (!_cache.TryGetValue(name, out TOptions options))
		{
			// Store the options in our instance cache. Avoid closure on fast path by storing state into scoped locals.
			IOptionsFactory<TOptions> localFactory = _factory;
			string localName = name;
			options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
		}

		return options;
	}
}

IOptionsMonitor、OptionsMonitor

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

点击查看代码 OptionsMonitor

public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
	IOptionsMonitor<TOptions>,
	IDisposable
	where TOptions : class
{
	private readonly IOptionsMonitorCache<TOptions> _cache;
	private readonly IOptionsFactory<TOptions> _factory;
	private readonly List<IDisposable> _registrations = new List<IDisposable>();
	internal event Action<TOptions, string> _onChange;

	/// <summary>
	/// Constructor.
	/// </summary>
	/// <param name="factory">The factory to use to create options.</param>
	/// <param name="sources">The sources used to listen for changes to the options instance.</param>
	/// <param name="cache">The cache used to store options.</param>
	public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
	{
		_factory = factory;
		_cache = cache;

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

			_registrations.Add(registration);
		}

		// The default DI container uses arrays under the covers. Take advantage of this knowledge
		// by checking for an array and enumerate over that, so we don't need to allocate an enumerator.
		if (sources is IOptionsChangeTokenSource<TOptions>[] sourcesArray)
		{
			foreach (IOptionsChangeTokenSource<TOptions> source in sourcesArray)
			{
				RegisterSource(source);
			}
		}
		else
		{
			foreach (IOptionsChangeTokenSource<TOptions> 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);
		}
	}

	/// <summary>
	/// The present value of the options.
	/// </summary>
	public TOptions CurrentValue
	{
		get => Get(Options.DefaultName);
	}

	/// <summary>
	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
	/// </summary>
	public virtual TOptions Get(string name)
	{
		name = name ?? Options.DefaultName;
		return _cache.GetOrAdd(name, () => _factory.Create(name));
	}

	/// <summary>
	/// Registers a listener to be called whenever <typeparamref name="TOptions"/> changes.
	/// </summary>
	/// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param>
	/// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns>
	public IDisposable OnChange(Action<TOptions, string> listener)
	{
		var disposable = new ChangeTrackerDisposable(this, listener);
		_onChange += disposable.OnChange;
		return disposable;
	}

	/// <summary>
	/// Removes all change registration subscriptions.
	/// </summary>
	public void Dispose()
	{
		// Remove all subscriptions to the change tokens
		foreach (IDisposable registration in _registrations)
		{
			registration.Dispose();
		}

		_registrations.Clear();
	}

	internal sealed class ChangeTrackerDisposable : IDisposable
	{
		private readonly Action<TOptions, string> _listener;
		private readonly OptionsMonitor<TOptions> _monitor;

		public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
		{
			_listener = listener;
			_monitor = monitor;
		}

		public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);

		public void Dispose() => _monitor._onChange -= OnChange;
	}
}

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

IOptionsMonitorCache、OptionsCache

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

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

IOptionsFactory、OptionsFactory

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

点击查看代码 OptionsFactory

public class OptionsFactory<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
	IOptionsFactory<TOptions>
	where TOptions : class
{
	private readonly IConfigureOptions<TOptions>[] _setups;
	private readonly IPostConfigureOptions<TOptions>[] _postConfigures;
	private readonly IValidateOptions<TOptions>[] _validations;

	/// <summary>
	/// Initializes a new instance with the specified options configurations.
	/// </summary>
	/// <param name="setups">The configuration actions to run.</param>
	/// <param name="postConfigures">The initialization actions to run.</param>
	public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: Array.Empty<IValidateOptions<TOptions>>())
	{ }

	/// <summary>
	/// Initializes a new instance with the specified options configurations.
	/// </summary>
	/// <param name="setups">The configuration actions to run.</param>
	/// <param name="postConfigures">The initialization actions to run.</param>
	/// <param name="validations">The validations to run.</param>
	public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
	{
		// The default DI container uses arrays under the covers. Take advantage of this knowledge
		// by checking for an array and enumerate over that, so we don't need to allocate an enumerator.
		// When it isn't already an array, convert it to one, but don't use System.Linq to avoid pulling Linq in to
		// small trimmed applications.

		_setups = setups as IConfigureOptions<TOptions>[] ?? new List<IConfigureOptions<TOptions>>(setups).ToArray();
		_postConfigures = postConfigures as IPostConfigureOptions<TOptions>[] ?? new List<IPostConfigureOptions<TOptions>>(postConfigures).ToArray();
		_validations = validations as IValidateOptions<TOptions>[] ?? new List<IValidateOptions<TOptions>>(validations).ToArray();
	}

	/// <summary>
	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
	/// </summary>
	public TOptions Create(string name)
	{
		TOptions options = CreateInstance(name);
		foreach (IConfigureOptions<TOptions> setup in _setups)
		{
			if (setup is IConfigureNamedOptions<TOptions> namedSetup)
			{
				namedSetup.Configure(name, options);
			}
			else if (name == Options.DefaultName)
			{
				setup.Configure(options);
			}
		}
		foreach (IPostConfigureOptions<TOptions> post in _postConfigures)
		{
			post.PostConfigure(name, options);
		}

		if (_validations != null)
		{
			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;
	}

	/// <summary>
	/// Creates a new instance of options type
	/// </summary>
	protected virtual TOptions CreateInstance(string name)
	{
		return Activator.CreateInstance<TOptions>();
	}
}

以上就是 .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 - 选项系统之选项验证


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK