簡體   English   中英

IConfigurationRoot 和類型信息

[英]IConfigurationRoot and type information

根據此Using System.Text.Json to Serialize an IConfiguration back to Json看來您可以放入 JSON 的有限類型信息已被丟棄。

您似乎認為 IConfiguration 對象正在存儲對應於 JSON 元素類型的整數、布爾值等(例如)。 這是不正確的。 IConfiguration 中的所有數據都以字符串形式存儲。 基本配置提供程序類都需要一個 IDictionary<string, string> 填充數據。 甚至 JSON 配置提供程序也會對值執行顯式 ToString。

當我用這種擴展方法解決該問題中提出的問題時,我注意到了這一點。

using System.Collections.Generic;
using System.Dynamic;
using Microsoft.Extensions.Configuration;

public static class ExtendConfig
{
  public static dynamic AsDynamic(this IConfigurationRoot cr)
  {
    var result = new ExpandoObject();
    var resultAsDict = result as IDictionary<string, object>;
    foreach (var item in cr.AsEnumerable())
    {
      resultAsDict.Add(item.Key, item.Value);
    }
    return result;
  }
}

此方法重建圖形,但現在一切都是字符串。

我可以編寫自己的解析器並將其應用於原始 JSON 字符串,但這有點可怕。 有什么方法可以獲取此元數據,以便提高合並配置的保真度? 我將它傳遞給 JS 消費它確實注意到了差異。

合並是我使用配置擴展構建器的原因。

由於IConfiguration不提供有關類型的信息,但JsonConfigurationProvider使用的System.Text.Json提供,因此工作解決方案(或解決方法)將使用System.Text.Json反序列化器直接讀取配置文件並匹配類型到配置鍵。

但是我們有一些小問題需要先解決。 就像 - 配置文件在哪里? 我們不想在代碼中復制該信息,我們必須從IConfiguration實例中提取它。

然后 - 將具體的現有配置密鑰與 JSON 文檔樹節點匹配。 這將需要 DFS 或 BFS 樹遍歷算法。 我將 go 用於 DFS(深度優先搜索)。 簡而言之-如果您有可擴展的節點,則將它們以相反的順序放入堆棧中。 然后你有一個while循環,它從堆棧中獲取一個節點,如果它有孩子,你把它們放在同一個堆棧上,如果沒有 - 你只是產生節點。 就這么簡單,和 BFS 非常相似,但沒關系。

還有一件事: Newtonsoft.Json - 一個流行的Nuget package,當時甚至被微軟使用。 JSON 序列化器比System.Text.Json慢一點,但它更高級,允許用戶通過節點構建 JSON 文檔樹節點。

有了這個強大的工具,創建一個可寫的 JSON IConfiguration變得相對容易,尤其是使用下面我的一些助手。

查看SaveChanges()方法。 它遍歷IConfiguration節點,按路徑匹配適當的JObject節點,並將更改從IConfiguration實例復制到JObject實例。 然后你可以只寫 JSON 文件。

有一個丑陋的黑客用來獲取文件。 我得到包含IConfigurationRoot實例的私有字段,但如果您已經擁有配置根,則可以跳過該字段。 擁有根您可以從中獲取JsonConfigurationProvider ,那么它只是Source.Path屬性。

所以這里是代碼。 It's a part of the Woof.Toolkit and Woof.Config Nuget package, that provides writeable JSON configurations, some helper methods to them, and also Azure Key Vault client that uses a JSON configuration, with some helper methods to encrypt and decrypt sensitive data with存儲在 AKV 上的密鑰。

這是ConfigurationExtensions class 的第一個版本,因此它在性能方面可能不是最佳的,但它可以工作並說明如何將IConfiguration實例節點與JObject節點匹配以獲取配置屬性的類型。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

using Newtonsoft.Json.Linq;

namespace Woof.Config;

/// <summary>
/// Extensions for <see cref="IConfiguration"/> making JSON type <see cref="IConfiguration"/> writeable.
/// </summary>
public static class ConfigurationExtensions {


    /// <summary>
    /// Gets the configuration root element.
    /// </summary>
    /// <param name="configuration">Any <see cref="IConfiguration"/> part.</param>
    /// <returns>Root element.</returns>
    public static IConfigurationRoot? GetRoot(this IConfiguration configuration) {
        if (configuration is IConfigurationRoot root) return root;
        var rootField = configuration.GetType().GetField("_root", BindingFlags.Instance | BindingFlags.NonPublic);
        return rootField?.GetValue(configuration) as IConfigurationRoot;
    }

