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的使用。

  • 相关阅读:
    nginx能访问html静态文件但无法访问php文件
    LeetCode "498. Diagonal Traverse"
    LeetCode "Teemo Attacking"
    LeetCode "501. Find Mode in Binary Search Tree"
    LeetCode "483. Smallest Good Base" !!
    LeetCode "467. Unique Substrings in Wraparound String" !!
    LeetCode "437. Path Sum III"
    LeetCode "454. 4Sum II"
    LeetCode "445. Add Two Numbers II"
    LeetCode "486. Predict the Winner" !!
  • 原文地址:https://www.cnblogs.com/RainingNight/p/strongly-typed-options-configure-in-asp-net-core.html
Copyright © 2011-2022 走看看