From 96c6df015b7a4adfab0ebccd4988d16933d70fca Mon Sep 17 00:00:00 2001 From: Tho Ho Date: Wed, 15 May 2024 01:21:51 +0800 Subject: [PATCH] Add MapConfiguration --- Directory.Build.props | 2 +- .../ConfigurationBuilderBuilder.cs | 5 ++ .../ConfigurationBuilderExtensions.cs | 17 +++++ .../MapConfigurationProvider.cs | 64 +++++++++++++++++++ .../MapConfigurationSource.cs | 15 +++++ .../ConfigurationBuilderBuilderTest.cs | 63 +++++++++++++++++- .../appsettings.Development.json | 5 +- .../appsettings.Map.json | 9 +++ .../appsettings.Production.json | 5 +- 9 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 src/NetLah.Extensions.Configuration/MapConfigurationProvider.cs create mode 100644 src/NetLah.Extensions.Configuration/MapConfigurationSource.cs create mode 100644 test/NetLah.Extensions.Configuration.Test/appsettings.Map.json diff --git a/Directory.Build.props b/Directory.Build.props index 657c855..8d32392 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,7 +19,7 @@ true - 10.0 + 12.0 enable enable diff --git a/src/NetLah.Extensions.Configuration/ConfigurationBuilderBuilder.cs b/src/NetLah.Extensions.Configuration/ConfigurationBuilderBuilder.cs index 438cbb8..01d0a65 100644 --- a/src/NetLah.Extensions.Configuration/ConfigurationBuilderBuilder.cs +++ b/src/NetLah.Extensions.Configuration/ConfigurationBuilderBuilder.cs @@ -189,6 +189,11 @@ public ConfigurationBuilderBuilder WithTransformConfiguration(string sectionKey return WithAddPostConfiguration(builder => builder.AddTransformConfiguration(sectionKey)); } + public ConfigurationBuilderBuilder WithMapConfiguration(string sectionKey = "MapConfiguration") + { + return WithAddPostConfiguration(builder => builder.AddMapConfiguration(sectionKey)); + } + public static ConfigurationBuilderBuilder Create(string[]? args = null) => new ConfigurationBuilderBuilder() .WithCommandLines(args) diff --git a/src/NetLah.Extensions.Configuration/ConfigurationBuilderExtensions.cs b/src/NetLah.Extensions.Configuration/ConfigurationBuilderExtensions.cs index 30a6ac5..0fe9e81 100644 --- a/src/NetLah.Extensions.Configuration/ConfigurationBuilderExtensions.cs +++ b/src/NetLah.Extensions.Configuration/ConfigurationBuilderExtensions.cs @@ -24,6 +24,23 @@ public static TConfigurationBuilder AddTransformConfiguration(this TConfigurationBuilder configBuilder, string sectionKey = "MapConfiguration") + where TConfigurationBuilder : IConfigurationBuilder + { + if (string.IsNullOrWhiteSpace(sectionKey)) + { + throw new ArgumentException($"{nameof(sectionKey)} is required", nameof(sectionKey)); + } + + var configuration = configBuilder is IConfigurationRoot configurationRoot + ? configurationRoot + : configBuilder.Build(); + + configBuilder.Add(new MapConfigurationSource(configuration, sectionKey)); + + return configBuilder; + } + public static TConfigurationBuilder AddAddFileConfiguration(this TConfigurationBuilder configBuilder, Action? configureOptions = null, string sectionKey = "AddFile", bool? throwIfNotSupport = null) where TConfigurationBuilder : IConfigurationBuilder { diff --git a/src/NetLah.Extensions.Configuration/MapConfigurationProvider.cs b/src/NetLah.Extensions.Configuration/MapConfigurationProvider.cs new file mode 100644 index 0000000..42626a3 --- /dev/null +++ b/src/NetLah.Extensions.Configuration/MapConfigurationProvider.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; + +namespace NetLah.Extensions.Configuration; + +public class MapConfigurationProvider(MapConfigurationSource source) : ConfigurationProvider +{ + private readonly MapConfigurationSource _source = source; + private object? _lock = null; + private IChangeToken? _token; + private IConfigurationSection? _configurationSection; + + private void OnChange(object? obj) + { + Data.Clear(); + InternalLoad(); + OnReload(); + } + + public override void Load() + { + InternalLoad(); + + if (_configurationSection != null && Interlocked.CompareExchange(ref _lock, new object(), null) == null) + { + _token = _configurationSection.GetReloadToken(); + _token.RegisterChangeCallback(OnChange, this); + } + } + + private void InternalLoad() + { + var configuration = _source.Configuration; + _configurationSection ??= configuration.GetSection(_source.SectionKey); + + foreach (var item in _configurationSection.GetChildren()) + { + if (item.Value is { } keyValue) + { + TryParse(keyValue); + } + else + { + var key1 = item["From"] ?? item["Source"]; + var key2 = item["To"] ?? item["Destination"] ?? item["Dest"]; + if (!string.IsNullOrEmpty(key1) && !string.IsNullOrEmpty(key2) && configuration[key1] is { } value) + { + Data[key2] = value; + } + } + } + + void TryParse(string keyValue) + { + var pos = keyValue.IndexOf('='); + var key1 = keyValue[..pos]; + var key2 = keyValue[(pos + 1)..]; + if (configuration[key1] is { } value) + { + Data[key2] = value; + } + } + } +} diff --git a/src/NetLah.Extensions.Configuration/MapConfigurationSource.cs b/src/NetLah.Extensions.Configuration/MapConfigurationSource.cs new file mode 100644 index 0000000..9760dec --- /dev/null +++ b/src/NetLah.Extensions.Configuration/MapConfigurationSource.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Configuration; + +namespace NetLah.Extensions.Configuration; + +public class MapConfigurationSource(IConfiguration configuration, string sectionKey) : IConfigurationSource +{ + public IConfiguration Configuration { get; } = configuration; + + public string SectionKey { get; } = sectionKey; + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new MapConfigurationProvider(this); + } +} diff --git a/test/NetLah.Extensions.Configuration.Test/ConfigurationBuilderBuilderTest.cs b/test/NetLah.Extensions.Configuration.Test/ConfigurationBuilderBuilderTest.cs index 0c57c24..8be4989 100644 --- a/test/NetLah.Extensions.Configuration.Test/ConfigurationBuilderBuilderTest.cs +++ b/test/NetLah.Extensions.Configuration.Test/ConfigurationBuilderBuilderTest.cs @@ -94,12 +94,15 @@ private static void AssertProduction(IConfiguration configuration) { Assert.Equal("MainValue1", configuration["MainKey"]); Assert.Equal("EnvironmentProductionValue1", configuration["EnvironmentKey"]); + Assert.Equal("ProductionSubValue1", configuration["ProductionSection:SubKey"]); + Assert.Null(configuration["SecondaryKey"]); } private static void AssertDevelopment(IConfiguration configuration) { Assert.Equal("MainValue1", configuration["MainKey"]); Assert.Equal("EnvironmentDevelopmentValue1", configuration["EnvironmentKey"]); + Assert.Equal("DevelopmentSubValue1", configuration["DevelopmentSection:SubKey"]); } private static void AssertCommandLines(IConfiguration configuration) @@ -564,7 +567,9 @@ public void Build_Production_changeTo_dev_Success() null, }); - AssertDevelopment(configuration2); + Assert.Equal("MainValue1", configuration2["MainKey"]); + Assert.Equal("EnvironmentDevelopmentValue1", configuration2["EnvironmentKey"]); + Assert.Null(configuration2["DevelopmentSection:SubKey"]); AssertCommandLines(configuration2); } @@ -1433,4 +1438,60 @@ public void AddFileConfigurationProduction_KeyPerFile_Handler() Assert.Equal("Key-Per-File/mainValue line0", configuration["MainKey"]); Assert.Equal("KeyPerFileSection__Add-File-Source.txt", configuration["KeyPerFileSection:Add-File-Source"]); } + + [Fact] + public void EnvironmentNameMapConfiguration() + { + var configuration = ConfigurationBuilderBuilder.Create(["/CommandLineSection:SubKey=CommandLineSection-SubKeyValue"]) + .WithEnvironment("Map") + .WithMapConfiguration() + .BuildConfigurationRoot(); + + AssertProviders(configuration, new[] { + "JsonConfigurationProvider", + "JsonConfigurationProvider", + "EnvironmentVariablesConfigurationProvider", + "CommandLineConfigurationProvider", + "MapConfigurationProvider", + }, new[] { + "appsettings.json", + "appsettings.Map.json", + null, + null, + null, + }); + + Assert.Equal("MainValue1", configuration["MainKey"]); + Assert.Equal("MainValue1", configuration["SecondaryKey"]); + Assert.Equal("CommandLineSection-SubKeyValue", configuration["CommandLineSection:SubKey"]); + Assert.Equal("CommandLineSection-SubKeyValue", configuration["MapSection:SubKey"]); + } + + [Fact] + public void ConfigurationBuilder_EnvironmentNameMapConfiguration() + { + var configuration = CreateDefaultBuilder(["/CommandLineSection:SubKey=CommandLineSection-SubKeyValue"], "Map") + .AddMapConfiguration() + .Build(); + + AssertProviders(configuration, [ + "JsonConfigurationProvider", + "JsonConfigurationProvider", + "EnvironmentVariablesConfigurationProvider", + "CommandLineConfigurationProvider", + "MapConfigurationProvider", + ], [ + "appsettings.json", + "appsettings.Map.json", + null, + null, + null, + ]); + + Assert.Equal("MainValue1", configuration["MainKey"]); + Assert.Equal("MainValue1", configuration["SecondaryKey"]); + Assert.Equal("CommandLineSection-SubKeyValue", configuration["CommandLineSection:SubKey"]); + Assert.Equal("CommandLineSection-SubKeyValue", configuration["MapSection:SubKey"]); + } + } diff --git a/test/NetLah.Extensions.Configuration.Test/appsettings.Development.json b/test/NetLah.Extensions.Configuration.Test/appsettings.Development.json index 34e782a..8a2dd89 100644 --- a/test/NetLah.Extensions.Configuration.Test/appsettings.Development.json +++ b/test/NetLah.Extensions.Configuration.Test/appsettings.Development.json @@ -1,3 +1,6 @@ { - "EnvironmentKey": "EnvironmentDevelopmentValue1" + "EnvironmentKey": "EnvironmentDevelopmentValue1", + "DevelopmentSection": { + "SubKey": "DevelopmentSubValue1" + } } diff --git a/test/NetLah.Extensions.Configuration.Test/appsettings.Map.json b/test/NetLah.Extensions.Configuration.Test/appsettings.Map.json new file mode 100644 index 0000000..7019731 --- /dev/null +++ b/test/NetLah.Extensions.Configuration.Test/appsettings.Map.json @@ -0,0 +1,9 @@ +{ + "MapConfiguration": [ + "MainKey=SecondaryKey", + { + "From": "CommandLineSection:SubKey", + "To": "MapSection:SubKey" + } + ] +} diff --git a/test/NetLah.Extensions.Configuration.Test/appsettings.Production.json b/test/NetLah.Extensions.Configuration.Test/appsettings.Production.json index c61def0..1a0c468 100644 --- a/test/NetLah.Extensions.Configuration.Test/appsettings.Production.json +++ b/test/NetLah.Extensions.Configuration.Test/appsettings.Production.json @@ -1,3 +1,6 @@ { - "EnvironmentKey": "EnvironmentProductionValue1" + "EnvironmentKey": "EnvironmentProductionValue1", + "ProductionSection": { + "SubKey": "ProductionSubValue1" + } }