    /// <summary>
    /// Gets the first <see cref="JsonConfigurationProvider"/> if exists, null otherwise.
    /// </summary>
    /// <param name="root">Configuration root element.</param>
    /// <returns><see cref="JsonConfigurationProvider"/> or null.</returns>
    public static JsonConfigurationProvider? GetJsonConfigurationProvider(this IConfigurationRoot root)
        => root.Providers.OfType<JsonConfigurationProvider>().FirstOrDefault();

    /// <summary>
    /// Gets the first <see cref="JsonConfigurationProvider"/> if exists, null otherwise.
    /// </summary>
    /// <param name="config">Any <see cref="IConfiguration"/> part.</param>
    /// <returns><see cref="JsonConfigurationProvider"/> or null.</returns>
    public static JsonConfigurationProvider? GetJsonConfigurationProvider(this IConfiguration config)
        => config.GetRoot()?.GetJsonConfigurationProvider();

    /// <summary>
    /// Saves changes made to <see cref="IConfiguration"/> to the JSON file if exists.
    /// </summary>
    /// <param name="config">Any <see cref="IConfiguration"/> part.</param>
    /// <exception cref="InvalidOperationException">Configuration does not have <see cref="JsonConfigurationProvider"/>.</exception>
    public static void SaveChanges(this IConfiguration config) {
        var provider = config.GetJsonConfigurationProvider();
        if (provider is null) throw new InvalidOperationException("Can't get JsonConfigurationProvider");
        var sourceJson = File.ReadAllText(provider.Source.Path);
        var target = JObject.Parse(sourceJson);
        var stack = new Stack<IConfigurationSection>();
        foreach (IConfigurationSection section in config.GetChildren().Reverse()) stack.Push(section);
        while (stack.TryPop(out var node)) {
            var children = node.GetChildren();
            if (children.Any()) foreach (var child in children.Reverse()) stack.Push(child);
            else {
                var jPath = GetJPath(node.Path);
                var element = target.SelectToken(jPath);
                var valueString =
                    element!.Type == JTokenType.Null
                        ? "null" :
                        element!.Type == JTokenType.String ? $"\"{node.Value}\"" : node.Value;
                element!.Replace(JToken.Parse(valueString));
            }
        }
        File.WriteAllText(provider.Source.Path, target.ToString());
    }

    /// <summary>
    /// Sets <paramref name="configuration"/>'s <paramref name="key"/> with specified <paramref name="value"/>.
    /// </summary>
    /// <param name="configuration">The configuration.</param>
    /// <param name="key">The key of the configuration section.</param>
    /// <param name="value">Value to set.</param>
    /// <exception cref="InvalidOperationException">Not supported type as value.</exception>
    public static void SetValue(this IConfiguration configuration, string key, object? value) {
        var c = CultureInfo.InvariantCulture;
        var valueString = value switch {
            null => null,
            string v => v,
            Uri v => v.ToString(),
            byte[] v => Convert.ToBase64String(v),
            bool v => v.ToString(c),
            int v => v.ToString(c),
            decimal v => v.ToString(c),
            double v => v.ToString(c),
            uint v => v.ToString(c),
            long v => v.ToString(c),
            ulong v => v.ToString(c),
            short v => v.ToString(c),
            ushort v => v.ToString(c),
            byte v => v.ToString(c),
            sbyte v => v.ToString(c),
            float v => v.ToString(c),
            _ => throw new InvalidOperationException($"Cannot set value of type {value.GetType()}")
        };
        configuration[key] = valueString;
    }

    /// <summary>
    /// Gets the path for JObject.SelectToken method.
    /// </summary>
    /// <param name="path"><see cref="IConfiguration"/> path.</param>
    /// <returns><see cref="JObject"/> path.</returns>
    private static string GetJPath(string path) => RxIConfigurationIndex.Replace(path, "[$1]").Replace(':', '.');

    /// <summary>
    /// Matches the <see cref="IConfiguration"/> indices.
    /// </summary>
    private static readonly Regex RxIConfigurationIndex = new(@":(\d+)", RegexOptions.Compiled);

}

為什么JObject JSON 文件能否僅代表 object? 否 - 它可以表示任何值,包括 null。 但是 JSON 配置必須是 object。 這就是為什么我使用JObject作為我的輔助配置根。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM