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); + } + } }