feat(core): dynamic settings service, IFinlyticLogger, log broadcaster, and persistent Yahoo auth
This commit is contained in:
@@ -1,50 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticCore.Database;
|
||||
using FinlyticCore.Dtos.Settings;
|
||||
using FinlyticCore.Entities.Settings;
|
||||
using FinlyticCore.Models.Settings;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FinlyticCore.Services;
|
||||
|
||||
public interface ISettingsService<TContext> where TContext : DbContext
|
||||
public interface ISettingsService
|
||||
{
|
||||
// --- 1. Typsicherer Zugriff über SettingKey<T> (Empfohlen) ---
|
||||
Task<T> GetSettingAsync<T>(SettingKey<T> key,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task SetSettingAsync<T>(SettingKey<T> key, T value,
|
||||
CancellationToken cancellationToken = default);
|
||||
Task<T> GetSettingAsync<T>(SettingKey<T> key, CancellationToken cancellationToken = default);
|
||||
Task SetSettingAsync<T>(SettingKey<T> key, T value, CancellationToken cancellationToken = default);
|
||||
|
||||
// --- 2. Dynamischer Zugriff über Enum-Key ---
|
||||
Task<T> GetSettingAsync<TEnum, T>(TEnum enumKey, T defaultValue = default!,
|
||||
CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
||||
|
||||
Task SetSettingAsync<TEnum, T>(TEnum enumKey, T value,
|
||||
CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
||||
Task<T> GetSettingAsync<TEnum, T>(TEnum enumKey, T defaultValue = default!, CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
||||
Task SetSettingAsync<TEnum, T>(TEnum enumKey, T value, CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
||||
|
||||
// --- 3. Dynamischer Zugriff über String-Key ---
|
||||
Task<T> GetSettingAsync<T>(string key, T defaultValue = default!,
|
||||
CancellationToken cancellationToken = default);
|
||||
Task<T> GetSettingAsync<T>(string key, T defaultValue = default!, CancellationToken cancellationToken = default);
|
||||
Task SetSettingAsync<T>(string key, T value, CancellationToken cancellationToken = default);
|
||||
|
||||
Task SetSettingAsync<T>(string key, T value,
|
||||
CancellationToken cancellationToken = default);
|
||||
// --- 4. Reflection-Erkennung & Bulk-Verwaltung für Web UI / MQTT ---
|
||||
Task<List<DynamicSettingDto>> GetAllRegisteredSettingsAsync(IEnumerable<Type>? customKeyHolders = null, CancellationToken cancellationToken = default);
|
||||
Task UpdateSettingsAsync(Dictionary<string, object?> updatedSettings, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class SettingsService<TContext> : ISettingsService<TContext> where TContext : DbContext
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly Microsoft.Extensions.DependencyInjection.IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<SettingsService<TContext>>? _logger;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<SettingsService>? _logger;
|
||||
|
||||
// Fast In-Memory Cache: Key Schema: "KeyName"
|
||||
private readonly ConcurrentDictionary<string, string> _cache = new();
|
||||
// Fast In-Memory Cache: Key Schema: "KeyName" -> JSON string
|
||||
private readonly ConcurrentDictionary<string, string> _cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public SettingsService(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory scopeFactory, ILogger<SettingsService<TContext>>? logger = null)
|
||||
public SettingsService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ILogger<SettingsService>? logger = null)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -52,11 +56,13 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
|
||||
public Task<T> GetSettingAsync<T>(SettingKey<T> key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
return GetSettingInternalAsync(key.Name, key.DefaultValue, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SetSettingAsync<T>(SettingKey<T> key, T value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
return SetSettingInternalAsync(key.Name, value, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -96,11 +102,138 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
|
||||
#endregion
|
||||
|
||||
#region Core Engine Logik
|
||||
#region Bulk & Reflection Discovery
|
||||
|
||||
public async Task<List<DynamicSettingDto>> GetAllRegisteredSettingsAsync(
|
||||
IEnumerable<Type>? customKeyHolders = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var holderTypes = new List<Type> { typeof(CoreSettingKeys) };
|
||||
if (customKeyHolders != null)
|
||||
{
|
||||
holderTypes.AddRange(customKeyHolders);
|
||||
}
|
||||
|
||||
var resultList = new List<DynamicSettingDto>();
|
||||
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 1. Reflection auf allen SettingKey<T> Feldern
|
||||
foreach (var type in holderTypes.Distinct())
|
||||
{
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var fieldType = field.FieldType;
|
||||
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(SettingKey<>))
|
||||
{
|
||||
var valType = fieldType.GetGenericArguments()[0];
|
||||
var settingKeyObj = field.GetValue(null);
|
||||
if (settingKeyObj == null) continue;
|
||||
|
||||
var nameProp = fieldType.GetProperty("Name");
|
||||
var defaultProp = fieldType.GetProperty("DefaultValue");
|
||||
|
||||
var keyName = nameProp?.GetValue(settingKeyObj)?.ToString() ?? field.Name;
|
||||
if (seenKeys.Contains(keyName)) continue;
|
||||
seenKeys.Add(keyName);
|
||||
|
||||
var defVal = defaultProp?.GetValue(settingKeyObj);
|
||||
var typeName = MapToSimpleTypeName(valType);
|
||||
|
||||
// Aktuellen Wert aus DB / Cache lesen
|
||||
var currentRawValue = await GetSettingInternalObjectAsync(keyName, valType, defVal, cancellationToken);
|
||||
|
||||
resultList.Add(new DynamicSettingDto(
|
||||
Key: keyName,
|
||||
Value: currentRawValue,
|
||||
Type: typeName,
|
||||
Description: FormatDescriptionFromKey(keyName),
|
||||
UpdatedAt: DateTime.UtcNow
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Prüfen, ob in der DB weitere gespeicherte Settings existieren, die nicht im Code deklariert sind
|
||||
try
|
||||
{
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var dbContext = scope.ServiceProvider.GetService<ISettingsDbContext>();
|
||||
if (dbContext != null)
|
||||
{
|
||||
var dbSettings = await dbContext.DynamicSettings.AsNoTracking().ToListAsync(cancellationToken);
|
||||
foreach (var dbSetting in dbSettings)
|
||||
{
|
||||
if (!seenKeys.Contains(dbSetting.Key))
|
||||
{
|
||||
seenKeys.Add(dbSetting.Key);
|
||||
var (inferredVal, inferredType) = InferJsonValueAndType(dbSetting.ValueJson);
|
||||
resultList.Add(new DynamicSettingDto(
|
||||
Key: dbSetting.Key,
|
||||
Value: inferredVal,
|
||||
Type: inferredType,
|
||||
Description: FormatDescriptionFromKey(dbSetting.Key),
|
||||
UpdatedAt: dbSetting.LastUpdatedUtc
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "[SettingsService] Error reading database settings during GetAllRegisteredSettingsAsync.");
|
||||
}
|
||||
|
||||
return resultList.OrderBy(s => s.Key).ToList();
|
||||
}
|
||||
|
||||
public async Task UpdateSettingsAsync(Dictionary<string, object?> updatedSettings, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (updatedSettings == null || updatedSettings.Count == 0) return;
|
||||
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var dbContext = scope.ServiceProvider.GetService<ISettingsDbContext>();
|
||||
|
||||
foreach (var (key, rawValue) in updatedSettings)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key)) continue;
|
||||
|
||||
string jsonValue = NormalizeJsonValue(rawValue);
|
||||
_cache[key] = jsonValue;
|
||||
|
||||
if (dbContext != null)
|
||||
{
|
||||
var entity = await dbContext.DynamicSettings.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new SettingEntity
|
||||
{
|
||||
Key = key,
|
||||
ValueJson = jsonValue,
|
||||
LastUpdatedUtc = DateTime.UtcNow
|
||||
};
|
||||
dbContext.DynamicSettings.Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.ValueJson = jsonValue;
|
||||
entity.LastUpdatedUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dbContext != null)
|
||||
{
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Engine Logic
|
||||
|
||||
private async Task<T> GetSettingInternalAsync<T>(string key, T defaultValue, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. Zuerst im In-Memory Cache prüfen
|
||||
if (_cache.TryGetValue(key, out var cachedJson))
|
||||
{
|
||||
return DeserializeValue(cachedJson, defaultValue);
|
||||
@@ -108,17 +241,21 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TContext>(scope.ServiceProvider);
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var dbContext = scope.ServiceProvider.GetService<ISettingsDbContext>();
|
||||
if (dbContext == null)
|
||||
{
|
||||
var defaultJson = NormalizeJsonValue(defaultValue);
|
||||
_cache[key] = defaultJson;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 2. Aus DB der spezifischen TContext-Instanz laden
|
||||
var entity = await dbContext.Set<SettingEntity>()
|
||||
var entity = await dbContext.DynamicSettings
|
||||
.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
||||
|
||||
// 3. Falls noch nicht vorhanden: In DB anlegen (Seed on Demand)
|
||||
if (entity == null)
|
||||
{
|
||||
var defaultJson = JsonSerializer.Serialize(defaultValue);
|
||||
var defaultJson = NormalizeJsonValue(defaultValue);
|
||||
entity = new SettingEntity
|
||||
{
|
||||
Key = key,
|
||||
@@ -126,35 +263,76 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
LastUpdatedUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
dbContext.Set<SettingEntity>().Add(entity);
|
||||
dbContext.DynamicSettings.Add(entity);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_cache[key] = defaultJson;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// In Cache legen & Wert zurückgeben
|
||||
_cache[key] = entity.ValueJson;
|
||||
return DeserializeValue(entity.ValueJson, defaultValue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Setting '{Key}' could not be loaded or initialized in DB. Using default value in memory.", key);
|
||||
_cache[key] = JsonSerializer.Serialize(defaultValue);
|
||||
_logger?.LogWarning(ex, "[SettingsService] Setting '{Key}' could not be loaded or initialized in DB. Using default value.", key);
|
||||
_cache[key] = NormalizeJsonValue(defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<object?> GetSettingInternalObjectAsync(string key, Type valueType, object? defaultValue, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out var cachedJson))
|
||||
{
|
||||
return DeserializeObject(cachedJson, valueType, defaultValue);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var dbContext = scope.ServiceProvider.GetService<ISettingsDbContext>();
|
||||
if (dbContext == null) return defaultValue;
|
||||
|
||||
var entity = await dbContext.DynamicSettings.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
||||
if (entity == null)
|
||||
{
|
||||
var defaultJson = NormalizeJsonValue(defaultValue);
|
||||
entity = new SettingEntity
|
||||
{
|
||||
Key = key,
|
||||
ValueJson = defaultJson,
|
||||
LastUpdatedUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
dbContext.DynamicSettings.Add(entity);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_cache[key] = defaultJson;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
_cache[key] = entity.ValueJson;
|
||||
return DeserializeObject(entity.ValueJson, valueType, defaultValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetSettingInternalAsync<T>(string key, T value, CancellationToken cancellationToken)
|
||||
{
|
||||
var jsonValue = JsonSerializer.Serialize(value);
|
||||
var jsonValue = NormalizeJsonValue(value);
|
||||
_cache[key] = jsonValue;
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TContext>(scope.ServiceProvider);
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var dbContext = scope.ServiceProvider.GetService<ISettingsDbContext>();
|
||||
if (dbContext == null) return;
|
||||
|
||||
var entity = await dbContext.Set<SettingEntity>()
|
||||
var entity = await dbContext.DynamicSettings
|
||||
.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
@@ -165,7 +343,7 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
ValueJson = jsonValue,
|
||||
LastUpdatedUtc = DateTime.UtcNow
|
||||
};
|
||||
dbContext.Set<SettingEntity>().Add(entity);
|
||||
dbContext.DynamicSettings.Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -177,17 +355,91 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Setting '{Key}' could not be saved to DB.", key);
|
||||
_logger?.LogWarning(ex, "[SettingsService] Setting '{Key}' could not be saved to DB.", key);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeJsonValue(object? rawValue)
|
||||
{
|
||||
if (rawValue == null) return "null";
|
||||
|
||||
if (rawValue is JsonElement jsonElem)
|
||||
{
|
||||
if (jsonElem.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var str = jsonElem.GetString()?.Trim() ?? string.Empty;
|
||||
var unquoted = str.Trim('\"', ' ');
|
||||
if (bool.TryParse(unquoted, out var b)) return b ? "true" : "false";
|
||||
if (long.TryParse(unquoted, out var l)) return l.ToString();
|
||||
if (double.TryParse(unquoted, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) return d.ToString(CultureInfo.InvariantCulture);
|
||||
return JsonSerializer.Serialize(str);
|
||||
}
|
||||
return jsonElem.GetRawText();
|
||||
}
|
||||
|
||||
// Cache trotzdem aktualisieren
|
||||
_cache[key] = jsonValue;
|
||||
if (rawValue is string s)
|
||||
{
|
||||
var unquoted = s.Trim('\"', ' ');
|
||||
if (bool.TryParse(unquoted, out var b)) return b ? "true" : "false";
|
||||
if (long.TryParse(unquoted, out var l)) return l.ToString();
|
||||
if (double.TryParse(unquoted, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) return d.ToString(CultureInfo.InvariantCulture);
|
||||
return JsonSerializer.Serialize(s);
|
||||
}
|
||||
|
||||
if (rawValue is bool bVal) return bVal ? "true" : "false";
|
||||
if (rawValue is int or long or short or byte) return rawValue.ToString()!;
|
||||
if (rawValue is double or float or decimal) return Convert.ToString(rawValue, CultureInfo.InvariantCulture)!;
|
||||
|
||||
return JsonSerializer.Serialize(rawValue);
|
||||
}
|
||||
|
||||
private static T DeserializeValue<T>(string json, T defaultValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
var unquoted = json.Trim('\"', ' ');
|
||||
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(unquoted, out var b))
|
||||
{
|
||||
return (T)(object)b;
|
||||
}
|
||||
}
|
||||
else if (typeof(T) == typeof(int))
|
||||
{
|
||||
if (int.TryParse(unquoted, out var i))
|
||||
{
|
||||
return (T)(object)i;
|
||||
}
|
||||
}
|
||||
else if (typeof(T) == typeof(long))
|
||||
{
|
||||
if (long.TryParse(unquoted, out var l))
|
||||
{
|
||||
return (T)(object)l;
|
||||
}
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
if (double.TryParse(unquoted, NumberStyles.Any, CultureInfo.InvariantCulture, out var d))
|
||||
{
|
||||
return (T)(object)d;
|
||||
}
|
||||
}
|
||||
else if (typeof(T) == typeof(string))
|
||||
{
|
||||
var trimmed = json.Trim();
|
||||
if (trimmed.StartsWith("\"") && trimmed.EndsWith("\"") && trimmed.Length >= 2)
|
||||
{
|
||||
try { return (T)(object)(JsonSerializer.Deserialize<string>(trimmed) ?? trimmed.Trim('\"')); }
|
||||
catch { return (T)(object)trimmed.Trim('\"'); }
|
||||
}
|
||||
return (T)(object)trimmed;
|
||||
}
|
||||
|
||||
var result = JsonSerializer.Deserialize<T>(json);
|
||||
return result ?? defaultValue;
|
||||
}
|
||||
@@ -197,5 +449,106 @@ public class SettingsService<TContext> : ISettingsService<TContext> where TConte
|
||||
}
|
||||
}
|
||||
|
||||
private static object? DeserializeObject(string json, Type valueType, object? defaultValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
var unquoted = json.Trim('\"', ' ');
|
||||
|
||||
if (valueType == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(unquoted, out var b)) return b;
|
||||
}
|
||||
else if (valueType == typeof(int))
|
||||
{
|
||||
if (int.TryParse(unquoted, out var i)) return i;
|
||||
}
|
||||
else if (valueType == typeof(long))
|
||||
{
|
||||
if (long.TryParse(unquoted, out var l)) return l;
|
||||
}
|
||||
else if (valueType == typeof(double))
|
||||
{
|
||||
if (double.TryParse(unquoted, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) return d;
|
||||
}
|
||||
else if (valueType == typeof(string))
|
||||
{
|
||||
var trimmed = json.Trim();
|
||||
if (trimmed.StartsWith("\"") && trimmed.EndsWith("\"") && trimmed.Length >= 2)
|
||||
{
|
||||
try { return JsonSerializer.Deserialize<string>(trimmed) ?? trimmed.Trim('\"'); }
|
||||
catch { return trimmed.Trim('\"'); }
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize(json, valueType) ?? defaultValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static string MapToSimpleTypeName(Type type)
|
||||
{
|
||||
if (type == typeof(bool)) return "bool";
|
||||
if (type == typeof(int) || type == typeof(short) || type == typeof(byte) || type == typeof(long)) return "int";
|
||||
if (type == typeof(double) || type == typeof(float) || type == typeof(decimal)) return "double";
|
||||
return "string";
|
||||
}
|
||||
|
||||
private static (object? Value, string Type) InferJsonValueAndType(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return (string.Empty, "string");
|
||||
|
||||
try
|
||||
{
|
||||
var unquoted = json.Trim('\"', ' ');
|
||||
if (bool.TryParse(unquoted, out var b)) return (b, "bool");
|
||||
if (long.TryParse(unquoted, out var l)) return (l, "int");
|
||||
if (double.TryParse(unquoted, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) return (d, "double");
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
return root.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => (true, "bool"),
|
||||
JsonValueKind.False => (false, "bool"),
|
||||
JsonValueKind.Number when root.TryGetInt64(out var i) => (i, "int"),
|
||||
JsonValueKind.Number => (root.GetDouble(), "double"),
|
||||
JsonValueKind.String => (root.GetString(), "string"),
|
||||
_ => (json, "string")
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (json, "string");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatDescriptionFromKey(string key)
|
||||
{
|
||||
if (key.StartsWith("Logging.Channel.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"Logging-Kanal für {key.Substring(16)} (Ein/Aus)";
|
||||
}
|
||||
if (key.StartsWith("Feature.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"Feature-Toggle für {key.Substring(8)}";
|
||||
}
|
||||
if (key.StartsWith("Cache.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"Cache-Konfiguration ({key.Substring(6)})";
|
||||
}
|
||||
if (key.StartsWith("Scraper.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"Scraper-Konfiguration ({key.Substring(8)})";
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user