zoukankan      html  css  js  c++  java
  • [Asp.net 5] Options-配置文件之后的配置

    今天要讲的是OptionsModel解决方案,整个解决方案中也只有Microsoft.Framework.OptionsModel一个工程。按照表面文字OptionsModel应该翻译成选项模型,但是这个词没表现它实际的含义,我觉得称呼它为配置选项好些,不过为了原滋原味,我们还是用英文的:Configuration和OptionsModel表示它们。

    什么是OptionsModel

    在之前的配置文件一节([Asp.net 5] Configuration-新一代的配置文件)我们介绍过配置文件最后生成的是IConfiguration对象,但是IConfiguration可能包含很多信息混杂在一起。比如设置日志的等级Level、连接数据库的字符串connectionstring等。我们有时候不希望直接使用IConfiguration对象(比如对象中的配置项会变动),而是抽取一部分有用的信息以及一些其他值构成的具体的实体对象,那么这个从配置文件之后抽取的配置,就可以叫做OptionsModel。

    简而言之:IConfiguration是配置文件的抽象;OptionsModel配置文件之后的配置,是直接对于系统的配置项。

    OptionsModel的特点与实现

    这类对象的特点很明显一般都是只包含一些属性,并无具体的内部逻辑;但是这种配置可能不是仅有一个:比如有数据库的connectionModel,而日志可能有LogOption。而且让这些OptionsModel实现统一的接口也不是现实的,也没有实际意义。那么如何实现Configuration到OptionsModel的转换呢?

    答案很简单:Binder(神奇的Binder)。

            [Fact]
            public void CanReadComplexProperties()
            {
                var dic = new Dictionary<string, string>
                {
                    {"Integer", "-2"},
                    {"Boolean", "TRUe"},
                    {"Nested:Integer", "11"}
                };
                var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
                var config = builder.Build();
                var options = ConfigurationBinder.Bind<ComplexOptions>(config);
                Assert.True(options.Boolean);
                Assert.Equal(-2, options.Integer);
                Assert.Equal(11, options.Nested.Integer);
            }

    如何DI?

    很多工程使用了DependencyInjection(依赖注入),而OptionsModel是比较基础的配置,几乎可以肯定内部用到OptionsModel的类会被注入,并且使用类型注册的方式注入。那么DependencyInjection内部肯定会递归到OptionsModel类,所以OptionsModel类也必须要进行注入,那么如何实现?

    答曰:使用实例(Instance)直接注入到该类类型。[services.AddInstance(serviceType, type.Assembly.CreateInstance(type));]

    如果我对于多个OptionsModel注册,但是我可以通过命名方式注入,还可以进行排序,那又该如何实现?

    答曰:Microsoft.Framework.OptionsModel。

    Microsoft.Framework.OptionsModel

    类文件分类

    在Microsoft.Framework.OptionsModel中,使用泛型的方式进行注入,之后以泛型的方式获取注入;但是注入的类和获取的泛型类确是不完全一致的。

    • 注入的类和接口:IConfigureOptions<in TOptions>、ConfigureOptions<TOptions>、ConfigureFromConfigurationOptions<TOptions>
    • 获取注入的类和接口:IOptions<out TOptions>、OptionsManager<TOptions>
    • 注册的扩展类: OptionsServiceCollectionExtensions
    • 其他:OptionsConstants

    注入类和接口

     IConfigureOptions<in TOptions>、ConfigureOptions<TOptions>、ConfigureFromConfigurationOptions<TOptions>这几个类和接口的关系为:

        public interface IConfigureOptions<in TOptions>
        {
            int Order { get; }
            void Configure(TOptions options, string name = "");
        }
    IConfigureOptions
        public class ConfigureOptions<TOptions> : IConfigureOptions<TOptions>
        {
            public ConfigureOptions([NotNull]Action<TOptions> action)
            {
                Action = action;
            }
    
            public Action<TOptions> Action { get; private set; }
    
            public string Name { get; set; } = "";
            public virtual int Order { get; set; } = OptionsConstants.DefaultOrder;
    
            public virtual void Configure([NotNull]TOptions options, string name = "")
            {
                // Always invoke the action if no Name was specified, otherwise only if it was the requested name
                if (string.IsNullOrEmpty(Name) || string.Equals(name, Name, StringComparison.OrdinalIgnoreCase))
                {
                    Action.Invoke(options);
                }
            }
        }
    ConfigureOptions
        public class ConfigureFromConfigurationOptions<TOptions> : ConfigureOptions<TOptions>
        {
            public ConfigureFromConfigurationOptions([NotNull] IConfiguration config)
                : base(options => ConfigurationBinder.Bind(options, config))
            {
            }
        }
    ConfigureFromConfigurationOptions

    注入类和接口的注入

    在OptionsServiceCollectionExtensions中提供了三种注册方式(实际是七个方法和重载):

    • 直接注册实现 IConfigureOptions<in TOptions>类型的数据。(假设实现类的类型为OptionsDev,则实际注册[services.AddTransient(IConfigureOptions<T>,OptionsDev)])
    • 提供Action<TOptions>进行注册。(创建实例instanceA=new ConfigureOptions<TOptions>(setupAction),之后注册[services.AddInstance(IConfigureOptions<T>,configureInstance)])
    • 提供IConfiguration进行注册。(创建实例instanceA=new ConfigureFromConfigurationOptions<TOptions>(setupAction),之后注册[services.AddInstance(IConfigureOptions<T>, configureInstance)])
        public static class OptionsServiceCollectionExtensions
        {
            public static IServiceCollection AddOptions([NotNull]this IServiceCollection services)
            {
                services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
                return services;
            }
    
            private static bool IsAction(Type type)
            {
                return (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<>));
                if (!serviceTypes.Any())
                {
                    string error = "TODO: No IConfigureOptions<> found.";
                    if (IsAction(type))
                    {
                        error += " did you mean Configure(Action<T>)";
                    }
                    throw new InvalidOperationException(error);
                }
                return serviceTypes;
            }
    
            public static IServiceCollection ConfigureOptions([NotNull]this IServiceCollection services, Type configureType)
            {
                var serviceTypes = FindIConfigureOptions(configureType);
                foreach (var serviceType in serviceTypes)
                {
                    services.AddTransient(serviceType, configureType);
                }
                return services;
            }
    
            public static IServiceCollection ConfigureOptions<TSetup>([NotNull]this IServiceCollection services)
            {
                return services.ConfigureOptions(typeof(TSetup));
            }
    
            public static IServiceCollection ConfigureOptions([NotNull]this IServiceCollection services, [NotNull]object configureInstance)
            {
                var serviceTypes = FindIConfigureOptions(configureInstance.GetType());
                foreach (var serviceType in serviceTypes)
                {
                    services.AddInstance(serviceType, configureInstance);
                }
                return services;
            }
    
            public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
                [NotNull] Action<TOptions> setupAction,
                string optionsName)
            {
                return services.Configure(setupAction, OptionsConstants.DefaultOrder, optionsName);
            }
    
            public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
                [NotNull] Action<TOptions> setupAction,
                int order = OptionsConstants.DefaultOrder,
                string optionsName = "")
            {
                services.ConfigureOptions(new ConfigureOptions<TOptions>(setupAction)
                {
                    Name = optionsName,
                    Order = order
                });
                return services;
            }
    
            public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
                [NotNull] IConfiguration config, string optionsName)
            {
                return services.Configure<TOptions>(config, OptionsConstants.ConfigurationOrder, optionsName);
            }
    
            public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
                [NotNull] IConfiguration config,
                int order = OptionsConstants.ConfigurationOrder, 
                string optionsName = "")
            {
                services.ConfigureOptions(new ConfigureFromConfigurationOptions<TOptions>(config)
                {
                    Name = optionsName,
                    Order = order
                });
                return services;
            }
        }
    OptionsServiceCollectionExtensions

    我们可以看到OptionsServiceCollectionExtensions内部使用的是泛型注册,不同的方法只是注册的方式不一样,最终结果注册的都是IConfigureOptions<T>对象。

    获取注入的类和接口
    OptionsServiceCollectionExtensions中获取方式的注册:

    • services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));

    我们获取的时候,IOptions<T>,那么获取的一定是OptionsManager<T>对象,那么我们看下相关的实现代码:

        public interface IOptions<out TOptions> where TOptions : class,new()
        {
            TOptions Options { get; }
            TOptions GetNamedOptions(string name);
        }
    IOptions
        public class OptionsManager<TOptions> : IOptions<TOptions> where TOptions : class,new()
        {
            private object _mapLock = new object();
            private Dictionary<string, TOptions> _namedOptions = new Dictionary<string, TOptions>(StringComparer.OrdinalIgnoreCase);
            private IEnumerable<IConfigureOptions<TOptions>> _setups;
    
            public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> setups)
            {
                _setups = setups;
            }
    
            public virtual TOptions GetNamedOptions([NotNull] string name)
            {
                if (!_namedOptions.ContainsKey(name))
                {
                    lock (_mapLock)
                    {
                        if (!_namedOptions.ContainsKey(name))
                        {
                            _namedOptions[name] = Configure(name);
                        }
                    }
                }
                return _namedOptions[name];
            }
    
            public virtual TOptions Configure(string optionsName = "")
            {
                return _setups == null 
                    ? new TOptions() 
                    : _setups.OrderBy(setup => setup.Order)
                             .Aggregate(new TOptions(),
                                        (options, setup) =>
                                        {
                                            setup.Configure(options, optionsName);
                                            return options;
                                        });
            }
    
            public virtual TOptions Options
            {
                get
                {
                    return GetNamedOptions("");
                }
            }
        }
    OptionsManager

    由于OptionsManager是使用类类型进行注入的,所以一定会调用OptionsManager类的构造函数,但是构造函数中包含参数(类型为IEnumerable<IConfigureOptions<T>>),那么系统就会递归获取其参数的注入方式。那么我们就会获取到所有IConfigureOptions<T>的注入项。之后我们就可以根据我们获取到的IConfigureOptions<T>类型,进行相关的排序,获取实例。

    获取IEnumerable<T>的注入方式,请详细见( [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)

    OptionsManager详解

    对于OptionsManager类中_setups是已经获取到的IConfigureOptions<T>集合。那么我们获取Options/GetNamedOptions([NotNull] string name)时候,实际上内部在第一次会执行Configure方法

            public virtual TOptions Configure(string optionsName = "")
            {
                return _setups == null 
                    ? new TOptions() 
                    : _setups.OrderBy(setup => setup.Order)
                             .Aggregate(new TOptions(),
                                        (options, setup) =>
                                        {
                                            setup.Configure(options, optionsName);
                                            return options;
                                        });
            }

    该方法首先判断setups是否有值。如果没值创建新的;如果有值,则排序后,进行函数聚合。

     Aggregate聚合函数:该函数包含俩个参数初始值T A,返回为T类型的Func(T,item,T)。系统会便利内部的所有item,之后使用初始值A,以及当前item进行函数计算,之后返回A‘,然后将A’赋值A,进行下一次循环,最后得到的A进行返回。

    测试用例

        public class FakeOptions
        {
            public FakeOptions()
            {
                Message = "";
            }
    
            public string Message { get; set; }
        }
        public class FakeOptionsSetupA : ConfigureOptions<FakeOptions>
        {
            public FakeOptionsSetupA() : base(o => o.Message += "A")
            {
                Order = -1;
            }
        }
    
        public class FakeOptionsSetupB : ConfigureOptions<FakeOptions>
        {
            public FakeOptionsSetupB() : base(o => o.Message += "B")
            {
                Order = 10;
            }
        }
    
        public class FakeOptionsSetupC : ConfigureOptions<FakeOptions>
        {
            public FakeOptionsSetupC() : base(o => o.Message += "C")
            {
                Order = 1000;
            }
        }
    测试基础类
            public void SetupCallsSortedInOrder()
            {
                var services = new ServiceCollection().AddOptions();
                var dic = new Dictionary<string, string>
                {
                    {"Message", "!"},
                };
                var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
                var config = builder.Build();
                services.Configure<FakeOptions>(o => o.Message += "Igetstomped", -100000);
                services.Configure<FakeOptions>(config);
                services.Configure<FakeOptions>(o => o.Message += "a", -100);
                services.ConfigureOptions<FakeOptionsSetupC>();
                services.ConfigureOptions(new FakeOptionsSetupB());
                services.ConfigureOptions(typeof(FakeOptionsSetupA));
                services.Configure<FakeOptions>(o => o.Message += "z", 10000);
    
                var service = services.BuildServiceProvider().GetService<IOptions<FakeOptions>>();
                Assert.NotNull(service);
                var options = service.Options;
                Assert.NotNull(options);
                Assert.Equal("!aABCz", options.Message);
            }
    public void NamedSetupDoNotCollideWithEachOther()
            {
                var services = new ServiceCollection().AddOptions();
                var dic = new Dictionary<string, string>
                {
                    {"Message", "!"},
                };
                var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
                var config = builder.Build();
    
                services.ConfigureOptions(new FakeOptionsSetupB { Name = "2" });
                services.Configure<FakeOptions>(o => o.Message += "Z", 10000, "2");
    
                services.ConfigureOptions(new FakeOptionsSetupB { Name = "3" });
                services.Configure<FakeOptions>(config, "3");
                services.Configure<FakeOptions>(o => o.Message += "z", 10000, "3");
    
                var service = services.BuildServiceProvider().GetService<IOptions<FakeOptions>>();
                Assert.NotNull(service);
                var options = service.Options;
                Assert.NotNull(options);
                Assert.Equal("", options.Message);
    
                var options2 = service.GetNamedOptions("2");
                Assert.NotNull(options2);
                Assert.Equal("BZ", options2.Message);
    
                var options3 = service.GetNamedOptions("3");
                Assert.NotNull(options3);
                Assert.Equal("!Bz", options3.Message);
    
            }
  • 相关阅读:
    linux 文件系统解析及相关命令
    2015暑假佛山移动实习个人总结——大三学生
    java 自动装箱自动拆箱
    java HashMap那点事
    STL学习笔记— —无序容器(Unordered Container)
    GNU C库「glibc」getaddrinfo 发现重大漏洞
    分布式缓存Memcache和Redis
    让你的动画不再生硬 Android插值器Interpolator使用秘籍
    安卓数据解析之 fastjson 的解析以及Gson解析
    (转)Unity3d使用心得(2):Unity3d 动态下载动画资源——AnimationClip 的使用
  • 原文地址:https://www.cnblogs.com/watermoon2/p/4561158.html
Copyright © 2011-2022 走看看