Options

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(x => x.Name = new Random().Next(10000).ToString());

var provider = services.BuildServiceProvider();

var opt1 = provider.GetRequiredService<IOptions>();
Console.WriteLine(opt1.Value.Name);

var opt2 = provider.GetRequiredService<IOptions>();
Console.WriteLine(opt2.Value.Name);

var opt3 = provider.GetRequiredService<IOptionsSnapshot>();
Console.WriteLine(opt3.Get(Options.DefaultName).Name);

var opt4 = provider.GetRequiredService<IOptionsSnapshot>();
Console.WriteLine(opt4.Get(Options.DefaultName).Name);

using var scope = provider.CreateScope();
var scopeProvider = scope.ServiceProvider;

var opt11 = scopeProvider.GetRequiredService<IOptions>();
Console.WriteLine(opt11.Value.Name);

var opt21 = scopeProvider.GetRequiredService<IOptions>();
Console.WriteLine(opt21.Value.Name);

var opt31 = scopeProvider.GetRequiredService<IOptionsSnapshot>();
Console.WriteLine(opt31.Get(Options.DefaultName).Name);

var opt41 = scopeProvider.GetRequiredService<IOptionsSnapshot>();
Console.WriteLine(opt41.Get(Options.DefaultName).Name);

Console.ReadKey();
}
}

public class MyOptions
{
public string Name { get; set; }
}

public interface IOptions
{
T Value { get; }
}

public interface IConfigureOptions where T : class
{
void Configure(T config);
}

public interface IConfigureNamedOptions : IConfigureOptions where T : class
{
void Configure(string? name, T options);
}

public interface IPostConfigureOptions where T : class
{
void PostConfigure(string? name, T options);
}

public interface IOptionsFactory where T : class
{
T Create(string name);
}

public interface IOptionsSnapshot where T : class
{
T Get(string? name);
}

public interface IOptionsMonitor where T : class
{
T CurrentValue { get; }
T Get(string? name);
IDisposable? OnChange(Action<T, string?> listener);
}

public interface IOptionsChangeTokenSource where T : class
{
IChangeToken GetChangeToken();
string? Name { get; }
}

public class ConfigureNamedOptions : IConfigureNamedOptions where T : class
{
public ConfigureNamedOptions(string? name, Action? action)
{
Name = name;
Action = action;
}

public string? Name { get; }

public Action? 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
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 : IPostConfigureOptions where T : class
{
public PostConfigureOptions(string? name, Action? action)
{
Name = name;
Action = action;
}

public string? Name { get; }

public Action? Action { get; }

public virtual void PostConfigure(string? name, T options)
{
if (Name == null || name == Name)
{
Action?.Invoke(options);
}
}
}

public class OptionsFactory : IOptionsFactory where T : class
{
private readonly IConfigureOptions[] _setups;
private readonly IPostConfigureOptions[] _postConfigures;

public OptionsFactory(IEnumerable<IConfigureOptions> setups, IEnumerable<IPostConfigureOptions> postConfigures)
{
_setups = setups as IConfigureOptions[] ?? new List<IConfigureOptions>(setups).ToArray();
_postConfigures = postConfigures as IPostConfigureOptions[] ?? new List<IPostConfigureOptions>(postConfigures).ToArray();
}

public T Create(string name)
{
T 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);
}
return options;
}

protected virtual T CreateInstance(string name)
{
return Activator.CreateInstance();
}
}

public class OptionsManager : IOptions, IOptionsSnapshot where T : class
{
private readonly IOptionsFactory _factory;
private Dictionary<string, T> _cache = new Dictionary<string, T>();

public OptionsManager(IOptionsFactory 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 : IOptions where T : class
{
private readonly IOptionsFactory _factory;
private volatile object? _syncObj;
private volatile T? _value;

public UnamedOptionsManager(IOptionsFactory 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 : IOptionsMonitor where T : class
{
private readonly IOptionsFactory _factory;
private readonly Dictionary<string, T> _cache = new Dictionary<string, T>();
internal event Action<T, string>? _onChange;

public OptionsMonitor(IOptionsFactory factory, IEnumerable<IOptionsChangeTokenSource> sources)
{
_factory = factory;

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

foreach (IOptionsChangeTokenSource 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 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 Configure(Action configureOptions)
{
Services.AddSingleton<IConfigureOptions>(new ConfigureNamedOptions(Name, configureOptions));
return this;
}

public virtual OptionsBuilder Configure(Action<T, TDep> configureOptions)
where TDep : class
{
Services.AddTransient<IConfigureOptions>(sp =>
new ConfigureNamedOptions<T, TDep>(Name, sp.GetRequiredService(), 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(this IServiceCollection services, Action configureOptions) where T : class
=> services.MyConfigure(Options.DefaultName, configureOptions);

public static IServiceCollection MyConfigure(this IServiceCollection services, string? name, Action configureOptions) where T : class
{
services.AddMyOptions();
services.AddSingleton<IConfigureOptions>(new ConfigureNamedOptions(name, configureOptions));
return services;
}

public static IServiceCollection MyConfigureAll(this IServiceCollection services, Action configureOptions) where T : class
=> services.MyConfigure(name: null, configureOptions: configureOptions);

public static IServiceCollection PostConfigure(this IServiceCollection services, Action configureOptions) where T : class
=> services.PostConfigure(Options.DefaultName, configureOptions);

public static IServiceCollection MyPostConfigure(this IServiceCollection services, string? name, Action configureOptions)
where T : class
{
services.AddMyOptions();
services.AddSingleton<IPostConfigureOptions>(new PostConfigureOptions(name, configureOptions));
return services;
}

public static IServiceCollection MyPostConfigureAll(this IServiceCollection services, Action 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 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 AddOptions(this IServiceCollection services) where TOptions : class
=> services.AddOptions(Options.DefaultName);

public static OptionsBuilder AddOptions(this IServiceCollection services, string? name)
where TOptions : class
{
services.AddMyOptions();
return new OptionsBuilder(services, name);
}
}
}

posted @ 2024-09-13 12:12  pojianbing  阅读(76)  评论(0)    收藏  举报