Nuget:以Microsoft.Extensins.Options開頭的nuget包
Github地址:https://github.com/dotnet/extensions/tree/master/src/Options
首先看下接口
IOptions依賴於服務的依賴注入
首先看下我們需要注冊的服務
public static IServiceCollection AddOptions(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>))); 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; }
接下來看下各個接口的具體實現:
一、OptionsManager類:
/// <summary> /// Implementation of <see cref="IOptions{TOptions}"/> and <see cref="IOptionsSnapshot{TOptions}"/>. /// </summary> /// <typeparam name="TOptions">Options type.</typeparam> public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new() { 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 { return 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; // Store the options in our instance cache return _cache.GetOrAdd(name, () => _factory.Create(name)); } }
該類實現IOptions<TOptions>, IOptionsSnapshot<TOptions>接口,當我們注入具體的選項時,依賴注入會解析到對應的OptionsManager<>實現。
OptionsCache選項的緩存
public class OptionsCache<TOptions> : IOptionsMonitorCache<TOptions> where TOptions : class { private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal); /// <summary> /// Clears all options instances from the cache. /// </summary> public void Clear() => _cache.Clear(); /// <summary> /// Gets a named options instance, or adds a new instance created with <paramref name="createOptions"/>. /// </summary> /// <param name="name">The name of the options instance.</param> /// <param name="createOptions">The func used to create the new instance.</param> /// <returns>The options instance.</returns> public virtual TOptions GetOrAdd(string name, Func<TOptions> createOptions) { if (createOptions == null) { throw new ArgumentNullException(nameof(createOptions)); } name = name ?? Options.DefaultName; return _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions)).Value; } /// <summary> /// Tries to adds a new option to the cache, will return false if the name already exists. /// </summary> /// <param name="name">The name of the options instance.</param> /// <param name="options">The options instance.</param> /// <returns>Whether anything was added.</returns> public virtual bool TryAdd(string name, TOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } name = name ?? Options.DefaultName; return _cache.TryAdd(name, new Lazy<TOptions>(() => options)); } /// <summary> /// Try to remove an options instance. /// </summary> /// <param name="name">The name of the options instance.</param> /// <returns>Whether anything was removed.</returns> public virtual bool TryRemove(string name) { name = name ?? Options.DefaultName; return _cache.TryRemove(name, out var ignored); } }
IOptionsFactory<TOptions>接口的實現OptionsFactory<TOptions>
/// <summary> /// Implementation of <see cref="IOptionsFactory{TOptions}"/>. /// </summary> /// <typeparam name="TOptions">The type of options being requested.</typeparam> public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new() { private readonly IEnumerable<IConfigureOptions<TOptions>> _setups; private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures; private readonly IEnumerable<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: null) { } /// <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) { _setups = setups; _postConfigures = postConfigures; _validations = validations; } /// <summary> /// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>. /// </summary> public TOptions Create(string name) { var options = new TOptions(); foreach (var setup in _setups) { if (setup is IConfigureNamedOptions<TOptions> namedSetup) { namedSetup.Configure(name, options); } else if (name == Options.DefaultName) { setup.Configure(options); } } foreach (var post in _postConfigures) { post.PostConfigure(name, options); } if (_validations != null) { var failures = new List<string>(); foreach (var validate in _validations) { var result = validate.Validate(name, options); if (result.Failed) { failures.AddRange(result.Failures); } } if (failures.Count > 0) { throw new OptionsValidationException(name, typeof(TOptions), failures); } } return options; } }
可以看到OptionsFactory的構造函數接收三個參數:IEnumerable<IConfigureOptions<TOptions>> 、IEnumerable<IPostConfigureOptions<TOptions>>、IEnumerable<IValidateOptions<TOptions>>
當依賴注入容器解析OptionsFactory<>實例時,會找到所有實現IConfigureOptions<TOptions>、IPostConfigureOptions<TOptions>、IValidateOptions<TOptions>的對象
再看下TOptions Create(string name)方法
先遍歷IEnumerable<IConfigureOptions<TOptions>>
如果對象實現了IConfigureNamedOptions<TOptions>接口,則調用Configure(name, options)方法,否則判斷name == Options.DefaultName是否為true,如果是true,則調用Configure(options)方法
再遍歷IEnumerable<IPostConfigureOptions<TOptions>>
直接調用PostConfigure(name, options)方法
再遍歷IEnumerable<IValidateOptions<TOptions>>方法,主要用於驗證結果是否正確,如果驗證失敗,則拋出異常。
以上是創建IOptions<TOptions>的一個流程
再來看下IOptionsMonitor<>接口,其實現為OptionsMonitor<TOptions>
OptionsMonitor構造函數有三個:IOptionsFactory<TOptions>、IEnumerable<IOptionsChangeTokenSource<TOptions>>、IOptionsMonitorCache<TOptions>
其中第一和第三個都是系統默認注冊好了
第二個參數,如果用戶已經注冊對應的服務,則遍歷該集合,監聽選項的變化
IOptionsMonitor可以監聽選項的變化而重新獲取值
再來看下一些擴展方法
/// <summary> /// Registers an action used to configure a particular type of options. /// Note: These are run before all <seealso cref="PostConfigure{TOptions}(IServiceCollection, Action{TOptions})"/>. /// </summary> /// <typeparam name="TOptions">The options type to be configured.</typeparam> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="configureOptions">The action used to configure the options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class => services.Configure(Options.Options.DefaultName, configureOptions); /// <summary> /// Registers an action used to configure a particular type of options. /// Note: These are run before all <seealso cref="PostConfigure{TOptions}(IServiceCollection, Action{TOptions})"/>. /// </summary> /// <typeparam name="TOptions">The options type to be 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="configureOptions">The action used to configure the options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> 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; } /// <summary> /// Registers an action used to configure all instances of a particular type of options. /// </summary> /// <typeparam name="TOptions">The options type to be configured.</typeparam> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="configureOptions">The action used to configure the options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection ConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class => services.Configure(name: null, configureOptions: configureOptions); /// <summary> /// Registers an action used to initialize a particular type of options. /// Note: These are run after all <seealso cref="Configure{TOptions}(IServiceCollection, Action{TOptions})"/>. /// </summary> /// <typeparam name="TOptions">The options type to be configured.</typeparam> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="configureOptions">The action used to configure the options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class => services.PostConfigure(Options.Options.DefaultName, configureOptions); /// <summary> /// Registers an action used to configure a particular type of options. /// Note: These are run after all <seealso cref="Configure{TOptions}(IServiceCollection, Action{TOptions})"/>. /// </summary> /// <typeparam name="TOptions">The options type to be configure.</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="configureOptions">The action used to configure the options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection PostConfigure<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<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(name, configureOptions)); return services; } /// <summary> /// Registers an action used to post configure all instances of a particular type of options. /// Note: These are run after all <seealso cref="Configure{TOptions}(IServiceCollection, Action{TOptions})"/>. /// </summary> /// <typeparam name="TOptions">The options type to be configured.</typeparam> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="configureOptions">The action used to configure the options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection PostConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class => services.PostConfigure(name: null, configureOptions: configureOptions); /// <summary> /// Registers a type that will have all of its I[Post]ConfigureOptions registered. /// </summary> /// <typeparam name="TConfigureOptions">The type that will configure options.</typeparam> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection ConfigureOptions<TConfigureOptions>(this IServiceCollection services) where TConfigureOptions : class => services.ConfigureOptions(typeof(TConfigureOptions)); private static bool IsAction(Type type) => (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Action<>)); private static IEnumerable<Type> FindIConfigureOptions(Type type) { var serviceTypes = type.GetTypeInfo().ImplementedInterfaces .Where(t => t.GetTypeInfo().IsGenericType && (t.GetGenericTypeDefinition() == typeof(IConfigureOptions<>) || t.GetGenericTypeDefinition() == typeof(IPostConfigureOptions<>))); if (!serviceTypes.Any()) { throw new InvalidOperationException( IsAction(type) ? Resources.Error_NoIConfigureOptionsAndAction : Resources.Error_NoIConfigureOptions); } return serviceTypes; } /// <summary> /// Registers a type that will have all of its I[Post]ConfigureOptions registered. /// </summary> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="configureType">The type that will configure options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection ConfigureOptions(this IServiceCollection services, Type configureType) { services.AddOptions(); var serviceTypes = FindIConfigureOptions(configureType); foreach (var serviceType in serviceTypes) { services.AddTransient(serviceType, configureType); } return services; } /// <summary> /// Registers an object that will have all of its I[Post]ConfigureOptions registered. /// </summary> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="configureInstance">The instance that will configure options.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection ConfigureOptions(this IServiceCollection services, object configureInstance) { services.AddOptions(); var serviceTypes = FindIConfigureOptions(configureInstance.GetType()); foreach (var serviceType in serviceTypes) { services.AddSingleton(serviceType, configureInstance); } return services; } /// <summary> /// Gets an options builder that forwards Configure calls for the same <typeparamref name="TOptions"/> to the underlying service collection. /// </summary> /// <typeparam name="TOptions">The options type to be configured.</typeparam> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <returns>The <see cref="OptionsBuilder{TOptions}"/> so that configure calls can be chained in it.</returns> public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services) where TOptions : class => services.AddOptions<TOptions>(Options.Options.DefaultName); /// <summary> /// Gets an options builder that forwards Configure calls for the same named <typeparamref name="TOptions"/> to the underlying service collection. /// </summary> /// <typeparam name="TOptions">The options type to be 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> /// <returns>The <see cref="OptionsBuilder{TOptions}"/> so that configure calls can be chained in it.</returns> 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); }