zoukankan      html  css  js  c++  java
  • ASP.NET Core 2.1 源码学习之 Options[1]:Configure

    配置的本质就是字符串的键值对,但是对于面向对象语言来说,能使用强类型的配置是何等的爽哉!

    目录

    1. ASP.NET Core 配置系统
    2. 强类型的 Options
    3. Configure 方法
    4. ConfigureNamedOptions

    ASP.NET Core 配置系统

    在ASP.NET 4.X中,通常将配置存储在 web.config 中,使用静态帮助类来获取这些配置,而对 web.cofng 中进行任何修改时,则会导致应用程序池的回收,这种实现方式并不是很友好。

    因此,在ASP.NET Core中,对配置系统进行了重写,仍然使用的是基本的键值对,但是它们可以从多种格式的配置源中来获取,比如:命令行、环境变量、XML文件、JSON文件等等,你也可以编写自定义的配置源提供程序。

    通常在Stratup类的构造函数中对配置源进行设置:

    public Startup(IHostingEnvironment env)  
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }
    
    public IConfigurationRoot Configuration { get; }  
    

    首先创建了一个ConfigurationBuilder,然后设置配置源,最终生成的Configuration是所有配置源中的键值对组合。

    你可以直接在程序中使用IConfigurationRoot来读取配置,但是建议你使用强类型的Options,这样在你想获取某个配置时,只需要注入对应的Options,而不是获取整个配置。

    强类型的 Options

    Options is a framework for accessing and configuring POCO settings.

    简单来说,Options 就是将一个 POCO 的配置类,通过在Startup类中注册到容器中,在后续使用的时候使用构造函数注入来获取到POCO对象。我们将这种编程模式称为Options模式

    首先定义一个 Options

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

    然后我们在对应的appsettings.json中添加如下片段:

    {
      "MyOptions": {
        "DefaultValue" : "first"
      }
    }
    

    Startup中的ConfigureServices方法中,进行服务的注册:

    public void ConfigureServices(IServiceCollection services)  
    {
        services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
    }
    

    最后,便在控制器中注入IOptions<MyOptions>,通过其Value属性对MyOptions进行访问:

    [Route("api/[controller]")]
    public class ValuesController : Controller  
    {
        private readonly MyOptions _options;
        public ValuesController(IOptions<MyOptions> options)
        {
            _options = options.Value;
        }
    
        [HttpGet]
        public string Get()
        {
            return _options.DefaultValue;
        }
    }
    

    Configure 方法

    Options框架为我们提供了一系统的IServiceCollection的扩展方法,方便我们的使用。

    直接使用Action配置

    // 最简单的注册方式
    services.Configure<MyOptions>(o => o.DefaultValue = true);
    
    // 指定具体名称
    services.Configure<MyOptions>("my", o => o.DefaultValue = true);
    
    // 配置所有实例
    services.ConfigureAll<MyOptions>(o => o.DefaultValue = true);
    

    通过配置文件进行配置

    // 使用配置文件来注册实例
    services.Configure<MyOptions>(Configuration.GetSection("Sign"));
    
    // 指定具体名称
    services.Configure<MyOptions>("my", Configuration.GetSection("Sign"));
    
    // 配置所有实例
    services.ConfigureAll<MyOptions>(Configuration.GetSection("Sign"));
    

    PostConfigure方法

    PostConfigure 方法在 Configure 方法之后执行,是2.0中新增加的。

    services.PostConfigure<MyOptions>(o => o.DefaultValue = true);
    services.PostConfigure<MyOptions>("smyign", o => o.DefaultValue = true);
    services.PostConfigureAll<MyOptions>(o => o.DefaultValue = true);
    

    源码解析

    首先看一下IConfigureOptions接口:

    public interface IConfigureOptions<in TOptions> where TOptions : class
    {
        void Configure(TOptions options);
    }
    

    Configure扩展方法中便是为IConfigureOptions<>注册了一个单例ConfigureNamedOptions<>

    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
        where TOptions : class
    {
        services.AddOptions();
        services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
        return services;
    }
    

    而不指定nameConfigureConfigureAll方法,都只是一种简写形式,使用默认的DefaultName

    public static class Options
    {
        public static readonly string DefaultName = string.Empty;
    }
    
    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class 
    => services.Configure(Options.Options.DefaultName, configureOptions);
    
    public static IServiceCollection ConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
    => services.Configure(name: null, configureOptions: configureOptions);
    

    如上,Configure方法其实就是为IConfigureOptions<>注册了一个单例ConfigureNamedOptions<>

    再看一下使用IConfiguration进行配置的扩展方法:

    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config)
        where TOptions : class
    {
        services.AddOptions();
        services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
        return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config));
    }
    

    可以看到,注册的实例变成了NamedConfigureFromConfigurationOptions,而其本质依然是调用ConfigureNamedOptions,只不过Action的方法体变成了ConfigurationBinder.Bind()

    public class NamedConfigureFromConfigurationOptions<TOptions> : ConfigureNamedOptions<TOptions>
        where TOptions : class
    {
        public NamedConfigureFromConfigurationOptions(string name, IConfiguration config)
            : base(name, options => ConfigurationBinder.Bind(config, options))
        {
            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }
        }
    }
    

    在上面的Configure方法中,都调用了AddOptions,我们来看一下:

    public static IServiceCollection AddOptions(this IServiceCollection 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;
    }
    

    如上便是Options系统的几个核心对象了,后续章节再来一一介绍。

    大家或许会有点疑问:“ IConfigureOptions 中的 Configure 方法是在什么时候调用的呢 ”,这个且看下章分解。

    ConfigureNamedOptions

    ConfigureNamedOptions 实现了 IConfigureNamedOptions,而 IConfigureNamedOptions 则是对 IConfigureOptions 的一个扩展,添加了Name参数,这样,我们便可以为同一个 Options 类型注册多个独立的实例,在某些场景下则是非常有用的。有关Name的使用,则会在 第三章 来讲。

    public interface IConfigureNamedOptions<in TOptions> : IConfigureOptions<TOptions> where TOptions : class
    {
        void Configure(string name, TOptions options);
    }
    

    再看一下 ConfigureNamedOptions 的源码:

    public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions>, IConfigureOptions<TOptions> where TOptions : class
    {
        public ConfigureNamedOptions(string name, Action<TOptions> action)
        {
            Name = name;
            Action = action;
        }
    
        public string Name { get; }
    
        public Action<TOptions> Action { get; }
    
        public virtual void Configure(string name, TOptions options)
        {
            if (Name == null || name == Name)
            {
                Action?.Invoke(options);
            }
        }
    
        public void Configure(TOptions options) => Configure(Options.DefaultName, options);
    }
    

    ConfigureNamedOptions 本质上就是把我们注册的Action包装成统一的Configure方法,以方便后续创建Options实例时,进行初始化。

    总结

    本文描述了在 .NET Core 配置系统中Options的配置及原理,在 下一章 来讲一下IOptions的使用。

  • 相关阅读:
    NOIP2016 愤怒的小鸟
    LCIS code force 10D
    UVA 1398
    uva1382 Distant Galaxy
    洛谷-3930(我在洛谷上也写了题解)
    HDU-1505 City Game
    导弹拦截n logn的算法(单调性)洛谷1020
    POJ 1182 食物链
    POJ
    1202. 交换字符串中的元素
  • 原文地址:https://www.cnblogs.com/RainingNight/p/strongly-typed-options-configure-in-asp-net-core.html
Copyright © 2011-2022 走看看