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"
+ }
}