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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM