From 62123d02ace8b48108331edb11d1cfddb06b452e Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:11:46 +0200 Subject: [PATCH 01/11] Add IServiceCollectionExtensions.AddOptions with section name --- CHANGELOG.md | 4 +++ .../IServiceCollectionExtensionsTests.cs | 26 +++++++++++++++++++ .../IServiceCollectionExtensions.cs | 18 +++++++++++++ 3 files changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfb25c..b83bf51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `IServiceCollectionExtensions.AddOptions` with custom section name + ## [2.0.0] - 2026-05-05 ### Changed diff --git a/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index a4f69e5..95fcc53 100644 --- a/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -149,6 +149,32 @@ 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); + } + /// /// The sample options class used for testing. /// diff --git a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs index 98c86fe..83f0c9c 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); + ArgumentNullException.ThrowIfNull(sectionName); + serviceCollection.Configure(configuration.GetSection(sectionName)); + return serviceCollection; + } } From d51b4932b645524503cf611af0468edfbeaac5f5 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:25:39 +0200 Subject: [PATCH 02/11] x --- CHANGELOG.md | 1 + .../IConfigurationExtensionsTests.cs | 40 +++++++++++++++++++ .../Extensions/IConfigurationExtensions.cs | 15 +++++++ 3 files changed, 56 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b83bf51..32ed738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `IServiceCollectionExtensions.AddOptions` with custom section name +- `IConfigurationExtensions.GetValue` with strongly typed key ## [2.0.0] - 2026-05-05 diff --git a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs index b109adb..1f22148 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,30 @@ public void GivenMissingSection_WhenGetSectionCalled_ThenReturnsNonExistingSecti section.Exists().ShouldBeFalse(); } + /// + /// Gets the value should return correct value when key exists. + /// + [Fact] + public void GetValue_ShouldReturnCorrectValue_WhenKeyExists() + { + // 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); + } + /// /// The sample options class used for testing. /// diff --git a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs index 2312c1f..a0c38af 100644 --- a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs +++ b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs @@ -36,4 +36,19 @@ 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 : Enum + { + ArgumentNullException.ThrowIfNull(config); + return config.GetValue(key.ToString()); + } } From 22a82482c96dd11b6ad54792f673ad74d6f34417 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:27:45 +0200 Subject: [PATCH 03/11] x --- Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs index 83f0c9c..033a7d8 100644 --- a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs +++ b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs @@ -37,7 +37,7 @@ public static IServiceCollection AddOptions(this IServiceCollection se { ArgumentNullException.ThrowIfNull(serviceCollection); ArgumentNullException.ThrowIfNull(configuration); - ArgumentNullException.ThrowIfNull(sectionName); + ArgumentNullException.ThrowIfNullOrWhiteSpace(sectionName); serviceCollection.Configure(configuration.GetSection(sectionName)); return serviceCollection; } From 83af0a280d13cbbeb14d6b5e2c4fabcc2d245434 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:31:46 +0200 Subject: [PATCH 04/11] Add test for null configuration ArgumentNullException Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Extensions/IConfigurationExtensionsTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs index 1f22148..729b5d3 100644 --- a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs +++ b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs @@ -231,6 +231,23 @@ 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. /// From d03ace5403d96eda7704fa188e452849f5a59a94 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:34:14 +0200 Subject: [PATCH 05/11] Change exception type for null or whitespace sectionName Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs index 033a7d8..8da63e3 100644 --- a/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs +++ b/Neolution.Utilities/Extensions/IServiceCollectionExtensions.cs @@ -37,7 +37,7 @@ public static IServiceCollection AddOptions(this IServiceCollection se { ArgumentNullException.ThrowIfNull(serviceCollection); ArgumentNullException.ThrowIfNull(configuration); - ArgumentNullException.ThrowIfNullOrWhiteSpace(sectionName); + ArgumentException.ThrowIfNullOrWhiteSpace(sectionName); serviceCollection.Configure(configuration.GetSection(sectionName)); return serviceCollection; } From 36a27ca00796661eb87476c6645f4f270cd936c8 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:36:10 +0200 Subject: [PATCH 06/11] Add tests for null and whitespace section names Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../IServiceCollectionExtensionsTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 95fcc53..5f7fd0c 100644 --- a/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/Neolution.Utilities.UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -175,6 +175,46 @@ public void GivenConfigurationWithOptionsSection_WhenAddOptionsWithSectionNameCa 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. /// From d3f7356d54488e9cb03ad5c0adb64886a2326776 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Mon, 7 Sep 2026 16:37:09 +0200 Subject: [PATCH 07/11] Rename test method for clarity Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Extensions/IConfigurationExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs index 729b5d3..ea78bf3 100644 --- a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs +++ b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs @@ -252,7 +252,7 @@ public void GivenNullConfiguration_WhenGetValueCalled_ThenThrowsArgumentNullExce /// Gets the value should return correct value when key exists. /// [Fact] - public void GetValue_ShouldReturnCorrectValue_WhenKeyExists() + public void GivenConfigurationWithExistingKeys_WhenGetValueCalled_ThenReturnsValues() { // Arrange var configuration = new ConfigurationBuilder() From c46637eee9d8227e1cd05d05271aba7c4efebf0f Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Tue, 8 Sep 2026 13:34:03 +0200 Subject: [PATCH 08/11] x --- .../Extensions/IConfigurationExtensions.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs index a0c38af..99d60ec 100644 --- a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs +++ b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs @@ -51,4 +51,20 @@ public static IConfigurationSection GetSection(this IConfiguration config) 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 : Enum + { + ArgumentNullException.ThrowIfNull(config); + return config.GetValue(key.ToString(), defaultValue); + } } From 8b9ecbedd58800f1f02ea9069caa81b79d8a3138 Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Tue, 8 Sep 2026 13:54:15 +0200 Subject: [PATCH 09/11] x --- .../IConfigurationExtensionsTests.cs | 56 +++++++++++++++++++ .../Extensions/IConfigurationExtensions.cs | 4 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs index ea78bf3..2fdfcee 100644 --- a/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs +++ b/Neolution.Utilities.UnitTests/Extensions/IConfigurationExtensionsTests.cs @@ -272,6 +272,62 @@ public void GivenConfigurationWithExistingKeys_WhenGetValueCalled_ThenReturnsVal 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/Extensions/IConfigurationExtensions.cs b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs index 99d60ec..93765e0 100644 --- a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs +++ b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs @@ -46,7 +46,7 @@ public static IConfigurationSection GetSection(this IConfiguration config) /// The key. /// The value. public static T? GetValue(this IConfiguration config, TEnum key) - where TEnum : Enum + where TEnum : struct, Enum { ArgumentNullException.ThrowIfNull(config); return config.GetValue(key.ToString()); @@ -62,7 +62,7 @@ public static IConfigurationSection GetSection(this IConfiguration config) /// 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 : Enum + where TEnum : struct, Enum { ArgumentNullException.ThrowIfNull(config); return config.GetValue(key.ToString(), defaultValue); From bdfea062835c1d24bfce619c07eb700d04346f0c Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Tue, 8 Sep 2026 14:01:29 +0200 Subject: [PATCH 10/11] Update GetValue method return type to non-nullable Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Neolution.Utilities/Extensions/IConfigurationExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs index 93765e0..b8ff3a6 100644 --- a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs +++ b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs @@ -61,7 +61,7 @@ public static IConfigurationSection GetSection(this IConfiguration config) /// 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) +public static T GetValue(this IConfiguration config, TEnum key, T defaultValue) where TEnum : struct, Enum { ArgumentNullException.ThrowIfNull(config); From d2e03449f7ef2364db2aee73a0767766ef9a172d Mon Sep 17 00:00:00 2001 From: Daniele Debernardi Date: Tue, 8 Sep 2026 14:02:20 +0200 Subject: [PATCH 11/11] fix copilot --- Neolution.Utilities/Extensions/IConfigurationExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs index b8ff3a6..d8997f4 100644 --- a/Neolution.Utilities/Extensions/IConfigurationExtensions.cs +++ b/Neolution.Utilities/Extensions/IConfigurationExtensions.cs @@ -61,10 +61,10 @@ public static IConfigurationSection GetSection(this IConfiguration config) /// 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) + public static T GetValue(this IConfiguration config, TEnum key, T defaultValue) where TEnum : struct, Enum { ArgumentNullException.ThrowIfNull(config); - return config.GetValue(key.ToString(), defaultValue); + return config.GetValue(key.ToString(), defaultValue)!; } }