Auto stash before merge of "main" and "origin/main"
This commit is contained in:
parent
21d7128e74
commit
521190475d
41 changed files with 991 additions and 1150 deletions
|
|
@ -1,50 +0,0 @@
|
|||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Core.Configurations
|
||||
{
|
||||
public class AzureConfigurationManager
|
||||
{
|
||||
private static IConfigurationBuilder _configurationBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// This AppConfigBuilder assumes that AppConfigEndpoint and AppConfigLabelFilter are configured as Settings on Azure for the Application that needs them.
|
||||
/// AppConfigEndpoint would look like this: Endpoint=https://config-dec-test.azconfig.io;Id=0-l9-s0:foo;Secret=somesecret/bar
|
||||
/// </summary>
|
||||
/// <param name="localSettingsFile">
|
||||
/// Path relative to the base path stored in Microsoft.Extensions.Configuration.IConfigurationBuilder.Properties of builder.
|
||||
/// </param>
|
||||
public static IConfigurationBuilder AppConfigBuilder(string localSettingsFile)
|
||||
{
|
||||
if (_configurationBuilder == null)
|
||||
{
|
||||
var envConfiguration = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
|
||||
|
||||
var appConfigEndpoint = envConfiguration["AppConfigEndpoint"];
|
||||
var appConfigLabel = envConfiguration["AppConfigLabelFilter"];
|
||||
|
||||
_configurationBuilder = new Microsoft.Extensions.Configuration.ConfigurationBuilder();
|
||||
if (!string.IsNullOrEmpty(appConfigEndpoint))
|
||||
{
|
||||
_configurationBuilder
|
||||
.AddAzureAppConfiguration(options =>
|
||||
{
|
||||
options.Connect(appConfigEndpoint);
|
||||
options.Select(keyFilter: "*", labelFilter: appConfigLabel);
|
||||
})
|
||||
.AddEnvironmentVariables();
|
||||
}
|
||||
else
|
||||
{
|
||||
_configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
|
||||
_configurationBuilder.AddJsonFile(localSettingsFile, optional: false);
|
||||
}
|
||||
}
|
||||
return _configurationBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Core/Configurations/Common/KeyValueToJson.cs
Normal file
85
Core/Configurations/Common/KeyValueToJson.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Configurations.Common
|
||||
{
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static class KeyValueToJson
|
||||
{
|
||||
public static JObject Convert(IEnumerable<KeyValuePair<string, JToken>> pairs)
|
||||
{
|
||||
var root = new JObject();
|
||||
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var keys = pair.Key.Split(':');
|
||||
var current = root;
|
||||
|
||||
// Gennemgå hierarkiet og opret underobjekter, hvis de ikke eksisterer
|
||||
for (int i = 0; i < keys.Length - 1; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
|
||||
if (current[key] == null)
|
||||
{
|
||||
current[key] = new JObject();
|
||||
}
|
||||
|
||||
current = (JObject)current[key];
|
||||
}
|
||||
|
||||
// Håndter den sidste nøgle og tilføj værdien
|
||||
var lastKey = keys[keys.Length - 1];
|
||||
var value = ConvertValue(pair.Value);
|
||||
|
||||
// Hvis den sidste nøgle allerede eksisterer, tilføj til en liste
|
||||
if (current[lastKey] != null)
|
||||
{
|
||||
// Hvis den allerede er en liste, tilføj til listen
|
||||
if (current[lastKey].Type == JTokenType.Array)
|
||||
{
|
||||
((JArray)current[lastKey]).Add(value);
|
||||
}
|
||||
// Hvis den ikke er en liste, konverter til en liste
|
||||
else
|
||||
{
|
||||
var existingValue = current[lastKey];
|
||||
current[lastKey] = new JArray { existingValue, value };
|
||||
}
|
||||
}
|
||||
// Ellers tilføj som en enkelt værdi
|
||||
else
|
||||
{
|
||||
current[lastKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static JToken ConvertValue(object value)
|
||||
{
|
||||
// Hvis værdien allerede er en JToken, returner den direkte
|
||||
if (value is JToken token)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
// Konverter andre typer
|
||||
return value switch
|
||||
{
|
||||
int i => new JValue(i),
|
||||
double d => new JValue(d),
|
||||
bool b => new JValue(b),
|
||||
string s => new JValue(s),
|
||||
_ => new JValue(value.ToString())
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
namespace Core.Configurations
|
||||
{
|
||||
public class ConfigurationBuilder
|
||||
{
|
||||
private readonly List<IConfigurationProvider> _providers = new();
|
||||
public interface IConfigurationBuilder
|
||||
{
|
||||
ConfigurationBuilder AddProvider(IConfigurationProvider provider);
|
||||
IConfigurationRoot Build();
|
||||
List<IConfigurationProvider> ConfigurationProviders { get; }
|
||||
}
|
||||
|
||||
public ConfigurationBuilder AddProvider(IConfigurationProvider provider)
|
||||
{
|
||||
_providers.Add(provider);
|
||||
return this;
|
||||
}
|
||||
public class ConfigurationBuilder : IConfigurationBuilder
|
||||
{
|
||||
public List<IConfigurationProvider> ConfigurationProviders { get; private set; } = [];
|
||||
|
||||
public IConfigurationRoot Build()
|
||||
{
|
||||
// Her kan du implementere din egen sammenlægningslogik
|
||||
return new ConfigurationRoot(_providers);
|
||||
}
|
||||
}
|
||||
public ConfigurationBuilder AddProvider(IConfigurationProvider provider)
|
||||
{
|
||||
((IConfigurationBuilder)this).ConfigurationProviders.Add(provider);
|
||||
return this;
|
||||
}
|
||||
public IConfigurationRoot Build()
|
||||
{
|
||||
foreach (var provider in ConfigurationProviders)
|
||||
{
|
||||
provider.Build();
|
||||
}
|
||||
//TODO: we need to come up with merge strategy
|
||||
|
||||
public class ConfigurationRoot : IConfigurationRoot
|
||||
{
|
||||
public ConfigurationRoot(List<IConfigurationProvider> configurationProviders)
|
||||
{
|
||||
return new ConfigurationRoot(ConfigurationProviders);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public T GetSection<T>(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
public static class ConfigurationPredicateExtensions
|
||||
{
|
||||
public class ConfigurationRoot : IConfigurationRoot
|
||||
{
|
||||
List<IConfigurationProvider> IConfigurationRoot.ConfigurationProviders { get; set; }
|
||||
|
||||
public static IConfigurationSection GetSection(this IConfigurationRoot configurationSection, string key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public ConfigurationRoot(List<IConfigurationProvider> configurationProviders)
|
||||
{
|
||||
((IConfigurationRoot)this).ConfigurationProviders = configurationProviders;
|
||||
}
|
||||
|
||||
public static T Get<T>(this IConfigurationSection configuration, string key)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
public interface IConfigurationProvider
|
||||
{
|
||||
Dictionary<string, object> Configuration();
|
||||
}
|
||||
}
|
||||
public static class ConfigurationBinder
|
||||
{
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
return path?.Replace(":", ".", StringComparison.Ordinal) ?? string.Empty;
|
||||
}
|
||||
public static string GetConnectionString(this IConfigurationRoot configuration, string name)
|
||||
{
|
||||
return configuration.GetSection("ConnectionStrings").Get<string>(name);
|
||||
}
|
||||
public static IConfigurationSection GetSection(this IConfigurationRoot configuration, string path)
|
||||
{
|
||||
JToken value = null;
|
||||
foreach (var provider in configuration.ConfigurationProviders)
|
||||
{
|
||||
var test = provider.Configuration().SelectToken(NormalizePath(path));
|
||||
|
||||
public interface IConfigurationSection
|
||||
{
|
||||
string Key { get; }
|
||||
string Path { get; }
|
||||
string Value { get; set; }
|
||||
}
|
||||
if (test != null)
|
||||
value = test;
|
||||
}
|
||||
|
||||
return new ConfigurationSection { Path = path, Key = path.Split(':').Last(), Value = value };
|
||||
}
|
||||
public static T Get<T>(this IConfigurationRoot configuration, string path)
|
||||
{
|
||||
JToken value = null;
|
||||
foreach (var provider in configuration.ConfigurationProviders)
|
||||
{
|
||||
var test = provider.Configuration().SelectToken(NormalizePath(path));
|
||||
|
||||
if (test != null)
|
||||
value = test;
|
||||
}
|
||||
|
||||
return value.ToObject<T>();
|
||||
}
|
||||
public static T Get<T>(this IConfigurationSection configuration, string path)
|
||||
{
|
||||
var value = configuration.Value.SelectToken(NormalizePath(path)).ToObject<T>();
|
||||
return value;
|
||||
}
|
||||
public static T ToObject<T>(this IConfigurationSection configuration)
|
||||
{
|
||||
var value = configuration.Value.ToObject<T>();
|
||||
return value;
|
||||
}
|
||||
|
||||
[Obsolete("Use ToObject")]
|
||||
public static T Get<T>(this IConfigurationSection configuration)
|
||||
{
|
||||
return configuration.Value.ToObject<T>();
|
||||
}
|
||||
|
||||
}
|
||||
public interface IConfigurationProvider
|
||||
{
|
||||
void Build();
|
||||
Newtonsoft.Json.Linq.JObject Configuration();
|
||||
}
|
||||
|
||||
public class ConfigurationSection : IConfigurationSection
|
||||
{
|
||||
public required string Path { get; set; }
|
||||
public required string Key { get; set; }
|
||||
|
||||
public required JToken Value { get; set; }
|
||||
|
||||
}
|
||||
public interface IConfigurationSection
|
||||
{
|
||||
string Path { get; }
|
||||
string Key { get; }
|
||||
Newtonsoft.Json.Linq.JToken Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Configurations
|
||||
namespace Core.Configurations
|
||||
{
|
||||
public interface IConfigurationRoot
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal List<IConfigurationProvider> ConfigurationProviders { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
using Core.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Core.Configurations.JsonConfigProvider
|
||||
{
|
||||
public static class JsonConfigExtension
|
||||
{
|
||||
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string configurationFilePath = "appconfiguration.json", bool? optional = true, bool? reloadOnChange = false)
|
||||
{
|
||||
return builder.AddProvider(new JsonConfigProvider(builder, configurationFilePath, optional ?? true, reloadOnChange ?? false));
|
||||
}
|
||||
}
|
||||
|
||||
public interface IHasConfigurationFilePath
|
||||
{
|
||||
string ConfigurationFilePath { get; }
|
||||
}
|
||||
public class JsonConfigProvider : IConfigurationProvider, IHasConfigurationFilePath
|
||||
{
|
||||
private readonly IConfigurationBuilder _builder;
|
||||
private readonly bool _reloadOnChange;
|
||||
JObject _configuration;
|
||||
public string ConfigurationFilePath { get; private set; }
|
||||
|
||||
public JsonConfigProvider() { }
|
||||
|
||||
public JsonConfigProvider(IConfigurationBuilder builder, string configurationFilePath, bool optional, bool reloadOnChange)
|
||||
{
|
||||
if (!optional && !File.Exists(configurationFilePath))
|
||||
throw new ConfigurationException($"File not found, path: {configurationFilePath}");
|
||||
if (optional && !File.Exists(configurationFilePath))
|
||||
return;
|
||||
|
||||
ConfigurationFilePath = configurationFilePath;
|
||||
_builder = builder;
|
||||
_reloadOnChange = reloadOnChange;
|
||||
}
|
||||
|
||||
public void Build()
|
||||
{
|
||||
using (StreamReader file = File.OpenText(ConfigurationFilePath))
|
||||
using (JsonTextReader reader = new JsonTextReader(file))
|
||||
{
|
||||
_configuration = (JObject)JToken.ReadFrom(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public JObject Configuration()
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Npgsql;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Configurations.PostgresqlConfigurationBuilder
|
||||
{
|
||||
public class PostgresConfigurationSource : Microsoft.Extensions.Configuration.IConfigurationSource
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _channel;
|
||||
private readonly string _configurationQuery;
|
||||
|
||||
public PostgresConfigurationSource(string connectionString, string channel, string configurationQuery)
|
||||
{
|
||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||
_configurationQuery = configurationQuery ?? throw new ArgumentNullException(nameof(configurationQuery));
|
||||
}
|
||||
|
||||
public Microsoft.Extensions.Configuration.IConfigurationProvider Build(IConfigurationBuilder builder)
|
||||
{
|
||||
return new PostgresConfigurationProvider(_connectionString, _channel, _configurationQuery);
|
||||
}
|
||||
}
|
||||
|
||||
public class PostgresConfigurationProvider : ConfigurationProvider, IDisposable
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _channel;
|
||||
private readonly string _configurationQuery;
|
||||
private readonly NpgsqlConnection _listenerConnection;
|
||||
private bool _disposedValue;
|
||||
|
||||
public PostgresConfigurationProvider(string connectionString, string channel, string configurationQuery)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
_channel = channel;
|
||||
_configurationQuery = configurationQuery;
|
||||
_listenerConnection = new NpgsqlConnection(connectionString);
|
||||
|
||||
// Start listening for notifications
|
||||
StartListening();
|
||||
}
|
||||
|
||||
private async void StartListening()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _listenerConnection.OpenAsync();
|
||||
_listenerConnection.Notification += OnNotificationReceived;
|
||||
|
||||
using var cmd = new NpgsqlCommand($"LISTEN {_channel};", _listenerConnection);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error and possibly retry
|
||||
Console.WriteLine($"Error starting listener: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNotificationReceived(object sender, NpgsqlNotificationEventArgs e)
|
||||
{
|
||||
if (e.Channel == _channel)
|
||||
{
|
||||
// Reload configuration and notify
|
||||
Load();
|
||||
OnReload();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
using (var connection = new NpgsqlConnection(_connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using var cmd = new NpgsqlCommand(_configurationQuery, connection);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
string key = reader.GetString(0);
|
||||
string value = reader.GetString(1);
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Data = data;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_listenerConnection?.Dispose();
|
||||
}
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
namespace Core.Configurations.PostgresqlConfigurationBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// LISTEN / NOTIFY in Postgresql
|
||||
/// </summary>
|
||||
public static class PostgresConfigurationExtensions
|
||||
{
|
||||
public static IConfigurationBuilder AddPostgresConfiguration(
|
||||
this IConfigurationBuilder builder,
|
||||
Action<PostgresConfigurationOptions> setupAction)
|
||||
{
|
||||
var options = new PostgresConfigurationOptions();
|
||||
setupAction(options);
|
||||
|
||||
builder.Add(new PostgresConfigurationSource(
|
||||
options.ConnectionString,
|
||||
options.Channel,
|
||||
options.ConfigurationQuery));
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public class PostgresConfigurationOptions
|
||||
{
|
||||
public string ConnectionString { get; set; }
|
||||
public string Channel { get; set; }
|
||||
public string ConfigurationQuery { get; set; }
|
||||
}
|
||||
}
|
||||
14
Core/Configurations/SmartConfigProvider/AppConfiguration.cs
Normal file
14
Core/Configurations/SmartConfigProvider/AppConfiguration.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace Core.Configurations.SmartConfigProvider;
|
||||
public class AppConfiguration
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
public object Value { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
public DateTime? ValidFrom { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
public Guid? Etag { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using System.Data;
|
||||
using Core.Configurations.SmartConfiguration;
|
||||
using Insight.Database;
|
||||
|
||||
namespace Core.Configurations.SmartConfigProvider;
|
||||
public class ConfigurationRepository : IConfigurationRepository
|
||||
{
|
||||
private readonly IDbConnection _connection;
|
||||
|
||||
public ConfigurationRepository(IDbConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
}
|
||||
public ConfigurationRepository(string connectionString)
|
||||
{
|
||||
_connection = new Npgsql.NpgsqlConnection(connectionString);
|
||||
}
|
||||
public IEnumerable<AppConfiguration> GetActiveConfigurations()
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT id, ""key"", value, label, content_type,
|
||||
valid_from, expires_at, created_at, modified_at, etag
|
||||
FROM app_configuration
|
||||
WHERE CURRENT_TIMESTAMP BETWEEN valid_from AND expires_at
|
||||
OR (valid_from IS NULL AND expires_at IS NULL)";
|
||||
|
||||
|
||||
return _connection.QuerySql<AppConfiguration>(sql);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
using Core.Configurations.SmartConfigProvider;
|
||||
|
||||
namespace Core.Configurations.SmartConfiguration;
|
||||
public interface IConfigurationRepository
|
||||
{
|
||||
IEnumerable<AppConfiguration> GetActiveConfigurations();
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using Core.Configurations.JsonConfigProvider;
|
||||
using Core.Configurations.SmartConfigProvider;
|
||||
using Core.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Core.Configurations.SmartConfig
|
||||
{
|
||||
public static class SmartConfigExtension
|
||||
{
|
||||
public static IConfigurationBuilder AddSmartConfig(this IConfigurationBuilder builder, string configKey = "DefaultConnection", string path = null)
|
||||
{
|
||||
return builder.AddProvider(new SmartConfigProvider(builder, configKey, path));
|
||||
}
|
||||
}
|
||||
|
||||
public class SmartConfigProvider : IConfigurationProvider
|
||||
{
|
||||
string _configKey;
|
||||
string _connectionString;
|
||||
string _path;
|
||||
IConfigurationBuilder _builder;
|
||||
|
||||
Newtonsoft.Json.Linq.JObject _configuration;
|
||||
|
||||
|
||||
public SmartConfigProvider() { }
|
||||
|
||||
public SmartConfigProvider(IConfigurationBuilder builder, string configKey, string configurationFilePath)
|
||||
{
|
||||
_builder = builder;
|
||||
_configKey = configKey;
|
||||
_path = configurationFilePath;
|
||||
SetConnectionString();
|
||||
}
|
||||
|
||||
void SetConnectionString()
|
||||
{
|
||||
var carrier = _builder.ConfigurationProviders.OfType<IHasConfigurationFilePath>().SingleOrDefault();
|
||||
|
||||
if (carrier?.ConfigurationFilePath is null && _path is null)
|
||||
throw new ConfigurationException($"Expected a previous added ConfigurationProvider with IHasConfigurationFilePath or a configurationFilePath where to find the appsettingsfile");
|
||||
|
||||
_path ??= carrier.ConfigurationFilePath;
|
||||
|
||||
if (!File.Exists(_path))
|
||||
throw new ConfigurationException($"File not found, configurationFilePath: {_path}");
|
||||
|
||||
|
||||
using (StreamReader file = File.OpenText(_path))
|
||||
using (JsonTextReader reader = new JsonTextReader(file))
|
||||
{
|
||||
var jsonConfiguration = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.ReadFrom(reader);
|
||||
|
||||
_connectionString = jsonConfiguration.SelectToken($"ConnectionStrings.{_configKey}")?.ToString();
|
||||
}
|
||||
}
|
||||
public void Build()
|
||||
{
|
||||
var repository = new ConfigurationRepository(_connectionString);
|
||||
var configs = repository.GetActiveConfigurations();
|
||||
|
||||
var pairs = configs.Select(x => new KeyValuePair<string, JToken>(x.Key, JToken.Parse(x.Value.ToString())));
|
||||
|
||||
_configuration = Common.KeyValueToJson.Convert(pairs);
|
||||
|
||||
}
|
||||
|
||||
public Newtonsoft.Json.Linq.JObject Configuration()
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
namespace Core.Configurations.SmartConfiguration;
|
||||
public class AppConfiguration
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
public DateTime? ValidFrom { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
public Guid? Etag { get; set; }
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Core.Configurations.SmartConfiguration
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static T Get<T>(this IConfigurationSection section) where T : class
|
||||
{
|
||||
if (section is JsonConfigurationSection jsonSection)
|
||||
{
|
||||
var token = jsonSection.GetToken();
|
||||
return token?.ToObject<T>();
|
||||
}
|
||||
throw new InvalidOperationException("Section is not a JsonConfigurationSection");
|
||||
}
|
||||
|
||||
public static T GetValue<T>(this IConfigurationSection section, string key)
|
||||
{
|
||||
if (section is JsonConfigurationSection jsonSection)
|
||||
{
|
||||
var token = jsonSection.GetToken().SelectToken(key.Replace(":", "."));
|
||||
return token.ToObject<T>();
|
||||
}
|
||||
throw new InvalidOperationException("Section is not a JsonConfigurationSection");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
using System.Data;
|
||||
using Insight.Database;
|
||||
|
||||
namespace Core.Configurations.SmartConfiguration;
|
||||
public class ConfigurationRepository : IConfigurationRepository
|
||||
{
|
||||
private readonly IDbConnection _connection;
|
||||
|
||||
public ConfigurationRepository(IDbConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppConfiguration>> GetActiveConfigurations()
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT id, key, value, label, content_type, valid_from, expires_at, created_at, modified_at, etag
|
||||
FROM prod.app_configuration
|
||||
WHERE (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
|
||||
AND (valid_from IS NULL OR valid_from < CURRENT_TIMESTAMP)";
|
||||
|
||||
return await _connection.QueryAsync<AppConfiguration>(sql);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
namespace Core.Configurations.SmartConfiguration;
|
||||
public interface IConfigurationRepository
|
||||
{
|
||||
Task<IEnumerable<AppConfiguration>> GetActiveConfigurations();
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using System.Data;
|
||||
|
||||
namespace Core.Configurations.SmartConfiguration;
|
||||
public class JsonConfiguration : Microsoft.Extensions.Configuration.IConfiguration
|
||||
{
|
||||
private readonly JObject _data;
|
||||
|
||||
public JsonConfiguration(JObject data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get => _data.SelectToken(key.Replace(":", "."))?.ToString();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Microsoft.Extensions.Configuration.IConfigurationSection GetSection(string key) => null;
|
||||
//new JsonConfigurationSection(_data, key);
|
||||
|
||||
public IEnumerable<IConfigurationSection> GetChildren() =>
|
||||
_data.Properties().Select(p => new JsonConfigurationSection(_data, p.Name));
|
||||
|
||||
public IChangeToken GetReloadToken() => throw new NotImplementedException();
|
||||
|
||||
IEnumerable<Microsoft.Extensions.Configuration.IConfigurationSection> IConfiguration.GetChildren()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Core.Configurations.SmartConfiguration
|
||||
{
|
||||
public class JsonConfigurationSection : IConfigurationSection
|
||||
{
|
||||
private readonly JObject _data;
|
||||
private readonly string _path;
|
||||
private readonly string _normalizedPath;
|
||||
|
||||
public JsonConfigurationSection(JObject data, string path)
|
||||
{
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
_path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
_normalizedPath = NormalizePath(_path);
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
|
||||
var token = _data.SelectToken($"{_normalizedPath}.{NormalizePath(key)}");
|
||||
return token?.ToString();
|
||||
}
|
||||
set => throw new NotImplementedException("Setting values is not supported.");
|
||||
}
|
||||
|
||||
public string Key => _path.Split(':').Last();
|
||||
public string Path => _path;
|
||||
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var token = _data.SelectToken(_normalizedPath);
|
||||
return token?.ToString();
|
||||
}
|
||||
set => throw new NotImplementedException("Setting values is not supported.");
|
||||
}
|
||||
|
||||
public Microsoft.Extensions.Configuration.IConfigurationSection GetSection(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
|
||||
return null;// new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? key : $"{_path}:{key}");
|
||||
}
|
||||
|
||||
public JToken GetToken() => _data.SelectToken(_normalizedPath);
|
||||
|
||||
public IEnumerable<IConfigurationSection> GetChildren()
|
||||
{
|
||||
var token = _data.SelectToken(_normalizedPath);
|
||||
if (token is JObject obj)
|
||||
{
|
||||
return obj.Properties()
|
||||
.Select(p => new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? p.Name : $"{_path}:{p.Name}"));
|
||||
}
|
||||
return Enumerable.Empty<IConfigurationSection>();
|
||||
}
|
||||
|
||||
public T Get<T>() where T : class
|
||||
{
|
||||
var token = _data.SelectToken(_normalizedPath);
|
||||
return token?.ToObject<T>();
|
||||
}
|
||||
|
||||
public IChangeToken GetReloadToken() => new ConfigurationReloadToken();
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
return path?.Replace(":", ".", StringComparison.Ordinal) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Core.Configurations.SmartConfiguration
|
||||
{
|
||||
public class KeyValueConfigurationBuilder
|
||||
{
|
||||
private readonly IConfigurationRepository _repository;
|
||||
private readonly JObject _rootObject = new();
|
||||
private IConfiguration _configuration;
|
||||
private readonly object _configurationLock = new();
|
||||
|
||||
public KeyValueConfigurationBuilder(IConfigurationRepository repository)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads configurations from the repository and builds the configuration tree.
|
||||
/// </summary>
|
||||
public async Task LoadConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configurations = await _repository.GetActiveConfigurations();
|
||||
foreach (var config in configurations)
|
||||
AddKeyValue(config.Key, config.Value);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception or handle it as needed
|
||||
throw new InvalidOperationException("Failed to load configurations.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-value pair to the configuration tree.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to add.</param>
|
||||
/// <param name="jsonValue">The JSON value to add.</param>
|
||||
public void AddKeyValue(string key, string jsonValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
if (string.IsNullOrEmpty(jsonValue))
|
||||
throw new ArgumentNullException(nameof(jsonValue));
|
||||
|
||||
try
|
||||
{
|
||||
var valueObject = JsonConvert.DeserializeObject<JObject>(jsonValue);
|
||||
var parts = key.Split(':');
|
||||
|
||||
JObject current = _rootObject;
|
||||
for (int i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
var part = parts[i];
|
||||
if (!current.ContainsKey(part))
|
||||
{
|
||||
current[part] = new JObject();
|
||||
}
|
||||
current = (JObject)current[part];
|
||||
}
|
||||
|
||||
current[parts[^1]] = valueObject;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new ArgumentException("Invalid JSON value.", nameof(jsonValue), ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Builds the configuration instance.
|
||||
/// </summary>
|
||||
/// <returns>The built <see cref="IConfiguration"/> instance.</returns>
|
||||
public IConfiguration Build()
|
||||
{
|
||||
_configuration = new JsonConfiguration(_rootObject);
|
||||
return _configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Core.Configurations
|
||||
{
|
||||
public class SmartConfigManager
|
||||
{
|
||||
private static IConfigurationBuilder _configurationBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// This AppConfigBuilder assumes that AppConfigEndpoint and AppConfigLabelFilter are configured as Settings on Azure for the Application that needs them.
|
||||
/// AppConfigEndpoint would look like this: Endpoint=https://config-dec-test.azconfig.io;Id=0-l9-s0:foo;Secret=somesecret/bar
|
||||
/// </summary>
|
||||
/// <param name="localSettingsFile">
|
||||
/// Path relative to the base path stored in Microsoft.Extensions.Configuration.IConfigurationBuilder.Properties of builder.
|
||||
/// </param>
|
||||
public static IConfigurationBuilder AddSmartConfigBuilder(string localSettingsFile)
|
||||
{
|
||||
if (_configurationBuilder == null)
|
||||
{
|
||||
var envConfiguration = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
var appConfigEndpoint = envConfiguration["AppConfigEndpoint"];
|
||||
var appConfigLabel = envConfiguration["AppConfigLabelFilter"];
|
||||
|
||||
_configurationBuilder = new Microsoft.Extensions.Configuration.ConfigurationBuilder();
|
||||
if (!string.IsNullOrEmpty(appConfigEndpoint))
|
||||
{
|
||||
_configurationBuilder
|
||||
.AddAzureAppConfiguration(options =>
|
||||
{
|
||||
options.Connect(appConfigEndpoint);
|
||||
options.Select(keyFilter: "*", labelFilter: appConfigLabel);
|
||||
})
|
||||
.AddEnvironmentVariables();
|
||||
}
|
||||
else
|
||||
{
|
||||
_configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
|
||||
_configurationBuilder.AddJsonFile(localSettingsFile, optional: false);
|
||||
}
|
||||
}
|
||||
return _configurationBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka" Version="1.5.32" />
|
||||
<PackageReference Include="Autofac" Version="8.1.1" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Insight.Database" Version="8.0.1" />
|
||||
<PackageReference Include="Insight.Database.Providers.PostgreSQL" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.22.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
|
||||
<PackageReference Include="npgsql" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka" Version="1.5.32" />
|
||||
<PackageReference Include="Autofac" Version="8.1.1" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Insight.Database" Version="8.0.1" />
|
||||
<PackageReference Include="Insight.Database.Providers.PostgreSQL" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.22.0" />
|
||||
<PackageReference Include="npgsql" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Configurations\AzureAppConfigurationProvider\" />
|
||||
<Folder Include="Configurations\PostgresqlConfigurationBuilder\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue