- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
.NET Core 选项系统的主要实现在 Microsoft.Extensions.Options 和 Microsoft.Extensions.Options.ConfigurationExtensions 两个 Nuget 包。对于一个框架的源码进行解读,我们可以从我们常用的框架中的类或方法入手,这些类或方法就是我们解读的入口.
从上面对选项系统的介绍中,大家也可以看出,日常对选项系统的使用涉及到的主要有 Configure 方法,有 IOptions 、IOptionsSnapshot 、IOptionMonitor 等接口.
首先看选项注册,也就是 Configure 方法,注册相关的方法都是扩展方法,上面也讲到 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 方法,用于直接通过委托对选项类进行配置.
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 是选项配置行为服务接口,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服务配置选项”的具体实现.
/// <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 方法有两个重载:
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;
}
}
我们除了可以对选项进行配置绑定之外,还可以对选项进行验证。验证规则是通过上面的第二个 AddOptions 方法返回的 OptionsBuilder 方法进行添加的.
验证规则配置有三种方式,最后其实都是通过 IValidateOptions 的实现类来完成。我们自己实现的自定义验证类就不用说了,最后我们会将其注入到容器中,而从上面的代码中可以看到,当我们通过委托的方式自定义验证规则的时候,它会被构建成一个 ValidateOptions 类对象,并注入到容器中作为一个服务.
ValidateOptions 是 IValidateOptions 的一个实现类,构造函数中接收委托,通过委托返回的 bool 结构判断验证是否通过.
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;
}
}
我们可以通过重载方法传入相应的验证失败提醒文本.
接下来看选项使用相关的内容,其中 IOptions 中的选项类一经创建一直保持不变,默认实现类 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 ,该类中有一个私有的 OptionsCache 属性,每次对选项类进行读取的时候,都是先尝试从缓存读取,如果没有才创建。而由于 IOptionsSnapshot 被注册为请求域生命周期,所以单次请求内相应对象不会销毁,缓存不会清空,会一直保持一个.
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 ,实现类中使用了从容器中注入的单例缓存 IOptionsMonitorCache 来保存选项类,并且通过相应的 IOptionsChangeTokenSource 注册了选项类绑定内容的监听,例如上面讲到的 ConfigurationChangeTokenSource ,在选项类配置内容改变的时候会触发事件,而在事件中会将缓存先情况并重新获取创建类,并且执行注册进来的额外的监听事件,可以看看下面的 InvokeChanged 方法.
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 对象销毁的时候能够将相应的事件释放,如果不包装多一层的话,委托只在方法作用域中,对象释放的时候是获取不到的.
OptionsCache 是 IOptionsMonitorCache 接口的的实现类,从上面可以看到 OptionsMonitor 和 OptionsSnapshot 都使用到了这个,OptionsSnapshot 通过内部创建的私有的缓存属性实现了请求域内选项类不变,而 OptionsMonitor 则通过它减少了每次都直接读取配置来源(如文件、数据库、配置中心api)的性能消耗,而是通过变更事件的方式进行更新。其实我们还可以在需要的时候注入IOptionsMonitorCache 服务自行对选项类进行更新.
OptionsCache 的具体实现比较简单,主要就是通过 ConcurrentDictionary<string, Lazy > 对象作为内存缓存,其中为了性能还再使用了 Lazy 方式.
OptionsFactory 类实现 IOptionsFactory 接口,是选项类的实际创建配置之处,其实就是将之前注册到容器中与当前相关的各种配置、验证的行为配置类注入进来,再通过放射创建对象之后,将选项类对象传进去,逐一对相应的行为进行调用,最后得到一个成型的选项类。这里选项类的创建方式很简单,这也是要求选项类要有无参构造函数的原因.
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 - 选项系统之选项验证 。
最后此篇关于ASP.NETCore-选项系统之源码介绍的文章就讲到这里了,如果你想了解更多关于ASP.NETCore-选项系统之源码介绍的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
晚上在 QQ 上看到昵称为“乱码”的好友回答了搜搜问问里一个问题: 在VBS中有办法定义字节数组么? 在VBS中有办法定义字节数组么?就是字节子类型数组(VarType是8209的那种)注意不是V
例如,员工管理应用程序可能包括一个EmPloyee 类。然后可以用这个类来创建和维护特定实例,比如Gonn和Sally。 根据预定义的类创建对象常称为类的实例化(class insta
在自然语言中,我们理解抽象的概念是,一个物体的一种大的描述,这种描述对某类物体来说是共有的特性。那么在PHP中也是一样的,我们把一个类进行抽象,可以指明类的一般行为,这个类应该是一个模板,它指示它的
DBA_2PC_PENDING Oracle会自动处理分布事务,保证分布事务的一致性,所有站点全部提交或全部回滚。一般情况下,处理过程在很短的时间内完成,根本无法察觉到。但是,如果在commit或
目录 计算过程 投影分量计算 假设你有一家理发店,已经记录了过去一年中所有顾客的头发长度和发型偏好的数据。现在你想从这些数据中提取一些主要的信息,比如顾客最常
Object.defineProperty函数会直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回此对象。 一、简单使用 const obj = {} Object.defineP
SPL官网 http://www.scudata.com.cn/ 介绍 业务逻辑经常包含较复杂的流程和计算,同时涉及数据库的读写。由于授权麻烦、影响数据库安全、无法迁移、技术要求高、编写困难等原因,很
SPL官网 http://www.scudata.com.cn/ 介绍 业务逻辑经常包含较复杂的流程和计算,同时涉及数据库的读写。由于授权麻烦、影响数据库安全、无法迁移、技术要求高、编写困难等原因,很
一 点睛 Thrift 是一歀基于 CS 架构的 RPC 框架,最初由 Facebook 研发,2008 年转入 Apache 组织。开发人员可以使用 Thrift 提供的 IDL(接口定义语言)来定
数据库应用程序与主应用程序分开存在,并存储数据集合。 每个数据库都使用一个或多个API来创建,访问,管理,搜索和复制其包含的数据。 数据库还使用非关系数据源,例如对象或文件。 然而,数据库证明是大数
介绍 Ant是一个 Apache 基金会下的跨平台的基于 Java 语言开发的构件工具。在我们详细了解 Apache Ant 之前, 让我们来讲解为什么构建工具是需要最先了解的。 构建工具的需求
我现在正在尝试学习ocaml,并希望从一个小程序开始,生成所有位组合: [“0”,“0”,“0”] [“0”,“0”,“1”] [“0”,“1”,“0”] ... 等等 我的想法是下面的代码: let
我正在做我的介绍 C 类(class)作业,我的任务是执行以下任务...... 为一个函数编写代码,该函数通过值接收两个参数(a 和 b)并通过引用具有另外两个参数(c 和 d)。所有参数都是双倍的。
我希望提供有关我网站内容的快速演示,以及如何在用户访问我的页面后立即以正确的方式使用它们。我希望使用顶部的弹出式窗口进行演示。 我的意思是小信息框,一个接一个地通知用户各个步骤。任何人都可以帮助我如何
与C、Java等语言一样,JavaScript中可以用&&、||、!三个逻辑判断符来对boolean值进行逻辑判断。与C、Java不同的是,JavaScript中逻辑与(&&
JavaScript中,==与===操作符均可用于判断两个值是否相等;不同之处在于,如果进行判断的两个值类型不一致,===操作符会直接返回false,而==操作符则会在类型转换后再进行判断。详细的判
JavaScript中,object转换为boolean的操作非常简单:所有的object转换成boolean后均为true;即使是new Boolean(false)这样的object在转换为bo
在android开发中,当不满足触发条件就按返回键的时候,就要对此进行检测。尤其是当前Activity需要往前一个Activity传送消息时。即Activity1跳转到Activity3如果采用的是
背景 当要求系统启动一个应用程序时,系统会先查找当前命令是否是内部命令,若不是,则在当前目录下查找,如果仍没有找到,则在系统变量 Path 指定的路径去查找。JDK(Java Developmen
概述 想做一个微信的公众平台,阅读了微信官方给的网址接入的示例代码,发现有个问题好像一直都是半知半解的,就是在类里边直接使用$_GET。仔细查了下关于这方面的知识,发现PHP中这部分的基础知识掌握
我是一名优秀的程序员,十分优秀!