ASP.NET Core 源码学习之 Options[1]:Configure

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{  
   
     if (services == null)    {      
      throw new ArgumentNullException(nameof(services));    }  
    if (configureOptions == null)    {    
       throw new ArgumentNullException(nameof(configureOptions));    }    services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));    return services; }

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

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{  
   if (services == null)    {  
      throw new ArgumentNullException(nameof(services));    }  
  if (config == null)    {        throw new ArgumentNullException(nameof(config));    }    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));        }    } }

大家或许会有点疑问:“ 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 (options == null)        {        
      throw new ArgumentNullException(nameof(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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值