上篇,如何使用組態 Microsoft.Extensions.Configuration,最後我讓物件依賴 IConfiguration,不論是讀檔操作,還是重新載入檔案,它都可以完成,還可以更好嗎?.NET Core 的 Options Pattern 強化 IConfiguration,封裝了讀檔、轉強型別、重新載入、載入通知、驗證資料的行為,提供另一種使用參數的選擇。
開發環境
- Rider 2021.3.4
- Windows 10
- .Net Fx 4.8 via 新版專案範本 .NET Project SDKs
- Microsoft.Extensions.Options 5.0.0
Install-Package Microsoft.Extensions.Options -Version 5.0.0
Options Pattern
下圖出自:https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/configuration/options
實例化 Options 步驟
- 讀組態設定,參考 上篇
- 注入 Options 和 IConfiguration
- 需要使用組態的物件開一個注入點依賴 IOptions<TOptions> 或 IOptionsSnapshot<TOptions> 或 IOptionsMonitor<TOptions>
範例如下:
[TestMethod]
public void 注入Option()
{
var builder = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((hosting, configBuilder) =>
{
// 1.讀組態檔
var environmentName =
hosting.Configuration["ENVIRONMENT2"];
configBuilder.AddJsonFile("appsettings.json", false, true);
configBuilder
.AddJsonFile($"appsettings.{environmentName}.json",
true, true);
})
.ConfigureServices((hosting, services) =>
{
// 2. 注入 Option 和 Configuration
services.Configure<AppSetting1>(hosting.Configuration);
//注入其他服務
services.AddSingleton<AppWorkFlowWithOption>();
})
;
var host = builder.Build();
var service = host.Services.GetService<AppWorkFlowWithOption>();
var playerId = service.GetPlayerId();
Console.WriteLine($"PlayerId = {playerId}");
}
Options 的方法都是 IServiceCollection 的擴充方法,這是微軟的 DI Container,有關 DI Container 的用法,可以參考
如何使用 DI Container for Microsoft.Extensions.DependencyInjection | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
如何使用 Microsoft.Extensions.DependencyInjection for Autofac | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
AppWorkFlowWithOption 開一個注入點,依賴 IOptions<AppSetting1>
public class AppWorkFlowWithOption : IAppWorkFlow
{
private readonly AppSetting1 _appSetting;
public AppWorkFlowWithOption(IOptions<AppSetting1> options)
{
this._appSetting = options.Value;
}
public string GetPlayerId()
{
return this._appSetting.Player.AppId;
}
}
AppSetting1 這我從 AppSetting.cs 抄過來的,本來用 struct,現在改成 class
public class AppSetting1
{
public Player Player { get; set; }
public ConnectionStrings ConnectionStrings { get; set; }
}
注入Options 的幾個方法
services.Configure:可以決定要注入完整的組態節點或是部分組態節點
//注入 Options 和完整 IConfiguration
services.Configure<AppSetting>(this.Configuration);
//注入 Options 和 Configuration Section Name
services.Configure<Player>("Player1", this.Configuration.GetSection("Player1"));
services.PostConfigure:Configure 後再執行的動作,範例,依照 IConfigureNamedOptions 名稱的實例 Key 屬性變更
services.PostConfigure<Player>("Player1", myOptions =>
{
myOptions.Key = "post_configured_option1_value";
});
services.PostConfigureAll:Configure 後再執行的動作,範例,所有實例的 Key 屬性都會變更
services.PostConfigureAll<AppSetting>(config =>
{
config.Player.AppId = "post_configured_option1_value";
});
上述這兩個 PostConfigure 方法,目前想不到適用情境
讀取 Options
在需要組態的 AppWorkFlowWithOption 物件依賴 IOptions<TOptoins>,只要依賴 Options Interfaces 就能使用他們的功能
Options Interfaces 有三種:IOptions,IOptionsSnapshot、IOptionsMonitor,使用方式參考 Options Interfaces
在建構函數開注入點,如下圖:
private readonly AppSetting1 _appSetting;
public AppWorkFlowWithOption(IOptions<AppSetting1> options)
{
this._appSetting = options.Value;
}
更多的範例,請參考以下
sample.dotblog/AppWorkFlowWithOption.cs at master · yaochangyu/sample.dotblog (github.com)
sample.dotblog/AppWorkFlowWithOptionsMonitor.cs at master · yaochangyu/sample.dotblog (github.com)
sample.dotblog/AppWorkFlowWithOptionsSnapshot.cs at master · yaochangyu/sample.dotblog (github.com)
在 ASP.NET Core 框架的注入取出 Service
[Route("options/appsettings")]
public IActionResult Get()
{
var serviceProvider = this.HttpContext.RequestServices;
var options = serviceProvider.GetService<IOptions<AppSetting>>();
return this.Ok(options?.Value);
}
詳細範例請參考
sample.dotblog/DefaultController.cs at master · yaochangyu/sample.dotblog (github.com)
Options Class 規則
Options<TOptoins>
TOptions 有以下規則:
- 具名公開、無參數、非抽象類別。
- 所有公開屬性都會繫結,預設公開屬性繫結組態節點名稱。
- 欄位不會繫結。
IConfigureNamedOptions
- 不同的組態節點綁定相同的物件
- 區分大小寫
怎麼使用?注入Options 時可以針對不同的 Configuration Setion Name,給予一組對應的名字,下列範例注入了兩個 Options
[TestMethod]
public void 注入OptionMonitor()
{
….
.ConfigureServices((hosting, services) =>
{
// 注入 Option 和完整 Configuration
services.Configure<AppSetting1>(hosting.Configuration);
// 注入 Option 和特定 Configuration Section Name
services.Configure<Player1>("Player",
hosting.Configuration.GetSection("Player"));
//注入其他服務
services.AddScoped<AppWorkFlowWithOptionsMonitor>();
})
;
…
}
取出時指定 Name
public AppWorkFlowWithOptionsMonitor(IOptionsMonitor<AppSetting1> appSettingOption,
IOptionsMonitor<Player1> playerOption)
{
this._player = playerOption.Get("Player");
this._appSetting = appSettingOption?.CurrentValue;
}
Options Interfaces
有以下幾種介面,請根據你的需求挑選,差異在於物件的生命週期
IOptions
- 生命週期註冊為 Singleton (AddSingleton / ServiceLifetime.Singleton),可以注入所有生命週期的服務
- 不支援
- 在應用程式啟動後讀取組態。
- 不同的組態節點綁定相同的物件(IConfigureNamedOptions)
IOptionsSnapshot
- 生命週期註冊為 Scope (AddScope),不可以注入 Singleton 生命週期的服務
- 在應用程式啟動後讀取組態。
- 不同的組態節點綁定相同的物件(IConfigureNamedOptions)
IOptionsMonitor
- 生命週期註冊為 Singleton (AddSingleton / ServiceLifetime.Singleton),可以注入所有生命週期的服務
- 在應用程式啟動後讀取組態。
- 不同的組態節點綁定相同的物件(IConfigureNamedOptions)
- 變更通知
- 選擇讓 Options 失效 (IOptionsMonitorCache<TOptions>)
重新讀取組態,依賴 IConfigurationBuilder.AddJsonFile(reloadOnChange = true),當 reloadOnChange = false 時,重新讀取將會失效
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
;
var configRoot = configBuilder.Build();
... read config
驗證 Options
Install-Package Microsoft.Extensions.Options.DataAnnotations -Version 5.0.0
注入 Validate
services.AddOptions 方法有兩個擴充方法可以啟用驗證
- ValidateDataAnnotations:驗證屬性有掛 Attribute
- Validate:可以寫更複雜的驗證
//驗證
services.AddOptions<AppSetting1>()
.ValidateDataAnnotations()
.Validate(p =>
{
if (p.ConnectionStrings
.DefaultConnectionString == null)
{
return false;
}
return true;
},
"DefaultConnectionString must be value"); // Failure message.
;
調用 option.Value 屬性觸發驗證,驗證失敗則噴出 OptionsValidationException 例外
public AppWorkFlowWithOption(IOptions<AppSetting1> options)
{
try
{
this._appSetting = options.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
Console.WriteLine(failure);
}
}
}
直接注入 AppSetting
IConfiguration / IOptions 對我而言,最重要的功能是重新讀取組態,若不需要重新讀取組態,也可以讓物件直接依賴組態強型別物件,以下範例是透過 IConfiguration 讀檔後綁定,直接注入 AppSetting 物件
[TestMethod]
public void 直接注入組態物件()
{
var builder = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((hosting, configBuilder) =>
{
// 1.讀組態檔
var environmentName =
hosting.Configuration["ENVIRONMENT2"];
configBuilder.AddJsonFile("appsettings.json", false, true);
configBuilder
.AddJsonFile($"appsettings.{environmentName}.json",
true, true);
})
.ConfigureServices((hosting, services) =>
{
var appSetting = hosting.Configuration.Get<AppSetting>();
services.AddSingleton(typeof(AppSetting), appSetting);
//注入其他服務
services.AddSingleton<AppWorkFlow1>();
})
;
var host = builder.Build();
var service = host.Services.GetService<AppWorkFlow1>();
var playerId = service.GetPlayerId();
Console.WriteLine($"PlayerId = {playerId}");
}
public class AppWorkFlow1 : IAppWorkFlow
{
private AppSetting _appSetting;
public AppWorkFlow1(AppSetting appSetting)
{
this._appSetting = appSetting;
}
public string GetPlayerId()
{
return this._appSetting.Player.AppId;
}
}
AppWorkFlow1 建構函數依賴 AppSetting
public class AppWorkFlow1 : IAppWorkFlow
{
private AppSetting _appSetting;
public AppWorkFlow1(AppSetting appSetting)
{
this._appSetting = appSetting;
}
public string GetPlayerId()
{
return this._appSetting.Player.AppId;
}
}
範例位置
sample.dotblog/SurveyOptionTests.cs at master · yaochangyu/sample.dotblog (github.com)
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET