.NET Core 原生的組態設定是使用 appsettings.json 來讀取組態設定,要先產生出各個環境的組態設定 appsettings.Development.json、appsettings.Staging.json、appsettings.Production.json,再透過環境變數+Build,取代掉原本的值,要佈署的環境越多檔案就越多,每次的改動設定都要很小心,生怕一個不注意就弄壞了,我想要讓組態管理的行為變簡單一些…
有關 appsettings.json 組態設定的切換可以參考 .NET 6 應用程式如何切換環境和組態 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
開發環境
- Windows 11 Home
- .NET 8
- Spectre.Console.Cli 0.49.1
概念
- 在一個檔案管理所有的參數
- 同時比對不同的環境的參數值
- 根據環境產生出正確的檔案
範本檔定義
- [DefaultConnection]:參數名稱
- default:參數值,預設,必填
- qa/prod:環境,產生器會根據這個值覆蓋掉預設值
[DefaultConnection]
default=local
qa=Server=qa;Database=proddb;User Id=produser;Password=prodpassword;
prod=Server=prodserver;Database=proddb;User Id=produser;Password=prodpassword;
[Ports]
default=8001, 8001, 8002
[Allowed]
default=false
qa=true
prod=false
範例
我期望轉換程式能幫我根據環境轉出不同的設定檔
輸入 gen.exe --env qa,產生 app.qa.env 檔案,內容如下
DefaultConnection=Server=qa;Database=proddb;User Id=produser;Password=prodpassword;
Ports=8001, 8001, 8002
Allowed=true
輸入 gen.exe --env prod,產生 app.prod.env 檔案,內容如下
DefaultConnection=Server=prodserver;Database=proddb;User Id=produser;Password=prodpassword;
Ports=8001, 8001, 8002
Allowed=false
需求確定後就可以開幹了,需要一個應用程式讀檔、轉檔,建立一個 console 應用程式,並加入 Spectre.Console.Cli 套件
這是硬生生的每一行每一行讀,沒有甚麼太特別的手段
private static Dictionary<string, string> ParseEnvTemplate(string[] templateLines, string env)
{
var result = new Dictionary<string, string>();
var currentSection = "";
foreach (var line in templateLines)
{
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
{
continue;
}
// 找出 section
if (line.StartsWith("[") && line.EndsWith("]"))
{
currentSection = line.Trim('[', ']');
continue;
}
var parts = line.Split('=', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
{
continue;
}
var key = parts[0].Trim();
var value = parts[1].Trim();
if (parts.Length > 2)
{
// 被分割的部分重新組合
value = string.Join("=", parts.Skip(1)).Trim();
}
// 優先取環境變數的值
if (key == env)
{
result[currentSection] = value;
}
// 若沒有環境變數的值,則取 default
else if (key == "default")
{
result.TryAdd(currentSection, value);
}
}
return result;
}
讀完之後轉成檔案
private static void GenerateEnvFile(Dictionary<string, string> settings, string outputFileName)
{
using var writer = new StreamWriter(outputFileName);
foreach (var setting in settings)
{
writer.WriteLine($"{setting.Key}={setting.Value}");
}
}
流程如下
public override int Execute(CommandContext context, Settings settings)
{
// 讀取 env.template 檔案
var envTemplate = File.ReadAllLines("env.template");
var env = settings.Environment;
var outputFileName = $"app.{env}.env";
// 解析 env.template 檔案
var contents = ParseEnvTemplate(envTemplate, env);
GenerateEnvFile(contents, outputFileName);
Console.WriteLine($"Generated {outputFileName}.");
return 0;
}
建立 Command
internal sealed class ConvertEnvCommand : Command<ConvertEnvCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandOption("--env")]
public string? Environment { get; init; }
}
public override int Execute(CommandContext context, Settings settings)
{
...
return 0;
}
}
internal class Program
{
private static void Main(string[] args)
{
var app = new CommandApp();
app.Configure(config =>
{
config.AddCommand<ConvertEnvCommand>("convert")
.WithDescription("convert a file.")
.WithExample("convert", "--env", "qa");
});
app.Run(args);
}
}
上述用法套用 Spectre.Console.Cli 可以參考以下連結
使用 Spectre.Console.Cli 解析 Console / WinForm / WPF 的參數 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
程式碼完成之後,接著調用測試看看,在環境變數傳入以下參數
convert --env prod
convert --env qa
執行結果如下:
有了 env file 接下來就可以在應用程式套用了,有關環境變數的讀取可以參考 自訂 ConfigurationProvider - 實作 EnvFileConfigurationProvider | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
心得
這樣的作法應該可以有效改善組態管理的痛點,目前這還是 PoC,可能還有許多的場景沒有被考慮進去,若不喜歡使用 env 作為應用程式的參數設定,也已改成 json 檔,要考慮 json 檔有階層關係,所以可能要改寫一下範本檔,難度有點高
範例位置
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET