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


    .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
    {
    	/// 
    	/// Registers a configuration instance which TOptions will bind against.
    	/// 
    	/// The type of options being configured.
    	/// The  to add the services to.
    	/// The configuration being bound.
    	/// The  so that additional calls can be chained.
    	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
    	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
    		=> services.Configure(Options.Options.DefaultName, config);
    
    	/// 
    	/// Registers a configuration instance which TOptions will bind against.
    	/// 
    	/// The type of options being configured.
    	/// The  to add the services to.
    	/// The name of the options instance.
    	/// The configuration being bound.
    	/// The  so that additional calls can be chained.
    	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
    	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class
    		=> services.Configure(name, config, _ => { });
    
    	/// 
    	/// Registers a configuration instance which TOptions will bind against.
    	/// 
    	/// The type of options being configured.
    	/// The  to add the services to.
    	/// The configuration being bound.
    	/// Used to configure the .
    	/// The  so that additional calls can be chained.
    	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
    	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config, Action configureBinder)
    		where TOptions : class
    		=> services.Configure(Options.Options.DefaultName, config, configureBinder);
    
    	/// 
    	/// Registers a configuration instance which TOptions will bind against.
    	/// 
    	/// The type of options being configured.
    	/// The  to add the services to.
    	/// The name of the options instance.
    	/// The configuration being bound.
    	/// Used to configure the .
    	/// The  so that additional calls can be chained.
    	[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
    	public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(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>(new ConfigurationChangeTokenSource(name, config));
    		return services.AddSingleton>(new NamedConfigureFromConfigurationOptions(name, config, configureBinder));
    	}
    }
    

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

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

    点击查看代码 OptionsServiceCollectionExtensions.Configure
    public static class OptionsServiceCollectionExtensions
    {
    
    	public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action configureOptions) where TOptions : class
    		=> services.Configure(Options.Options.DefaultName, configureOptions);
    
    	public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action configureOptions)
    		where TOptions : class
    	{
    		if (services == null)
    		{
    			throw new ArgumentNullException(nameof(services));
    		}
    
    		if (configureOptions == null)
    		{
    			throw new ArgumentNullException(nameof(configureOptions));
    		}
    
    		services.AddOptions();
    		services.AddSingleton>(new ConfigureNamedOptions(name, configureOptions));
    		return services;
    	}
    }
    

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

    IConfigureOptions、IConfigureNamedOptions、IPostConfigureOptions

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

    点击查看代码 ConfigureOptions
    public class ConfigureOptions : IConfigureOptions where TOptions : class
    {
    	/// 
    	/// Constructor.
    	/// 
    	/// The action to register.
    	public ConfigureOptions(Action action)
    	{
    		Action = action;
    	}
    
    	/// 
    	/// The configuration action.
    	/// 
    	public Action Action { get; }
    
    	/// 
    	/// Invokes the registered configure .
    	/// 
    	/// The options instance to configure.
    	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; }
    /// 
    /// The options name.
    /// 
    public string Name { get; }
    
    /// 
    /// The configuration action.
    /// 
    public Action Action { get; }
    
    /// 
    /// Invokes the registered configure  if the  matches.
    /// 
    /// The name of the options instance being configured.
    /// The options instance to configure.
    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);
    	}
    }
    
    /// 
    /// Invoked to configure a  instance with the .
    /// 
    /// The options instance to configure.
    public void Configure(TOptions options) => Configure(Options.DefaultName, options);
    

    }

    而 NamedConfigureFromConfigurationOptions 类是 IConfigureNamedOptions 的另一个实现,继承了ConfigureNamedOptions 类,重写了一些行为,最终是通过之前讲到的 ConfigurationBuilder的 Bind 方法将配置绑定到选项类而已。
    点击查看代码 NamedConfigureFromConfigurationOptions ```csharp public class NamedConfigureFromConfigurationOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions> : ConfigureNamedOptions<TOptions> where TOptions : class { /// /// Constructor that takes the instance to bind against. /// /// The name of the options instance. /// The instance. [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public NamedConfigureFromConfigurationOptions(string name, IConfiguration config) : this(name, config, _ => { }) { } /// /// Constructor that takes the instance to bind against. /// /// The name of the options instance. /// The instance. /// Used to configure the . [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action 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 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(services, name);
    }
    

    }

    这里可以看出两者的返回值不同,而且第二个方法也调用了第一个方法,第一个方法中主要就是向容器中添加我们常用的IOptions、IOptionsSnapshot、IOptionsMonitor 服务接口,这里也可以看到不同服务接口对于的生命周期。除此之外还有工厂服务IOptionsFactory<>和缓存服务IOptionsMonitorCache<>,这两个就是选项体系的关键。每个选项进行配置的时候都会同时注入这些服务,所以每一个选项我们都能使用三个不同接口去解析。 # OptionsBuilder 上面第二个 AddOptions 方法返回 OptionsBuilder 对象。之前讲过 OptionsBuilder 类中也有 Configure 方法,其实不止 Configure 方法,其他的 PostConfigure 方法等也有,它其实就是最终的选项系统配置类,我们所有的选项配置其实都可以通过调用第二个 AddOptions 方法,再通过 OptionsBuilder 对象中的方法来完成配置。其他各个扩展方法的配置方式不过是进行了使用简化而已。
    点击查看代码 OptionsBuilder ```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 configureOptions) { Services.AddSingleton>(new ConfigureNamedOptions(Name, configureOptions)); return this; } public virtual OptionsBuilder<TOptions> PostConfigure(Action configureOptions) { Services.AddSingleton>(new PostConfigureOptions(Name, configureOptions)); return this; } public virtual OptionsBuilder Validate(Funcbool> validation) => Validate(validation: validation, failureMessage: DefaultValidationFailureMessage); public virtual OptionsBuilder<TOptions> Validate(Funcbool> validation, string failureMessage) { Services.AddSingleton>(new ValidateOptions(Name, validation, failureMessage)); return this; } }

    IValidateOptions

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

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

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

    点击查看代码 ValidateOptions
    public class ValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
    {
    	/// 
    	/// Constructor.
    	/// 
    	/// Options name.
    	/// Validation function.
    	/// Validation failure message.
    	public ValidateOptions(string name, Funcbool> validation, string failureMessage)
    	{
    		Name = name;
    		Validation = validation;
    		FailureMessage = failureMessage;
    	}
    
    	/// 
    	/// The options name.
    	/// 
    	public string Name { get; }
    
    	/// 
    	/// The validation function.
    	/// 
    	public Funcbool> Validation { get; }
    
    	/// 
    	/// The error to return when validation fails.
    	/// 
    	public string FailureMessage { get; }
    
    	/// 
    	/// Validates a specific named options instance (or all when  is null).
    	/// 
    	/// The name of the options instance being validated.
    	/// The options instance.
    	/// The  result.
    	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 _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);
    			}
    		}
    	}
    }
    

    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 _factory;
    	private readonly OptionsCache _cache = new OptionsCache(); // Note: this is a private cache
    
    	/// 
    	/// Initializes a new instance with the specified options configurations.
    	/// 
    	/// The factory to use to create options.
    	public OptionsManager(IOptionsFactory factory)
    	{
    		_factory = factory;
    	}
    
    	/// 
    	/// The default configured  instance, equivalent to Get(Options.DefaultName).
    	/// 
    	public TOptions Value => Get(Options.DefaultName);
    
    	/// 
    	/// Returns a configured  instance with the given .
    	/// 
    	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 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 _cache;
    	private readonly IOptionsFactory _factory;
    	private readonly List _registrations = new List();
    	internal event Actionstring> _onChange;
    
    	/// 
    	/// Constructor.
    	/// 
    	/// The factory to use to create options.
    	/// The sources used to listen for changes to the options instance.
    	/// The cache used to store options.
    	public OptionsMonitor(IOptionsFactory factory, IEnumerable> 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);
    		}
    
    		// 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[] sourcesArray)
    		{
    			foreach (IOptionsChangeTokenSource source in sourcesArray)
    			{
    				RegisterSource(source);
    			}
    		}
    		else
    		{
    			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);
    		}
    	}
    
    	/// 
    	/// The present value of the options.
    	/// 
    	public TOptions CurrentValue
    	{
    		get => Get(Options.DefaultName);
    	}
    
    	/// 
    	/// Returns a configured  instance with the given .
    	/// 
    	public virtual TOptions Get(string name)
    	{
    		name = name ?? Options.DefaultName;
    		return _cache.GetOrAdd(name, () => _factory.Create(name));
    	}
    
    	/// 
    	/// Registers a listener to be called whenever  changes.
    	/// 
    	/// The action to be invoked when  has changed.
    	/// An  which should be disposed to stop listening for changes.
    	public IDisposable OnChange(Actionstring> listener)
    	{
    		var disposable = new ChangeTrackerDisposable(this, listener);
    		_onChange += disposable.OnChange;
    		return disposable;
    	}
    
    	/// 
    	/// Removes all change registration subscriptions.
    	/// 
    	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 Actionstring> _listener;
    		private readonly OptionsMonitor _monitor;
    
    		public ChangeTrackerDisposable(OptionsMonitor monitor, Actionstring> 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> 对象作为内存缓存,其中为了性能还再使用了 Lazy 方式。

    IOptionsFactory、OptionsFactory

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

    点击查看代码 OptionsFactory
    public class OptionsFactory<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
    	IOptionsFactory<TOptions>
    	where TOptions : class
    {
    	private readonly IConfigureOptions[] _setups;
    	private readonly IPostConfigureOptions[] _postConfigures;
    	private readonly IValidateOptions[] _validations;
    
    	/// 
    	/// Initializes a new instance with the specified options configurations.
    	/// 
    	/// The configuration actions to run.
    	/// The initialization actions to run.
    	public OptionsFactory(IEnumerable> setups, IEnumerable> postConfigures) : this(setups, postConfigures, validations: Array.Empty>())
    	{ }
    
    	/// 
    	/// Initializes a new instance with the specified options configurations.
    	/// 
    	/// The configuration actions to run.
    	/// The initialization actions to run.
    	/// The validations to run.
    	public OptionsFactory(IEnumerable> setups, IEnumerable> postConfigures, IEnumerable> 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();
    	}
    
    	/// 
    	/// Returns a configured  instance with the given .
    	/// 
    	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);
    		}
    
    		if (_validations != null)
    		{
    			var failures = new List<string>();
    			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;
    	}
    
    	/// 
    	/// Creates a new instance of options type
    	/// 
    	protected virtual TOptions CreateInstance(string name)
    	{
    		return Activator.CreateInstance();
    	}
    }
    

    以上就是 .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 - 缓存之内存缓存(上)

  • 相关阅读:
    接口调试工具概论
    关于 Oracle 中字段值为 NULL 时的排序顺序
    后端编写Swagger接口管理文档
    智能合约语言对比:Solidity | Vyper | Move | Rust
    数据结构与算法解题-20240421
    LeetCode 0236. 二叉树的最近公共祖先
    R语言几何图像可视化
    《nlp入门+实战:第九章:循环神经网络》
    Web性能测试模型小结
    核心函数--少儿编程
  • 原文地址:https://www.cnblogs.com/wewant/p/17111746.html