1

Coding4Fun - .NET 依原始順序讀取 appsettings.json Key Value

 1 year ago
source link: https://blog.darkthread.net/blog/get-appsetings-in-origin-order/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

.NET 依原始順序讀取 appsettings.json Key Value-黑暗執行緒

Side Project 有個需求,要在 appsettings.json 定義要觀察的硬體偵測器,我第一個想到的做法是宣告一個 Sensors 直接用 "偵測器名稱": "偵測器ID" 的 Key/Value 形式列舉,像是這樣:

{
    "Sensors": {
        "SSD Temp": "/hdd/0/temperature/0",
        "CPU Temp": "/intelcpu/0/temperature/0",
        "CPU Load": "/intelcpu/0/load/0"
    }
}

透過 config.GetSection("Sensors").GetChildren() 可列舉 Sensors 的 Key/Value,輕鬆取得偵測器資料:

using Microsoft.Extensions.Configuration;

Console.WriteLine(AppContext.BaseDirectory);
var config = new ConfigurationBuilder()
                .SetBasePath(AppContext.BaseDirectory)
                .AddJsonFile("appsettings.json", optional: false)
                .Build();
foreach (var item in config.GetSection("Sensors").GetChildren())
{
    Console.WriteLine($"{item.Key}= {item.Value}");
}

但事與願違,雖然有讀到設定,但順序變了,CPU Load、CPU Temp、SSD Temp,感覺被排序過。

Fig1_638056379748864382.png

追進原始碼證實了這點。GetChildren() 背後會呼叫 GetChildrenImplementation(),在其中透過 GetChildKeys() 取得 Key 集合 (IEnumerable<string>),而 GetChildKeys() 裡有段排序邏輯 results.Sort(ConfigurationKeyComparer.Comparison); 對 Key 進行排序。

// InternalConfigurationRootExtensions.cs
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string? path)
{
    using ReferenceCountedProviders? reference = (root as ConfigurationManager)?.GetProvidersReference();
    IEnumerable<IConfigurationProvider> providers = reference?.Providers ?? root.Providers;

    IEnumerable<IConfigurationSection> children = providers
        .Aggregate(Enumerable.Empty<string>(),
            (seed, source) => source.GetChildKeys(seed, path))
        .Distinct(StringComparer.OrdinalIgnoreCase)
        .Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));

    if (reference is null)
    {
        return children;
    }
    else
    {
        // Eagerly evaluate the IEnumerable before releasing the reference so we don't allow iteration over disposed providers.
        return children.ToList();
    }
}

// ConfigurationProvider.cs
public virtual IEnumerable<string> GetChildKeys(
    IEnumerable<string> earlierKeys,
    string? parentPath)
{
    var results = new List<string>();

    if (parentPath is null)
    {
        foreach (KeyValuePair<string, string?> kv in Data)
        {
            results.Add(Segment(kv.Key, 0));
        }
    }
    else
    {
        Debug.Assert(ConfigurationPath.KeyDelimiter == ":");

        foreach (KeyValuePair<string, string?> kv in Data)
        {
            if (kv.Key.Length > parentPath.Length &&
                kv.Key.StartsWith(parentPath, StringComparison.OrdinalIgnoreCase) &&
                kv.Key[parentPath.Length] == ':')
            {
                results.Add(Segment(kv.Key, parentPath.Length + 1));
            }
        }
    }

    results.AddRange(earlierKeys);

    results.Sort(ConfigurationKeyComparer.Comparison);

    return results;
}

研究了一下,appsettings.json 絕大部分的應用方式是傳 Key 取 Value,很少人會在意 Key 順序,因此也沒有公開 API 可讀取未排序的 Key/Value,我想搭便車的構想破滅。

若要保留設定順序,回歸自訂物件陣列是較簡單的做法。將 appsettings.json Sensors 改為陣列:

{
    "Sensors": [
        { "Name":"SSD Temp", "Id":"/hdd/0/temperature/0" },
        { "Name":"CPU Temp", "Id":"/intelcpu/0/temperature/0" },
        { "Name":"CPU Load", "Id":"/intelcpu/0/load/0" }
    ]
}

專案參照 Microsoft.Extensions.Configuration.Binder 並修改如下:

using Microsoft.Extensions.Configuration;

Console.WriteLine(AppContext.BaseDirectory);
var config = new ConfigurationBuilder()
                .SetBasePath(AppContext.BaseDirectory)
                .AddJsonFile("appsettings.json", optional: false)
                .Build();

foreach (var item in config.GetSection("Sensors").Get<List<Sensor>>()!) {
    Console.WriteLine($"{item.Name}= {item.Id}");
}

public class Sensor {
    public string Id {get; set;}
    public string Name {get; set;}
}

如此就能如願保留原始順序:

Fig2_638056379765156334.png

最後,基於好玩,既然已追進原始碼,我也試著用 Reflection 偷出原始順序 Dictionary:

Fig3_638056379784335928.png

存取非公開屬性的做法可能在改版升級後失效,不建議當成正式解法,這裡純屬好玩兼練手感,實際應用還是讓回歸正道。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK