[英]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.