DotNet Configuration 源碼:https://github.com/dotnet/runtime/tree/master/src/libraries/,其中以 Microsoft.Extensions.Options 開頭的項目
Options 官方文檔:
Nuget包:Microsoft.Extensions.Options
建議先掌握:
- DependencyInjection ( 依賴注入 ):
- Configuration ( 配置 ) :
介紹
Options
提供了對配置數據的強類型訪問
Options
提供了驗證選項的機制
Configure
首先聲明一個選項類
public class MyOption
{
public string A { get; set; }
public string B { get; set; }
}
使用Configure
擴展方法對選項類進行配置
var serviceCollection = new ServiceCollection();
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
n.A = "A Value";
n.B = "B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
獲取IOptions<MyOption>
選項服務
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
在Aps.Net 中使用
配置
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<MyOption>(option =>
{
option.A = "A Value";
option.B = "A Value";
});
}
注入Ioption<MyOption>
[Route("Home")]
public class HomeController : ControllerBase
{
private readonly MyOption _myOption;
public HomeController(IOptions<MyOption> myOptions)
{
_myOption = myOptions.Value;
}
[HttpGet]
public string GetAsync()
{
return $"A:{_myOption.A},B:{_myOption.B}";
}
}
綁定配置,使用IConfiguration
來配置選項
Nuget包:Microsoft.Extensions.Options.ConfigurationExtensions
Configure<TOptions>(IConfigureation configuration)
不僅僅會綁定配置,還會添加對配置文件的更改通知,通知IOptionsMonitor
重新計算選項
實例:
appsettings.json
{
"MySetting": {
"A": "A Value",
"B": "B Value"
}
}
C#
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOption>(configuration.GetSection("MySetting"));
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
var myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
PostConfigure
PostConfigure
會在 Configure
之后進行配置
實例:
var serviceCollection = new ServiceCollection();
serviceCollection.PostConfigure<MyOption>(option =>
{
option.B = "PostConfigure B Value";
});
serviceCollection.Configure<MyOption>(n =>
{
n.A = "A Value";
n.B = "B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // PostConfigure B Value
命名選項
可以給選項命名
默認選項名稱為空字符串
IOptions
不支持獲取命名選項,只能獲取默認命名選項
可以使用IOptionsSnapshot
、IOptionsMonitor
等獲取命名選項
IOptionsSnapshot
繼承子IOptions
並添加了Get(string optionName)
方法來獲取命名選項
IOptionsMonitor
的CurrentValue
屬性用於獲取默認選項,Get(string optionName)
方法來獲取命名選項
實例:
var serviceCollection = new ServiceCollection();
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
n.A = "A Value";
n.B = "B Value";
});
serviceCollection.Configure<MyOption>("My", n =>
{
n.A = "My:A Value";
n.B = "My:B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value
// IOptionsSnapshot 獲取命名選項
var myOption2 = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // My:A Value
Console.WriteLine(myOptionValue2.B); // My:B Value
// IOptionsMonitor 獲取命名選項
var myOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MyOption>>();
MyOption myOptionValue3 = myOptionsMonitor.Get("My");
Console.WriteLine(myOptionValue3.A);
Console.WriteLine(myOptionValue3.B);
ConfigureAll、PostConfigureAll
ConfigureAll
、PostConfigureAll
可以配置全部的命名配置
實例:
var serviceCollection = new ServiceCollection();
serviceCollection.ConfigureAll<MyOption>(n =>
{
n.A = "A Value,Config By ConfigureAll";
});
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
n.B = "B Value";
});
serviceCollection.Configure<MyOption>("My", n =>
{
n.B = "My:B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value,Config By ConfigureAll
Console.WriteLine(myOptionValue.B); // B Value
var myOption2 = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // A Value,Config By ConfigureAll
Console.WriteLine(myOptionValue2.B); // My:B Value
IOptions、IOptionsSnapshot、IOptionsMonitor
IOptions<TOptions>
- 只會計算一次選項
- 不支持命名選項
- 單例服務,可以注入到任何服務生存期
IOptionsSnapshot<TOptions>
- 會計算多次選項,每次請求時應重新計算選項
- 注冊為范圍內,因此無法注入到單一實例服務
- 支持命名選項
IOptionsMonitor<TOptions>
:
- 單例服務,可以注入到任何服務生存期
- 支持更改通知 ( 比如配置文件更改通知 )
- 支持命名選項
- 支持重載配置
- 選擇性選項失效 (IOptionsMonitorCache
)
IOptionsFactory<TOptions>
負責創建、計算選項實例,IOptions<TOptions>
、IOptionsSnapshot<TOptions>
、IOptionsMonitor<TOptions>
都使用它來創建選項實例
對比實例:
appsettings.json
{
"MySetting": {
"B": "B 1"
}
}
C# Startup
public class Startup
{
public IConfiguration Configuration { get; }
public Startup( IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<MyOption>(option => { option.A = DateTime.Now.ToString(CultureInfo.CurrentCulture); });
services.Configure<MyOption>(Configuration.GetSection("MySetting"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(options => { options.MapControllers(); });
}
}
C# HomeController
[Route("Home")]
public class HomeController : ControllerBase
{
public IOptions<MyOption> MyOptions { get; }
public IOptionsSnapshot<MyOption> MyOptionsSnapshot { get; }
public IOptionsMonitor<MyOption> MyOptionsMonitor { get; }
public IServiceProvider ServiceProvider { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public HomeController(
IOptions<MyOption> myOptions,
IOptionsSnapshot<MyOption> myOptionsSnapshot,
IOptionsMonitor<MyOption> myOptionsMonitor,
IServiceProvider serviceProvider,
IWebHostEnvironment webHostEnvironment
)
{
MyOptions = myOptions;
MyOptionsSnapshot = myOptionsSnapshot;
MyOptionsMonitor = myOptionsMonitor;
ServiceProvider = serviceProvider;
WebHostEnvironment = webHostEnvironment;
}
[HttpGet]
public string GetAsync()
{
Console.WriteLine($"MyOption A:{MyOptions.Value.A}");
Console.WriteLine($"MyOption B:{MyOptions.Value.B}");
Console.WriteLine($"MyOptionsSnapshot A:{MyOptionsSnapshot.Value.A}");
Console.WriteLine($"MyOptionsSnapshot B:{MyOptionsSnapshot.Value.B}");
Console.WriteLine($"MyOptionsMonitor A:{MyOptionsMonitor.CurrentValue.A}");
Console.WriteLine($"MyOptionsMonitor B:{MyOptionsMonitor.CurrentValue.B}");
// 更改appsetting配置文件,處罰文件更改通知 ( json文件配置需要開啟更改重載 )
System.IO.File.WriteAllText(WebHostEnvironment.ContentRootPath + "/appsettings.json",
JsonSerializer.Serialize(new{
MySetting = new
{
B = "B 2"
}
})
);
var timer = new Timer(2000);
timer.Start();
timer.Elapsed += (sender, e) =>
{
Console.WriteLine($"MyOption2 A:{MyOptions.Value.A}");
Console.WriteLine($"MyOption2 B:{MyOptions.Value.B}");
Console.WriteLine($"MyOptionsSnapshot2 A:{MyOptionsSnapshot.Value.A}");
Console.WriteLine($"MyOptionsSnapshot2 B:{MyOptionsSnapshot.Value.B}");
Console.WriteLine($"MyOptionsMonitor2 A:{MyOptionsMonitor.CurrentValue.A}");
Console.WriteLine($"MyOptionsMonitor2 B:{MyOptionsMonitor.CurrentValue.B}");
timer.Stop();
};
return "Hello";
}
}
控制台打印信息如下:
第一次請求:
MyOption A:10/23/2020 11:48:37 PM
MyOption B:B 1
MyOptionsSnapshot A:10/23/2020 11:48:37 PM
MyOptionsSnapshot B:B 1
MyOptionsMonitor A:10/23/2020 11:48:37 PM
MyOptionsMonitor B:B 1
2秒后
MyOption2 A:10/23/2020 11:48:37 PM // 沒變,因為IOptions只會計算一次
MyOption2 B:B 1 // 沒變,因為IOptions只會計算一次
MyOptionsSnapshot2 A:10/23/2020 11:48:37 PM // 沒變,因為IOptionsSnapshot每次請求才會計算一次
MyOptionsSnapshot2 B:B 1 // 沒變,因為IOptionsSnapshot每次請求才會計算一次
MyOptionsMonitor2 A:10/23/2020 11:48:37 PM // 沒變,因為IOptionsMonitor沒有接受到 A鍵 的相關更改通知
MyOptionsMonitor2 B:B 2 // 變了,因為IOptionsMonitor接受到了文件配置更改通知
第二次請求:
MyOption A:10/23/2020 11:48:37 PM // 沒變,因為IOptions只會計算一次
MyOption B:B 1 // 沒變,因為IOptions只會計算一次
MyOptionsSnapshot A:10/23/2020 11:49:15 PM // 變了,因為IOptionsSnapshot每次請求都會計算一次
MyOptionsSnapshot B:B 2 // 變了,因為IOptionsSnapshot每次請求都會計算一次
OptionBuilder
特點:
- 簡化了創建命名選項的過程,因為它只是初始
AddOptions<TOptions>(string optionsName)
調用的單個參數,而不會出現在所有后續調用中 - 能在配置選項過程中使用依賴
- 能夠驗證選項
不使用OptionsBuilder
var serviceCollection = new ServiceCollection();
serviceCollection.PostConfigure<MyOption>("My",n =>
{
n.A = n.A + "_Stuff";
n.B = "B";
});
serviceCollection.Configure<MyOption>("My", n => { n.A = "A"; });
使用OptionsBuilder
通過AddOptions<TOptions>(string optionsName)
獲取OptionsBuilder
var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<MyOption>("My")
.PostConfigure(n =>
{
n.A = n.A + "_Stuff";
n.B = "B";
})
.Configure(n => { n.A = "A"; });
選項依賴
OptionBuilder
的Configure
最多支持5個泛型參數,他們表示選項的依賴
var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<MyOption>("My")
.Configure<IConfiguration>((option, configuration) =>
{
option.A = "A";
option.B = configuration["B"];
});
選項驗證
OptionBuilder
的Validate
可以對選項進行驗證,將在此選項獲取時進行驗證,如果驗證失敗將拋出異常
Validate
的第二個參數可以自定義驗證的錯誤信息
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions<MyOption>()
.Configure<IConfiguration>((option, configuration) =>
{
option.A = "A2";
option.B = configuration["B"];
})
.Validate(options => options.B != null,"B不能為空"); // 第二個參數用於自定義驗證錯誤信息
.Validate(options => options.A != null,"A不能為空"); // 第二個參數用於自定義驗證錯誤信息
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>(); // 如果驗證失敗,將拋出異常
使用特性進行選項驗證
Nuget包:Microsoft.Extensions.Options.DataAnnotations
實例:
appsettings.json
{
"MySetting": {
"A": "A Value",
"B": "B Value"
}
}
C#
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOption2>(configuration.GetSection("MySetting"));
serviceCollection.AddOptions<MyOption2>()
.ValidateDataAnnotations(); // 使用特性進行選項驗證
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption2>>();
var myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
ConfiguOptions
ConfiguOptions
用於在依賴容器中注冊IConfigureOptions<in TOptions>
、IPostConfigureOptions<in TOptions>
和IValidateOptions<TOptions>
的實現(如果傳入的類型實現了他們)
Configure
、PostConfigure
等對選項進行配置,本質就是在依賴容器中注冊IConfigureOptions<in TOptions>
的實現
OptionBuilder
的Validate
函數本質也是注冊IValidateOptions<TOptions>
的實現
所以我們也可以手動注冊IConfigureOptions<in TOptions>
,這樣的一個好處是可以注入依賴
注意:
serviceCollection.AddOptions()
函數用於注冊IOptions
、IOptionsSnapshot
、IOptionsMonitor
、IOptionsFactory
、IOptionsMonitorCache
的實現
使用serviceCollection.Configure
等函數會在內部調用serviceCollection.AddOptions()
,所以不需要在調用它
這里我們手動注入IConfigureOptions<in TOptions>
的實現,所以手動調用它
實例1:
C# 1
public class MyConfigureOption : IConfigureOptions<MyOption>
{
public IConfiguration Configuration { get; }
public MyConfigureOption(IConfiguration configuration)
{
Configuration = configuration;
}
public void Configure(MyOption options)
{
options.A = "A Value";
options.B = Configuration["B"];
}
}
C# 2
/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
Console.WriteLine(myOption.Value.A); // A Value
Console.WriteLine(myOption.Value.B); // B Value
實例2:
可以直接繼承自ConfigureNamedOptions<MyOption>
來實現命名配置
C# 1
public class MyConfigureOption : ConfigureNamedOptions<MyOption>
{
public MyConfigureOption(IConfiguration configuration)
: base("My", options =>
{
options.A = "A Value";
options.B = configuration["B"];
})
{
}
}
C# 2
/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value
實例3:
可以實現IValidateOptions<TOptions>
來對選項進行驗證
public class MyConfigureOption : ConfigureNamedOptions<MyOption>, IValidateOptions<MyOption>
{
public MyConfigureOption(IConfiguration configuration)
: base("My", options =>
{
options.A = "A Value";
options.B = configuration["B"];
})
{
}
public ValidateOptionsResult Validate(string name, MyOption options)
{
if (name != "My")
{
return ValidateOptionsResult.Skip;
}
if (options.B == null)
{
return ValidateOptionsResult.Fail("B 不能為空");
}
else
{
return ValidateOptionsResult.Success;
}
}
}
C# 2
/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
serviceCollection.AddTransient<IValidateOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
使用ConfiguOptions
代替手動注冊IConfigureOptions<in TOptions>
、IPostConfigureOptions<in TOptions>
和IValidateOptions<TOptions>
在上面實例中需要手動指定要注冊的服務類,在實例3中需要注冊2次,使用ConfiguOptions
能幫你自動注冊他們
實例3中的以下代碼
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
serviceCollection.AddTransient<IValidateOptions<MyOption>, MyConfigureOption>();
可以替換一下代碼
serviceCollection.ConfigureOptions<MyConfigureOption>();