天天看點

ASP.NET Core 選項模式源碼學習Options Configure(一)

前言

ASP.NET Core 後我們的配置變得更加輕量級了,在ASP.NET Core中,配置模型得到了顯著的擴充和增強,應用程式配置可以存儲在多環境變量配置中,appsettings.json使用者機密等 并可以通過應用程式中的相同界面輕松通路,除此之外,ASP.NET中的新配置系統允許使用Options的強類型設定。

強類型Options

在ASP.NET Core中沒有AppSettings["Key"]預設方法,那麼推薦的是建立強類型的配置類,去綁定配置項。

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

        public string Url { get; set; }
    }
           

然後我們在appsettings.json中添加如下内容:

{
  "MyOptions": 
    {
      "Name": "TestName",
      "Url": "TestUrl"
    }
}
           

配置綁定到類

ConfigureServices方法進行配置以綁定到類

public void ConfigureServices(IServiceCollection services)
        {

            services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
            services.AddControllers();

        }
           

MyOptions隻需将IOptions<>類的執行個體注入控制器中,然後通過Value屬性擷取Myoptions:

public class WeatherForecastController : ControllerBase
    {
        private readonly MyOptions _options;
        public WeatherForecastController(IOptions<MyOptions> options)
        {
            _options = options.Value;
        }

        [HttpGet]
        public OkObjectResult Get() {
            return Ok(string.Format("Name:{0},Url:{1}", _options.Name,_options.Url));
        }
    }
           

Configure

委托配置
//基礎注冊方式
            services.Configure<MyOptions>(o => { o.Url = "MyOptions"; o.Name = "Name111"; });
            //指定具體名稱
            services.Configure<MyOptions>("Option", o => { o.Url = "MyOptions"; o.Name = "Name111"; }) ;
            //配置所有執行個體
            services.ConfigureAll<MyOptions>(options =>{ options.Name = "Name1";  options.Url = "Url1";});

           
通過配置檔案配置
// 使用配置檔案來注冊執行個體
            services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
            // 指定具體名稱
            services.Configure<MyOptions>("Option", Configuration.GetSection("MyOptions"));
           
PostConfigure

PostConfigure會在Configure注冊完之後再進行注冊

services.PostConfigure<MyOptions>(o => o.Name = "Name1");
            services.PostConfigure<MyOptions>("Option", o => o.Name = "Name1");
            services.PostConfigureAll<MyOptions>(o => o.Name = "Name1");
           

源碼解析

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.AddOptions();
            services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
            return services;
        }


           

上面代碼IConfigureOptions實作了ConfigureNamedOptions,那我們再來看看内部源碼

ConfigureNamedOptions 其實就是把我們注冊的Action包裝成統一的Configure方法,以友善後續建立Options執行個體時,進行初始化。

public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<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));
            }

            // Null name is used to configure all named options.
            if (Name == null || name == Name)
            {
                Action?.Invoke(options);
            }
        }
        public void Configure(TOptions options) => Configure(Options.DefaultName, options);
    }
           

在 services.Configure(Configuration.GetSection("MyOptions")); 我們不指定具體名稱的時候預設是如下代碼片段

public virtual void Configure(string name, TOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            // Null name is used to configure all named options.
            if (Name == null || name == Name)
            {
                Action?.Invoke(options);
            }
        }
        public void Configure(TOptions options) => Configure(Options.DefaultName, options);
           

預設使用的是Options.DefaultName

AddOptions預設方法預設為我們注冊了一些核心的類

public static IServiceCollection AddOptions(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(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;
        }