diff --git a/src/Cuemon.Extensions.AspNetCore.Text.Json/Formatters/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Text.Json/Formatters/ServiceCollectionExtensions.cs
index dd4e295c4..ab2014fd3 100644
--- a/src/Cuemon.Extensions.AspNetCore.Text.Json/Formatters/ServiceCollectionExtensions.cs
+++ b/src/Cuemon.Extensions.AspNetCore.Text.Json/Formatters/ServiceCollectionExtensions.cs
@@ -3,6 +3,7 @@
using System.Text.Json;
using Cuemon.AspNetCore.Diagnostics;
using Cuemon.Extensions.AspNetCore.Text.Json.Converters;
+using Cuemon.Extensions.DependencyInjection;
using Cuemon.Extensions.Text.Json.Formatters;
using Cuemon.Net.Http;
using Microsoft.Extensions.DependencyInjection;
@@ -37,7 +38,7 @@ public static IServiceCollection AddJsonFormatterOptions(this IServiceCollection
{
Validator.ThrowIfNull(services);
Validator.ThrowIfInvalidConfigurator(setup, out var options);
- services.Configure(setup ?? (o =>
+ services.TryConfigure(setup ?? (o =>
{
o.Settings = options.Settings;
o.SensitivityDetails = options.SensitivityDetails;
diff --git a/src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs b/src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs
new file mode 100644
index 000000000..ddf2cfc76
--- /dev/null
+++ b/src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs
@@ -0,0 +1,64 @@
+using System.Text.Json;
+using Cuemon.Collections.Generic;
+using Cuemon.Extensions.AspNetCore.Text.Json.Converters;
+using Cuemon.Extensions.Text.Json.Formatters;
+using Microsoft.AspNetCore.Http.Json;
+using Microsoft.Extensions.Options;
+
+namespace Cuemon.Extensions.AspNetCore.Text.Json
+{
+ ///
+ /// A implementation which will pass to .
+ ///
+ public class MinimalJsonOptions : ConfigureOptions
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The formatter options.
+ ///
+ public MinimalJsonOptions(IOptions formatterOptions) : base(mo =>
+ {
+ var options = formatterOptions.Value;
+
+ var settings = new JsonSerializerOptions(options.Settings);
+ settings.Converters.AddHttpExceptionDescriptorConverter(o => o.SensitivityDetails = options.SensitivityDetails);
+
+ Decorator.Enclose(mo.SerializerOptions.Converters).AddRange(settings.Converters);
+ mo.SerializerOptions.AllowOutOfOrderMetadataProperties = settings.AllowOutOfOrderMetadataProperties;
+ mo.SerializerOptions.AllowTrailingCommas = settings.AllowTrailingCommas;
+ mo.SerializerOptions.DefaultBufferSize = settings.DefaultBufferSize;
+ mo.SerializerOptions.Encoder = settings.Encoder;
+ mo.SerializerOptions.DictionaryKeyPolicy = settings.DictionaryKeyPolicy;
+ mo.SerializerOptions.DefaultIgnoreCondition = settings.DefaultIgnoreCondition;
+ mo.SerializerOptions.NumberHandling = settings.NumberHandling;
+ mo.SerializerOptions.PreferredObjectCreationHandling = settings.PreferredObjectCreationHandling;
+ mo.SerializerOptions.UnknownTypeHandling = settings.UnknownTypeHandling;
+ mo.SerializerOptions.UnmappedMemberHandling = settings.UnmappedMemberHandling;
+ mo.SerializerOptions.IgnoreReadOnlyProperties = settings.IgnoreReadOnlyProperties;
+ mo.SerializerOptions.IgnoreReadOnlyFields = settings.IgnoreReadOnlyFields;
+ mo.SerializerOptions.IncludeFields = settings.IncludeFields;
+ mo.SerializerOptions.MaxDepth = settings.MaxDepth;
+ mo.SerializerOptions.PropertyNamingPolicy = settings.PropertyNamingPolicy;
+ mo.SerializerOptions.PropertyNameCaseInsensitive = settings.PropertyNameCaseInsensitive;
+ mo.SerializerOptions.ReadCommentHandling = settings.ReadCommentHandling;
+ mo.SerializerOptions.WriteIndented = settings.WriteIndented;
+ mo.SerializerOptions.IndentCharacter = settings.IndentCharacter;
+ mo.SerializerOptions.IndentSize = settings.IndentSize;
+ mo.SerializerOptions.ReferenceHandler = settings.ReferenceHandler;
+ mo.SerializerOptions.NewLine = settings.NewLine;
+ mo.SerializerOptions.RespectNullableAnnotations = settings.RespectNullableAnnotations;
+ mo.SerializerOptions.RespectRequiredConstructorParameters = settings.RespectRequiredConstructorParameters;
+#if NET10_0_OR_GREATER
+ mo.SerializerOptions.AllowDuplicateProperties = settings.AllowDuplicateProperties;
+#endif
+ if (settings.TypeInfoResolver is not null)
+ {
+ mo.SerializerOptions.TypeInfoResolver = settings.TypeInfoResolver;
+ }
+ })
+ {
+ }
+ }
+}
diff --git a/src/Cuemon.Extensions.AspNetCore.Text.Json/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Text.Json/ServiceCollectionExtensions.cs
new file mode 100644
index 000000000..b7c0eae18
--- /dev/null
+++ b/src/Cuemon.Extensions.AspNetCore.Text.Json/ServiceCollectionExtensions.cs
@@ -0,0 +1,36 @@
+using System;
+using Cuemon.Extensions.AspNetCore.Text.Json.Formatters;
+using Cuemon.Extensions.Text.Json.Formatters;
+using Microsoft.AspNetCore.Http.Json;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+
+namespace Cuemon.Extensions.AspNetCore.Text.Json
+{
+ ///
+ /// Extension methods for the interface.
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// Adds a service to the specified .
+ ///
+ /// The to add services to.
+ /// The which may be configured.
+ /// An that can be used to further configure other services.
+ ///
+ /// This method registers a configuration as a singleton for
+ /// and delegates to to configure the JSON exception response formatter.
+ ///
+ ///
+ /// cannot be null.
+ ///
+ public static IServiceCollection AddMinimalJsonOptions(this IServiceCollection services, Action setup = null)
+ {
+ Validator.ThrowIfNull(services);
+ services.TryAddEnumerable(ServiceDescriptor.Singleton, MinimalJsonOptions>());
+ return services.AddJsonExceptionResponseFormatter(setup);
+ }
+ }
+}
diff --git a/src/Cuemon.Extensions.AspNetCore.Xml/Formatters/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Xml/Formatters/ServiceCollectionExtensions.cs
index d11c9d2ca..be5a220dd 100644
--- a/src/Cuemon.Extensions.AspNetCore.Xml/Formatters/ServiceCollectionExtensions.cs
+++ b/src/Cuemon.Extensions.AspNetCore.Xml/Formatters/ServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using System.Net.Http;
using Cuemon.AspNetCore.Diagnostics;
using Cuemon.Extensions.AspNetCore.Xml.Converters;
+using Cuemon.Extensions.DependencyInjection;
using Cuemon.Net.Http;
using Cuemon.Xml.Serialization.Formatters;
using Microsoft.Extensions.DependencyInjection;
@@ -36,7 +37,7 @@ public static IServiceCollection AddXmlFormatterOptions(this IServiceCollection
{
Validator.ThrowIfNull(services);
Validator.ThrowIfInvalidConfigurator(setup, out var options);
- services.Configure(setup ?? (o =>
+ services.TryConfigure(setup ?? (o =>
{
o.Settings = options.Settings;
o.SensitivityDetails = options.SensitivityDetails;
diff --git a/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
index 6912e2fb8..60d95362a 100644
--- a/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
@@ -76,7 +76,7 @@ public static IServiceCollection Add(this IServiceCollection services,
Validator.ThrowIfNull(services);
Validator.ThrowIfNull(setup);
services.AddServices(service, implementation, lifetime, false);
- services.Configure(setup);
+ services.TryConfigure(setup);
return services;
}
@@ -398,7 +398,7 @@ public static IServiceCollection TryAdd(this IServiceCollection servic
Validator.ThrowIfNull(services);
Validator.ThrowIfNull(setup);
services.AddServices(service, implementation, lifetime, true);
- services.Configure(setup);
+ services.TryConfigure(setup);
return services;
}
@@ -475,7 +475,7 @@ public static IServiceCollection TryAdd(this IServiceCollection servic
Validator.ThrowIfNull(implementationFactory);
Validator.ThrowIfNull(setup);
services.AddServices(service, implementationFactory, lifetime, true);
- services.Configure(setup);
+ services.TryConfigure(setup);
return services;
}
diff --git a/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/Formatters/ServiceCollectionExtensionsTest.cs b/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/Formatters/ServiceCollectionExtensionsTest.cs
new file mode 100644
index 000000000..0c8c0c565
--- /dev/null
+++ b/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/Formatters/ServiceCollectionExtensionsTest.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using Cuemon.Extensions.Text.Json.Formatters;
+using Codebelt.Extensions.Xunit;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Cuemon.Extensions.AspNetCore.Text.Json.Formatters
+{
+ public class ServiceCollectionExtensionsTest : Test
+ {
+ public ServiceCollectionExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void AddJsonFormatterOptions_ShouldOnlyRegisterOnce_WhenCalledMultipleTimes()
+ {
+ var sut = new ServiceCollection();
+
+ sut.AddJsonFormatterOptions();
+ sut.AddJsonFormatterOptions();
+ sut.AddJsonFormatterOptions();
+
+ var configureOptionsCount = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions));
+
+ TestOutput.WriteLine($"IConfigureOptions registrations: {configureOptionsCount}");
+
+ Assert.Equal(1, configureOptionsCount);
+ }
+ }
+}
diff --git a/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs b/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs
new file mode 100644
index 000000000..baad06a65
--- /dev/null
+++ b/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs
@@ -0,0 +1,312 @@
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Cuemon.Extensions.Text.Json.Formatters;
+using System.Text.Json.Serialization.Metadata;
+using Codebelt.Extensions.Xunit;
+using Microsoft.AspNetCore.Http.Json;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Cuemon.Extensions.AspNetCore.Text.Json
+{
+ public class MinimalJsonOptionsTest : Test
+ {
+ public MinimalJsonOptionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagatePropertyNamingPolicy_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(JsonNamingPolicy.SnakeCaseLower, jsonOptions.SerializerOptions.PropertyNamingPolicy);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateWriteIndented_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.WriteIndented = false;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.False(jsonOptions.SerializerOptions.WriteIndented);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateDictionaryKeyPolicy_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.DictionaryKeyPolicy = JsonNamingPolicy.KebabCaseLower;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(JsonNamingPolicy.KebabCaseLower, jsonOptions.SerializerOptions.DictionaryKeyPolicy);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateDefaultIgnoreCondition_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.DefaultIgnoreCondition = JsonIgnoreCondition.Never;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(JsonIgnoreCondition.Never, jsonOptions.SerializerOptions.DefaultIgnoreCondition);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateMaxDepth_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.MaxDepth = 128;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(128, jsonOptions.SerializerOptions.MaxDepth);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateDefaultBufferSize_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.DefaultBufferSize = 8192;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(8192, jsonOptions.SerializerOptions.DefaultBufferSize);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateReadCommentHandling_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.ReadCommentHandling = JsonCommentHandling.Disallow;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(JsonCommentHandling.Disallow, jsonOptions.SerializerOptions.ReadCommentHandling);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateAllowTrailingCommas_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.AllowTrailingCommas = true;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.True(jsonOptions.SerializerOptions.AllowTrailingCommas);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateNumberHandling_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.NumberHandling = JsonNumberHandling.AllowReadingFromString;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(JsonNumberHandling.AllowReadingFromString, jsonOptions.SerializerOptions.NumberHandling);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagatePropertyNameCaseInsensitive_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.PropertyNameCaseInsensitive = true;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.True(jsonOptions.SerializerOptions.PropertyNameCaseInsensitive);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateIncludeFields_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.IncludeFields = true;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.True(jsonOptions.SerializerOptions.IncludeFields);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateIgnoreReadOnlyProperties_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.IgnoreReadOnlyProperties = true;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.True(jsonOptions.SerializerOptions.IgnoreReadOnlyProperties);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateEncoder_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.Encoder = JavaScriptEncoder.Default;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(JavaScriptEncoder.Default, jsonOptions.SerializerOptions.Encoder);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateReferenceHandler_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.ReferenceHandler = ReferenceHandler.Preserve;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(ReferenceHandler.Preserve, jsonOptions.SerializerOptions.ReferenceHandler);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateConverters_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions();
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ TestOutput.WriteLine($"Converter count: {jsonOptions.SerializerOptions.Converters.Count}");
+
+ Assert.NotEmpty(jsonOptions.SerializerOptions.Converters);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldNotDuplicateConverters_WhenOptionsCreatedMultipleTimes()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions();
+
+ var provider = services.BuildServiceProvider();
+ var formatterOptions = provider.GetRequiredService>().Value;
+ var optionsFactory = provider.GetRequiredService>();
+
+ var before = formatterOptions.Settings.Converters.Count;
+
+ optionsFactory.Create(Options.DefaultName);
+ optionsFactory.Create(Options.DefaultName);
+
+ var after = formatterOptions.Settings.Converters.Count;
+
+ Assert.Equal(before, after);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateTypeInfoResolver_WhenNotNull()
+ {
+ var resolver = new DefaultJsonTypeInfoResolver();
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.TypeInfoResolver = resolver;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Same(resolver, jsonOptions.SerializerOptions.TypeInfoResolver);
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldNotOverrideTypeInfoResolver_WhenNull()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.TypeInfoResolver = null;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ // When source TypeInfoResolver is null, the target's existing resolver should not be overridden
+ // (the target may or may not have its own default resolver, so we just verify no exception)
+ TestOutput.WriteLine($"TypeInfoResolver is null: {jsonOptions.SerializerOptions.TypeInfoResolver is null}");
+ }
+
+ [Fact]
+ public void MinimalJsonOptions_ShouldPropagateIndentSize_FromJsonFormatterOptions()
+ {
+ var services = new ServiceCollection();
+ services.AddMinimalJsonOptions(o =>
+ {
+ o.Settings.IndentSize = 4;
+ });
+
+ var provider = services.BuildServiceProvider();
+ var jsonOptions = provider.GetRequiredService>().Value;
+
+ Assert.Equal(4, jsonOptions.SerializerOptions.IndentSize);
+ }
+ }
+}
diff --git a/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/ServiceCollectionExtensionsTest.cs b/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/ServiceCollectionExtensionsTest.cs
new file mode 100644
index 000000000..8dda2f5b6
--- /dev/null
+++ b/test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/ServiceCollectionExtensionsTest.cs
@@ -0,0 +1,82 @@
+using System.Linq;
+using Cuemon.AspNetCore.Diagnostics;
+using Cuemon.Extensions.AspNetCore.Diagnostics;
+using Cuemon.Extensions.Text.Json.Formatters;
+using Codebelt.Extensions.Xunit;
+using Microsoft.AspNetCore.Http.Json;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Cuemon.Extensions.AspNetCore.Text.Json
+{
+ public class ServiceCollectionExtensionsTest : Test
+ {
+ public ServiceCollectionExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void AddMinimalJsonOptions_ShouldRegisterMinimalJsonOptionsAsSingleton()
+ {
+ var sut = new ServiceCollection();
+
+ sut.AddMinimalJsonOptions();
+
+ var descriptor = sut.Single(sd =>
+ sd.ServiceType == typeof(IConfigureOptions) &&
+ sd.ImplementationType == typeof(MinimalJsonOptions));
+
+ TestOutput.WriteLine($"Lifetime: {descriptor.Lifetime}");
+
+ Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
+ }
+
+ [Fact]
+ public void AddMinimalJsonOptions_ShouldRegisterMinimalJsonOptionsOnlyOnce_WhenCalledMultipleTimes()
+ {
+ var sut = new ServiceCollection();
+
+ sut.AddMinimalJsonOptions();
+ sut.AddMinimalJsonOptions();
+ sut.AddMinimalJsonOptions();
+
+ var count = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions) &&
+ sd.ImplementationType == typeof(MinimalJsonOptions));
+
+ TestOutput.WriteLine($"MinimalJsonOptions registrations: {count}");
+
+ Assert.Equal(1, count);
+ }
+
+ [Fact]
+ public void AddMinimalJsonOptions_ShouldAlsoRegisterJsonExceptionResponseFormatter()
+ {
+ var sut = new ServiceCollection();
+ sut.AddFaultDescriptorOptions();
+
+ sut.AddMinimalJsonOptions();
+
+ var hasFormatter = sut.Any(sd =>
+ sd.ServiceType == typeof(HttpExceptionDescriptorResponseFormatter));
+
+ Assert.True(hasFormatter);
+ }
+
+ [Fact]
+ public void AddMinimalJsonOptions_ShouldRegisterJsonFormatterOptions()
+ {
+ var sut = new ServiceCollection();
+
+ sut.AddMinimalJsonOptions();
+
+ var count = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions));
+
+ TestOutput.WriteLine($"IConfigureOptions registrations: {count}");
+
+ Assert.True(count >= 1);
+ }
+ }
+}
diff --git a/test/Cuemon.Extensions.AspNetCore.Tests/Xml/Formatters/ServiceCollectionExtensionsTest.cs b/test/Cuemon.Extensions.AspNetCore.Tests/Xml/Formatters/ServiceCollectionExtensionsTest.cs
new file mode 100644
index 000000000..3f9846f25
--- /dev/null
+++ b/test/Cuemon.Extensions.AspNetCore.Tests/Xml/Formatters/ServiceCollectionExtensionsTest.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using Cuemon.Xml.Serialization.Formatters;
+using Codebelt.Extensions.Xunit;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Cuemon.Extensions.AspNetCore.Xml.Formatters
+{
+ public class ServiceCollectionExtensionsTest : Test
+ {
+ public ServiceCollectionExtensionsTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void AddXmlFormatterOptions_ShouldOnlyRegisterOnce_WhenCalledMultipleTimes()
+ {
+ var sut = new ServiceCollection();
+
+ sut.AddXmlFormatterOptions();
+ sut.AddXmlFormatterOptions();
+ sut.AddXmlFormatterOptions();
+
+ var configureOptionsCount = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions));
+
+ TestOutput.WriteLine($"IConfigureOptions registrations: {configureOptionsCount}");
+
+ Assert.Equal(1, configureOptionsCount);
+ }
+ }
+}
diff --git a/test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs b/test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs
index 632f97596..b6fe10402 100644
--- a/test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs
+++ b/test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs
@@ -624,5 +624,56 @@ public void SynchronizeOptions_ShouldChangeAllWithAServiceTypeHavingFaultDescrip
#endif
+ [Fact]
+ public void Add_ShouldOnlyRegisterOptionsOnce_WhenCalledMultipleTimesWithSameOptionsType()
+ {
+ var sut = new ServiceCollection();
+
+ sut.Add(typeof(FakeService), typeof(FakeServiceScoped), ServiceLifetime.Scoped, (Action)(o => o.Greeting = "First"));
+ sut.Add(typeof(FakeService), typeof(FakeServiceSingleton), ServiceLifetime.Singleton, (Action)(o => o.Greeting = "Second"));
+ sut.Add(typeof(FakeService), typeof(FakeServiceTransient), ServiceLifetime.Transient, (Action)(o => o.Greeting = "Third"));
+
+ var configureOptionsCount = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions));
+
+ TestOutput.WriteLine($"IConfigureOptions registrations: {configureOptionsCount}");
+
+ Assert.Equal(1, configureOptionsCount);
+ }
+
+ [Fact]
+ public void TryAdd_ShouldOnlyRegisterOptionsOnce_WhenCalledMultipleTimesWithSameOptionsType()
+ {
+ var sut = new ServiceCollection();
+
+ sut.TryAdd(typeof(FakeService), typeof(FakeServiceScoped), ServiceLifetime.Scoped, (Action)(o => o.Greeting = "First"));
+ sut.TryAdd(typeof(FakeService), typeof(FakeServiceSingleton), ServiceLifetime.Singleton, (Action)(o => o.Greeting = "Second"));
+ sut.TryAdd(typeof(FakeService), typeof(FakeServiceTransient), ServiceLifetime.Transient, (Action)(o => o.Greeting = "Third"));
+
+ var configureOptionsCount = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions));
+
+ TestOutput.WriteLine($"IConfigureOptions registrations: {configureOptionsCount}");
+
+ Assert.Equal(1, configureOptionsCount);
+ }
+
+ [Fact]
+ public void TryAdd_WithFactory_ShouldOnlyRegisterOptionsOnce_WhenCalledMultipleTimesWithSameOptionsType()
+ {
+ var sut = new ServiceCollection();
+
+ sut.TryAdd(typeof(FakeService), _ => new FakeServiceScoped(default), ServiceLifetime.Scoped, (Action)(o => o.Greeting = "First"));
+ sut.TryAdd(typeof(FakeService), _ => new FakeServiceSingleton(default), ServiceLifetime.Singleton, (Action)(o => o.Greeting = "Second"));
+ sut.TryAdd(typeof(FakeService), _ => new FakeServiceTransient(default), ServiceLifetime.Transient, (Action)(o => o.Greeting = "Third"));
+
+ var configureOptionsCount = sut.Count(sd =>
+ sd.ServiceType == typeof(IConfigureOptions));
+
+ TestOutput.WriteLine($"IConfigureOptions registrations: {configureOptionsCount}");
+
+ Assert.Equal(1, configureOptionsCount);
+ }
+
}
}