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<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);
        }
    }
}
posted @ 2024-09-13 12:12  pojianbing  阅读(23)  评论(0编辑  收藏  举报