diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbfb25c..32ed738 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+
+- `IServiceCollectionExtensions.AddOptions` with custom section name
+- `IConfigurationExtensions.GetValue` with strongly typed key
+
## [2.0.0] - 2026-05-05
### Changed
diff --git a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs
index b109adb..2fdfcee 100644
--- a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs
+++ b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs
@@ -7,6 +7,22 @@
///
public class IConfigurationExtensionsTests
{
+ ///
+ /// The same enum used for testing.
+ ///
+ public enum AppSettingKeys
+ {
+ ///
+ /// The test string key
+ ///
+ TestStringKey,
+
+ ///
+ /// The test int key
+ ///
+ TestIntKey,
+ }
+
///
/// Test that given the null configuration when get options called then throws argument null exception.
///
@@ -215,6 +231,103 @@ public void GivenMissingSection_WhenGetSectionCalled_ThenReturnsNonExistingSecti
section.Exists().ShouldBeFalse();
}
+ ///
+ /// Test that given the null configuration when get value called then throws argument null exception.
+ ///
+ [Fact]
+ public void GivenNullConfiguration_WhenGetValueCalled_ThenThrowsArgumentNullException()
+ {
+ // Arrange
+ IConfiguration? configuration = null;
+
+ // Act
+ var act = () => configuration!.GetValue(AppSettingKeys.TestStringKey);
+
+ // Assert
+ var ex = Should.Throw(act);
+ ex.ParamName.ShouldBe("config");
+ }
+
+ ///
+ /// Gets the value should return correct value when key exists.
+ ///
+ [Fact]
+ public void GivenConfigurationWithExistingKeys_WhenGetValueCalled_ThenReturnsValues()
+ {
+ // Arrange
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["TestStringKey"] = "TestValue",
+ ["TestIntKey"] = "7",
+ })
+ .Build();
+
+ // Act
+ var value = configuration.GetValue(AppSettingKeys.TestStringKey);
+ var intValue = configuration.GetValue(AppSettingKeys.TestIntKey);
+
+ // Assert
+ value.ShouldBe("TestValue");
+ intValue.ShouldBe(7);
+ }
+
+ ///
+ /// Test that given the null configuration when get value with default called then throws argument null exception.
+ ///
+ [Fact]
+ public void GivenNullConfiguration_WhenGetValueWithDefaultCalled_ThenThrowsArgumentNullException()
+ {
+ // Arrange
+ IConfiguration? configuration = null;
+
+ // Act
+ var act = () => configuration!.GetValue(AppSettingKeys.TestStringKey, "fallback");
+
+ // Assert
+ var ex = Should.Throw(act);
+ ex.ParamName.ShouldBe("config");
+ }
+
+ ///
+ /// Test that given a missing key when get value with default called then returns the default value.
+ ///
+ [Fact]
+ public void GivenMissingKey_WhenGetValueWithDefaultCalled_ThenReturnsDefaultValue()
+ {
+ // Arrange
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection([])
+ .Build();
+
+ // Act
+ var value = configuration.GetValue(AppSettingKeys.TestStringKey, "fallback");
+
+ // Assert
+ value.ShouldBe("fallback");
+ }
+
+ ///
+ /// Test that given an existing key when get value with default called then returns configured value.
+ ///
+ [Fact]
+ public void GivenExistingKey_WhenGetValueWithDefaultCalled_ThenReturnsConfiguredValue()
+ {
+ // Arrange
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["TestIntKey"] = "7",
+ })
+ .Build();
+
+ // Act
+ var value = configuration.GetValue(AppSettingKeys.TestIntKey, 99);
+
+ // Assert
+ value.ShouldBe(7);
+ }
+
///
/// The sample options class used for testing.
///
diff --git a/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
index a4f69e5..5f7fd0c 100644
--- a/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
+++ b/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
@@ -149,6 +149,72 @@ public void GivenMultipleRegistrations_WhenAddOptionsCalledTwice_ThenLastRegistr
value.Level.ShouldBe(2);
}
+ ///
+ /// Test that given the configuration with options section when add options with section name called then options are configured.
+ ///
+ [Fact]
+ public void GivenConfigurationWithOptionsSection_WhenAddOptionsWithSectionNameCalled_ThenOptionsAreConfigured()
+ {
+ // Arrange
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["OtherSampleOptions:Name"] = "Configured",
+ ["OtherSampleOptions:Level"] = "7",
+ })
+ .Build();
+ var serviceCollection = new ServiceCollection();
+
+ // Act
+ serviceCollection.AddOptions(configuration, "OtherSampleOptions");
+ var provider = serviceCollection.BuildServiceProvider();
+
+ // Assert
+ var options = provider.GetRequiredService>().Value;
+ options.Name.ShouldBe("Configured");
+ options.Level.ShouldBe(7);
+ }
+
+ ///
+ /// Test that given the null section name when add options with section name called then throws argument null exception.
+ ///
+ [Fact]
+ public void GivenNullSectionName_WhenAddOptionsWithSectionNameCalled_ThenThrowsArgumentNullException()
+ {
+ // Arrange
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection([])
+ .Build();
+ var serviceCollection = new ServiceCollection();
+
+ // Act
+ var act = () => serviceCollection.AddOptions(configuration, null!);
+
+ // Assert
+ var ex = Should.Throw(act);
+ ex.ParamName.ShouldBe("sectionName");
+ }
+
+ ///
+ /// Test that given the whitespace section name when add options with section name called then throws argument exception.
+ ///
+ [Fact]
+ public void GivenWhitespaceSectionName_WhenAddOptionsWithSectionNameCalled_ThenThrowsArgumentException()
+ {
+ // Arrange
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection([])
+ .Build();
+ var serviceCollection = new ServiceCollection();
+
+ // Act
+ var act = () => serviceCollection.AddOptions(configuration, " ");
+
+ // Assert
+ var ex = Should.Throw(act);
+ ex.ParamName.ShouldBe("sectionName");
+ }
+
///
/// The sample options class used for testing.
///
diff --git a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs
index 2312c1f..d8997f4 100644
--- a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs
+++ b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs
@@ -36,4 +36,35 @@ public static IConfigurationSection GetSection(this IConfiguration config)
ArgumentNullException.ThrowIfNull(config);
return config.GetSection(typeof(T).Name);
}
+
+ ///
+ /// Gets the value with the specified key.
+ ///
+ /// The type of the value.
+ /// The type of the enum.
+ /// The configuration.
+ /// The key.
+ /// The value.
+ public static T? GetValue(this IConfiguration config, TEnum key)
+ where TEnum : struct, Enum
+ {
+ ArgumentNullException.ThrowIfNull(config);
+ return config.GetValue(key.ToString());
+ }
+
+ ///
+ /// Gets the value with the specified key.
+ ///
+ /// The type of the value.
+ /// The type of the enum.
+ /// The configuration.
+ /// The key.
+ /// The default value to use if no value is found.
+ /// The value.
+ public static T GetValue(this IConfiguration config, TEnum key, T defaultValue)
+ where TEnum : struct, Enum
+ {
+ ArgumentNullException.ThrowIfNull(config);
+ return config.GetValue(key.ToString(), defaultValue)!;
+ }
}
diff --git a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs
index 98c86fe..8da63e3 100644
--- a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs
+++ b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs
@@ -23,4 +23,22 @@ public static IServiceCollection AddOptions(this IServiceCollection se
serviceCollection.Configure(configuration.GetSection());
return serviceCollection;
}
+
+ ///
+ /// Adds the strongly typed options.
+ ///
+ /// The type of the options.
+ /// The service collection.
+ /// The configuration.
+ /// The section name.
+ /// The service collection
+ public static IServiceCollection AddOptions(this IServiceCollection serviceCollection, IConfiguration configuration, string sectionName)
+ where TOptions : class
+ {
+ ArgumentNullException.ThrowIfNull(serviceCollection);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentException.ThrowIfNullOrWhiteSpace(sectionName);
+ serviceCollection.Configure(configuration.GetSection(sectionName));
+ return serviceCollection;
+ }
}