使用範本檔管理組態並轉成 env 環境變數

.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 檔有階層關係,所以可能要改寫一下範本檔,難度有點高

範例位置

sample.dotblog/Configuration/Lab.EnvGenerator at b5c24a0cc0e2c5875a5c7dc0255bb8d11c9ba46a · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo