.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