using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace ConsoleApp1
{
internal class Program
{
/*
* 1. IOptions单例,获取的值是同一个实例
* 2. IOptionsSnapshot scope, 每次请求获取的值是不同的实例,但是在生命周期内是同一个实例
* 3. IOptionsMonitor单例,获取的值是同一个实例,当配置发生变化时,会更新实例
*
* 4. Configure<>, ConfigureOptions<>, AddOptions<> 三者的区别:
* 4.1 Configure<> 使用ConfigureNamedOptions配置选项
* 4.2 ConfigureOptions<> 使用自定义IConfigureNamedOptions
* 4.3 AddOptions<>, 使用ConfigureNamedOptions<T, Dep>配置选项,使其可以依赖注入
*/
static async Task Main(string[] args)
{
var services = new ServiceCollection();
services.MyConfigure<MyOptions>(x => x.Name = new Random().Next(10000).ToString());
var provider = services.BuildServiceProvider();
var opt1 = provider.GetRequiredService<IOptions<MyOptions>>();
Console.WriteLine(opt1.Value.Name);
var opt2 = provider.GetRequiredService<IOptions<MyOptions>>();
Console.WriteLine(opt2.Value.Name);
var opt3 = provider.GetRequiredService<IOptionsSnapshot<MyOptions>>();
Console.WriteLine(opt3.Get(Options.DefaultName).Name);
var opt4 = provider.GetRequiredService<IOptionsSnapshot<MyOptions>>();
Console.WriteLine(opt4.Get(Options.DefaultName).Name);
using var scope = provider.CreateScope();
var scopeProvider = scope.ServiceProvider;
var opt11 = scopeProvider.GetRequiredService<IOptions<MyOptions>>();
Console.WriteLine(opt11.Value.Name);
var opt21 = scopeProvider.GetRequiredService<IOptions<MyOptions>>();
Console.WriteLine(opt21.Value.Name);
var opt31 = scopeProvider.GetRequiredService<IOptionsSnapshot<MyOptions>>();
Console.WriteLine(opt31.Get(Options.DefaultName).Name);
var opt41 = scopeProvider.GetRequiredService<IOptionsSnapshot<MyOptions>>();
Console.WriteLine(opt41.Get(Options.DefaultName).Name);
Console.ReadKey();
}
}
public class MyOptions
{
public string Name { get; set; }
}
public interface IOptions<T>
{
T Value { get; }
}
public interface IConfigureOptions<T> where T : class
{
void Configure(T config);
}
public interface IConfigureNamedOptions<T> : IConfigureOptions<T> where T : class
{
void Configure(string? name, T options);
}
public interface IPostConfigureOptions<T> where T : class
{
void PostConfigure(string? name, T options);
}
public interface IOptionsFactory<T> where T : class
{
T Create(string name);
}
public interface IOptionsSnapshot<T> where T : class
{
T Get(string? name);
}
public interface IOptionsMonitor<T> where T : class
{
T CurrentValue { get; }
T Get(string? name);
IDisposable? OnChange(Action<T, string?> listener);
}
public interface IOptionsChangeTokenSource<T> where T : class
{
IChangeToken GetChangeToken();
string? Name { get; }
}
public class ConfigureNamedOptions<T> : IConfigureNamedOptions<T> where T : class
{
public ConfigureNamedOptions(string? name, Action<T>? action)
{
Name = name;
Action = action;
}
public string? Name { get; }
public Action<T>? Action { get; }
public virtual void Configure(string? name, T options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
Action?.Invoke(options);
}
}
public void Configure(T options) => Configure(Options.DefaultName, options);
}
public class ConfigureNamedOptions<TOptions, TDep> : IConfigureNamedOptions<TOptions>
where TOptions : class
where TDep : class
{
public ConfigureNamedOptions(string? name, TDep dependency, Action<TOptions, TDep>? action)
{
Name = name;
Action = action;
Dependency = dependency;
}
public string? Name { get; }
public Action<TOptions, TDep>? Action { get; }
public TDep Dependency { get; }
public virtual void Configure(string? name, TOptions options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
Action?.Invoke(options, Dependency);
}
}
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}
public class PostConfigureOptions<T> : IPostConfigureOptions<T> where T : class
{
public PostConfigureOptions(string? name, Action<T>? action)
{
Name = name;
Action = action;
}
public string? Name { get; }
public Action<T>? Action { get; }
public virtual void PostConfigure(string? name, T options)
{
if (Name == null || name == Name)
{
Action?.Invoke(options);
}
}
}
public class OptionsFactory<T> : IOptionsFactory<T> where T : class
{
private readonly IConfigureOptions<T>[] _setups;
private readonly IPostConfigureOptions<T>[] _postConfigures;
public OptionsFactory(IEnumerable<IConfigureOptions<T>> setups, IEnumerable<IPostConfigureOptions<T>> postConfigures)
{
_setups = setups as IConfigureOptions<T>[] ?? new List<IConfigureOptions<T>>(setups).ToArray();
_postConfigures = postConfigures as IPostConfigureOptions<T>[] ?? new List<IPostConfigureOptions<T>>(postConfigures).ToArray();
}
public T Create(string name)
{
T options = CreateInstance(name);
foreach (IConfigureOptions<T> setup in _setups)
{
if (setup is IConfigureNamedOptions<T> namedSetup)
{
namedSetup.Configure(name, options);
}
else if (name == Options.DefaultName)
{
setup.Configure(options);
}
}
foreach (IPostConfigureOptions<T> post in _postConfigures)
{
post.PostConfigure(name, options);
}
return options;
}
protected virtual T CreateInstance(string name)
{
return Activator.CreateInstance<T>();
}
}
public class OptionsManager<T> : IOptions<T>, IOptionsSnapshot<T> where T : class
{
private readonly IOptionsFactory<T> _factory;
private Dictionary<string, T> _cache = new Dictionary<string, T>();
public OptionsManager(IOptionsFactory<T> factory)
{
_factory = factory;
}
public T Value => Get(Options.DefaultName);
public T Get(string? name)
{
name = name ?? Options.DefaultName;
if (!_cache.TryGetValue(name, out var options))
{
options = _factory.Create(name);
_cache.Add(name, options);
}
return options;
}
}
public class UnamedOptionsManager<T> : IOptions<T> where T : class
{
private readonly IOptionsFactory<T> _factory;
private volatile object? _syncObj;
private volatile T? _value;
public UnamedOptionsManager(IOptionsFactory<T> factory)
{
_factory = factory;
}
public T Value
{
get
{
if (_value is T value) // _value is not null
{
return value;
}
lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
{
return _value ??= _factory.Create(Options.DefaultName);
}
}
}
}
public class OptionsMonitor<T> : IOptionsMonitor<T> where T : class
{
private readonly IOptionsFactory<T> _factory;
private readonly Dictionary<string, T> _cache = new Dictionary<string, T>();
internal event Action<T, string>? _onChange;
public OptionsMonitor(IOptionsFactory<T> factory, IEnumerable<IOptionsChangeTokenSource<T>> sources)
{
_factory = factory;
void RegisterSource(IOptionsChangeTokenSource<T> source)
{
IDisposable registration = ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name);
}
foreach (IOptionsChangeTokenSource<T> source in sources)
{
RegisterSource(source);
}
}
private void InvokeChanged(string? name)
{
name = name ?? Options.DefaultName;
_cache.Remove(name);
T options = Get(name);
if (_onChange != null)
{
_onChange.Invoke(options, name);
}
}
public T CurrentValue
{
get => Get(Options.DefaultName);
}
public T Get(string? name)
{
name = name ?? Options.DefaultName;
if (!_cache.TryGetValue(name, out var options))
{
options = _factory.Create(name);
_cache.Add(name, options);
}
return options;
}
public IDisposable? OnChange(Action<T, string?> listener)
{
throw new NotImplementedException();
}
}
public class OptionsBuilder<T> where T : class
{
public IServiceCollection Services { get; set; }
public string Name { get; }
public OptionsBuilder(IServiceCollection services, string name)
{
this.Services = services;
Name = name ?? Options.DefaultName;
}
public virtual OptionsBuilder<T> Configure(Action<T> configureOptions)
{
Services.AddSingleton<IConfigureOptions<T>>(new ConfigureNamedOptions<T>(Name, configureOptions));
return this;
}
public virtual OptionsBuilder<T> Configure<TDep>(Action<T, TDep> configureOptions)
where TDep : class
{
Services.AddTransient<IConfigureOptions<T>>(sp =>
new ConfigureNamedOptions<T, TDep>(Name, sp.GetRequiredService<TDep>(), configureOptions));
return this;
}
}
public static class OptionsServiceCollectionExtensions
{
public static IServiceCollection AddMyOptions(this IServiceCollection services)
{
services.AddSingleton(typeof(IOptions<>), typeof(UnamedOptionsManager<>));
services.AddSingleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>));
services.AddScoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>));
services.AddTransient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>));
return services;
}
public static IServiceCollection MyConfigure<T>(this IServiceCollection services, Action<T> configureOptions) where T : class
=> services.MyConfigure(Options.DefaultName, configureOptions);
public static IServiceCollection MyConfigure<T>(this IServiceCollection services, string? name, Action<T> configureOptions) where T : class
{
services.AddMyOptions();
services.AddSingleton<IConfigureOptions<T>>(new ConfigureNamedOptions<T>(name, configureOptions));
return services;
}
public static IServiceCollection MyConfigureAll<T>(this IServiceCollection services, Action<T> configureOptions) where T : class
=> services.MyConfigure(name: null, configureOptions: configureOptions);
public static IServiceCollection PostConfigure<T>(this IServiceCollection services, Action<T> configureOptions) where T : class
=> services.PostConfigure(Options.DefaultName, configureOptions);
public static IServiceCollection MyPostConfigure<T>(this IServiceCollection services, string? name, Action<T> configureOptions)
where T : class
{
services.AddMyOptions();
services.AddSingleton<IPostConfigureOptions<T>>(new PostConfigureOptions<T>(name, configureOptions));
return services;
}
public static IServiceCollection MyPostConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
=> services.PostConfigure(name: null, configureOptions: configureOptions);
public static IServiceCollection ConfigureOptions(this IServiceCollection services, Type configureType)
{
services.AddMyOptions();
bool added = false;
foreach (Type serviceType in FindConfigurationServices(configureType))
{
services.AddTransient(serviceType, configureType);
added = true;
}
if (!added)
{
//ThrowNoConfigServices(configureType);
}
return services;
}
private static IEnumerable<Type> FindConfigurationServices(Type type)
{
foreach (Type t in GetInterfacesOnType(type))
{
if (t.IsGenericType)
{
Type gtd = t.GetGenericTypeDefinition();
if (gtd == typeof(IConfigureOptions<>) ||
gtd == typeof(IPostConfigureOptions<>))
{
yield return t;
}
}
}
// Extracted the suppression to a local function as trimmer currently doesn't handle suppressions
// on iterator methods correctly.
static Type[] GetInterfacesOnType(Type t)
=> t.GetInterfaces();
}
public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services) where TOptions : class
=> services.AddOptions<TOptions>(Options.DefaultName);
public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services, string? name)
where TOptions : class
{
services.AddMyOptions();
return new OptionsBuilder<TOptions>(services, name);
}
}
}