From e4f09343b015554f785b5db778b6b7d4ba366568 Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 14:55:51 +0500 Subject: [PATCH 1/7] feat(app): support multiple options keys per module --- .../ApplicationModuleRegistration.cs | 34 +++++++++++-------- src/Sitko.Core.App/BaseApplicationModule.cs | 2 +- src/Sitko.Core.App/IApplicationModule.cs | 2 +- .../ConfigurationTests.cs | 33 +++++++++++++++++- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/Sitko.Core.App/ApplicationModuleRegistration.cs b/src/Sitko.Core.App/ApplicationModuleRegistration.cs index dec97fadd..cf6758025 100644 --- a/src/Sitko.Core.App/ApplicationModuleRegistration.cs +++ b/src/Sitko.Core.App/ApplicationModuleRegistration.cs @@ -17,7 +17,7 @@ internal sealed class ApplicationModuleRegistration : A private readonly TModule instance; private readonly Dictionary optionsCache = new(); - private readonly string optionsKey; + private readonly string[] optionKeys; private readonly Type? validatorType; public ApplicationModuleRegistration( @@ -27,7 +27,7 @@ public ApplicationModuleRegistration( { this.instance = instance; this.configureOptions = configureOptions; - this.optionsKey = optionsKey ?? instance.OptionsKey; + this.optionKeys = !string.IsNullOrEmpty(optionsKey) ? new[] { optionsKey } : instance.OptionKeys; var optionsInstance = Activator.CreateInstance(); if (optionsInstance is IModuleOptionsWithValidation moduleOptionsWithValidation) { @@ -45,19 +45,23 @@ public ApplicationModuleRegistration( public override IApplicationModule GetInstance() => instance; public override (string? optionsKey, object options) GetOptions(IApplicationContext applicationContext) => - (optionsKey, CreateOptions(applicationContext)); + (optionKeys.Last(), CreateOptions(applicationContext)); public override ApplicationModuleRegistration ConfigureOptions(IApplicationContext context, IServiceCollection services) { - var builder = services.AddOptions() - .Bind(context.Configuration.GetSection(optionsKey)) - .PostConfigure( - options => - { - options.Configure(context); - configureOptions?.Invoke(context, options); - }); + var builder = services.AddOptions(); + foreach (var optionsKey in optionKeys) + { + builder = builder.Bind(context.Configuration.GetSection(optionsKey)); + } + + builder = builder.PostConfigure( + options => + { + options.Configure(context); + configureOptions?.Invoke(context, options); + }); if (validatorType is not null) { @@ -135,7 +139,11 @@ private TModuleOptions CreateOptions(IApplicationContext applicationContext, boo else { options = Activator.CreateInstance(); - applicationContext.Configuration.Bind(optionsKey, options); + foreach (var optionsKey in optionKeys) + { + applicationContext.Configuration.Bind(optionsKey, options); + } + options.Configure(applicationContext); configureOptions?.Invoke(applicationContext, options); optionsCache[applicationContext.Id] = options; @@ -228,5 +236,3 @@ public abstract (bool isSuccess, IEnumerable missingModules) CheckRequired public abstract bool IsEnabled(IApplicationContext context); } - - diff --git a/src/Sitko.Core.App/BaseApplicationModule.cs b/src/Sitko.Core.App/BaseApplicationModule.cs index e06b8519b..77b4b252d 100644 --- a/src/Sitko.Core.App/BaseApplicationModule.cs +++ b/src/Sitko.Core.App/BaseApplicationModule.cs @@ -45,6 +45,7 @@ public virtual Task OnAfterRunAsync(Application application, IApplicationC string[] args) => Task.FromResult(true); public abstract string OptionsKey { get; } + public virtual string[] OptionKeys => new[] { OptionsKey }; public virtual bool AllowMultiple => false; public TModuleOptions GetOptions(IServiceProvider serviceProvider) => @@ -64,4 +65,3 @@ public virtual void Configure(IApplicationContext applicationContext) { } } - diff --git a/src/Sitko.Core.App/IApplicationModule.cs b/src/Sitko.Core.App/IApplicationModule.cs index fe51ba525..5ecbf9e06 100644 --- a/src/Sitko.Core.App/IApplicationModule.cs +++ b/src/Sitko.Core.App/IApplicationModule.cs @@ -7,7 +7,7 @@ namespace Sitko.Core.App; public interface IApplicationModule : IApplicationModule where TModuleOptions : class, new() { - string OptionsKey { get; } + public string[] OptionKeys { get; } bool AllowMultiple { get; } void ConfigureServices(IApplicationContext applicationContext, IServiceCollection services, diff --git a/tests/Sitko.Core.App.Tests/ConfigurationTests.cs b/tests/Sitko.Core.App.Tests/ConfigurationTests.cs index fe469187d..7434e45e7 100644 --- a/tests/Sitko.Core.App.Tests/ConfigurationTests.cs +++ b/tests/Sitko.Core.App.Tests/ConfigurationTests.cs @@ -1,4 +1,6 @@ using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Sitko.Core.Xunit; using Xunit; using Xunit.Abstractions; @@ -21,6 +23,23 @@ public async Task NestedModules() var options = app.GetModulesOptions(); options.Should().HaveCount(3); } + + [Theory] + [InlineData("Baz__Foo", "123", "Baz__Bar", "456")] // all from base options + [InlineData("Baz__Inner__Foo", "123", "Baz__Bar", "456")] // first from module, second from base + [InlineData("Baz__Foo", "123", "Baz__Inner__Bar", "456")] // first from base, second from module + [InlineData("Baz__Inner__Foo", "123", "Baz__Inner__Bar", "456")] // all from module options + public async Task BaseOptions(string fooKey, string fooValue, string barKey, string barValue) + { + Environment.SetEnvironmentVariable(fooKey, fooValue); + Environment.SetEnvironmentVariable(barKey, barValue); + var app = new TestApplication(Array.Empty()); + app.AddModule(); + var sp = await app.GetServiceProviderAsync(); + var options = sp.GetRequiredService>(); + options.Value.Foo.Should().Be(fooValue); + options.Value.Bar.Should().Be(barValue); + } } public class TestModuleFoo : BaseApplicationModule @@ -33,6 +52,19 @@ public class TestModuleFooBar : BaseApplicationModule public override string OptionsKey => "Foo:Bar"; } +public class TestModuleBaz : BaseApplicationModule +{ + public override string OptionsKey => "Baz:Inner"; + + public override string[] OptionKeys => new[] { "Baz", OptionsKey }; +} + +public class TestModuleBazOptions : BaseModuleOptions +{ + public string Foo { get; set; } = ""; + public string Bar { get; set; } = ""; +} + public class TestModuleFooBarOptions : BaseModuleOptions { public string Bar { get; set; } = ""; @@ -42,4 +74,3 @@ public class TestModuleFooOptions : BaseModuleOptions { public string Foo { get; set; } = ""; } - From 1b257ea087395c9797a596cc4726b42ed3abd3fb Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 14:56:17 +0500 Subject: [PATCH 2/7] feat(postgres): allow set global options for all dbcontexts --- src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs b/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs index 7dcacc28e..f82d74be7 100644 --- a/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs +++ b/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs @@ -12,6 +12,8 @@ public class { public override string OptionsKey => $"Db:Postgres:{typeof(TDbContext).Name}"; + public override string[] OptionKeys => new[] { "Db:Postgres", OptionsKey }; + public override async Task InitAsync(IApplicationContext applicationContext, IServiceProvider serviceProvider) { await base.InitAsync(applicationContext, serviceProvider); @@ -103,4 +105,3 @@ private void ConfigureNpgsql(DbContextOptionsBuilder options, applicationContext); } } - From 1cd591f3081306178265c2bed1996b4ff6bad199 Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 16:53:44 +0500 Subject: [PATCH 3/7] fix(postgres): put default options into subkey --- src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs b/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs index f82d74be7..1014ec27b 100644 --- a/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs +++ b/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs @@ -12,7 +12,7 @@ public class { public override string OptionsKey => $"Db:Postgres:{typeof(TDbContext).Name}"; - public override string[] OptionKeys => new[] { "Db:Postgres", OptionsKey }; + public override string[] OptionKeys => new[] { "Db:Postgres:Default", OptionsKey }; public override async Task InitAsync(IApplicationContext applicationContext, IServiceProvider serviceProvider) { From efb01ba565294100e9eb22c1593cf8a748068933 Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 17:19:41 +0500 Subject: [PATCH 4/7] refactore: format code --- src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs b/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs index 1014ec27b..f117ba59c 100644 --- a/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs +++ b/src/Sitko.Core.Db.Postgres/PostgresDatabaseModule.cs @@ -12,7 +12,7 @@ public class { public override string OptionsKey => $"Db:Postgres:{typeof(TDbContext).Name}"; - public override string[] OptionKeys => new[] { "Db:Postgres:Default", OptionsKey }; + public override string[] OptionKeys => new[] { "Db:Postgres:Default", OptionsKey }; public override async Task InitAsync(IApplicationContext applicationContext, IServiceProvider serviceProvider) { From 07427e2c0bbbab22de076307fa93132ae6330335 Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 17:25:59 +0500 Subject: [PATCH 5/7] feat(grpc): separate options for grpc clients with support for default --- src/Sitko.Core.Grpc.Client.Consul/ConsulGrpcClientModule.cs | 5 +++-- .../External/ExternalGrpcClientModule.cs | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Sitko.Core.Grpc.Client.Consul/ConsulGrpcClientModule.cs b/src/Sitko.Core.Grpc.Client.Consul/ConsulGrpcClientModule.cs index 77d910c98..68c3db73e 100644 --- a/src/Sitko.Core.Grpc.Client.Consul/ConsulGrpcClientModule.cs +++ b/src/Sitko.Core.Grpc.Client.Consul/ConsulGrpcClientModule.cs @@ -8,7 +8,9 @@ public class ConsulGrpcClientModule : GrpcClientModule> where TClient : ClientBase { - public override string OptionsKey => "Grpc:Client:Consul"; + public override string OptionsKey => $"Grpc:Client:Consul:{typeof(TClient).Name}"; + + public override string[] OptionKeys => new[] { "Grpc:Client:Consul:Default", OptionsKey }; public override IEnumerable GetRequiredModules(IApplicationContext applicationContext, ConsulGrpcClientModuleOptions options) => @@ -19,4 +21,3 @@ public class ConsulGrpcClientModuleOptions : GrpcClientModuleOptions { } - diff --git a/src/Sitko.Core.Grpc.Client/External/ExternalGrpcClientModule.cs b/src/Sitko.Core.Grpc.Client/External/ExternalGrpcClientModule.cs index 3e313c2c6..c5731906e 100644 --- a/src/Sitko.Core.Grpc.Client/External/ExternalGrpcClientModule.cs +++ b/src/Sitko.Core.Grpc.Client/External/ExternalGrpcClientModule.cs @@ -11,6 +11,8 @@ public class ExternalGrpcClientModule : GrpcClientModule $"Grpc:Client:External:{typeof(TClient).Name}"; + public override string[] OptionKeys => new[] { "Grpc:Client:External:Default", OptionsKey }; + protected override void RegisterResolver(IServiceCollection services, ExternalGrpcClientModuleOptions config) => services.AddSingleton>( From ad0aa38c9c0d212f1ac44d2f4be58063a9778f53 Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 17:31:47 +0500 Subject: [PATCH 6/7] feat(inmemorydb): support default options --- src/Sitko.Core.Db.InMemory/InMemoryDatabaseModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sitko.Core.Db.InMemory/InMemoryDatabaseModule.cs b/src/Sitko.Core.Db.InMemory/InMemoryDatabaseModule.cs index a624753c3..0f7fd8e7d 100644 --- a/src/Sitko.Core.Db.InMemory/InMemoryDatabaseModule.cs +++ b/src/Sitko.Core.Db.InMemory/InMemoryDatabaseModule.cs @@ -10,6 +10,7 @@ public class where TDbContext : DbContext { public override string OptionsKey => $"Db:InMemory:{typeof(TDbContext).Name}"; + public override string[] OptionKeys => new[] { "Db:InMemory:Default", OptionsKey }; public override void ConfigureServices(IApplicationContext applicationContext, IServiceCollection services, InMemoryDatabaseModuleOptions startupOptions) @@ -48,4 +49,3 @@ private void ConfigureInMemory(DbContextOptionsBuilder options, applicationContext); } } - From 43430d0871512f8ca07732f845e845cc3c1248b0 Mon Sep 17 00:00:00 2001 From: George Drak Date: Mon, 10 Apr 2023 17:32:08 +0500 Subject: [PATCH 7/7] feat(storage): support default options --- .../FileSystemStorageMetadataModule.cs | 1 + src/Sitko.Core.Storage.FileSystem/FileSystemStorageModule.cs | 1 + src/Sitko.Core.Storage.ImgProxy/ImgProxyStorageModule.cs | 1 + .../PostgresStorageMetadataModule.cs | 1 + src/Sitko.Core.Storage.Remote/RemoteStorageModule.cs | 1 + src/Sitko.Core.Storage.S3/S3StorageMetadataModule.cs | 1 + src/Sitko.Core.Storage.S3/S3StorageModule.cs | 1 + src/Sitko.Core.Storage/Cache/BaseStorageCacheModule.cs | 2 ++ 8 files changed, 9 insertions(+) diff --git a/src/Sitko.Core.Storage.FileSystem/FileSystemStorageMetadataModule.cs b/src/Sitko.Core.Storage.FileSystem/FileSystemStorageMetadataModule.cs index b9fee2c93..d35ebd6e3 100644 --- a/src/Sitko.Core.Storage.FileSystem/FileSystemStorageMetadataModule.cs +++ b/src/Sitko.Core.Storage.FileSystem/FileSystemStorageMetadataModule.cs @@ -7,5 +7,6 @@ public class FileSystemStorageMetadataModule : BaseStorageMetad where TStorageOptions : StorageOptions, IFileSystemStorageOptions { public override string OptionsKey => $"Storage:Metadata:FileSystem:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:Metadata:FileSystem:Default", OptionsKey }; } diff --git a/src/Sitko.Core.Storage.FileSystem/FileSystemStorageModule.cs b/src/Sitko.Core.Storage.FileSystem/FileSystemStorageModule.cs index 6cb551623..c0cfdae4d 100644 --- a/src/Sitko.Core.Storage.FileSystem/FileSystemStorageModule.cs +++ b/src/Sitko.Core.Storage.FileSystem/FileSystemStorageModule.cs @@ -5,4 +5,5 @@ public class where TStorageOptions : StorageOptions, IFileSystemStorageOptions, new() { public override string OptionsKey => $"Storage:FileSystem:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:FileSystem:Default", OptionsKey }; } diff --git a/src/Sitko.Core.Storage.ImgProxy/ImgProxyStorageModule.cs b/src/Sitko.Core.Storage.ImgProxy/ImgProxyStorageModule.cs index 287f75b7b..91b6f3696 100644 --- a/src/Sitko.Core.Storage.ImgProxy/ImgProxyStorageModule.cs +++ b/src/Sitko.Core.Storage.ImgProxy/ImgProxyStorageModule.cs @@ -9,6 +9,7 @@ public class where TStorageOptions : StorageOptions { public override string OptionsKey => $"Storage:ImgProxy:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:ImgProxy:Default", OptionsKey }; public override void ConfigureServices(IApplicationContext applicationContext, IServiceCollection services, BaseApplicationModuleOptions startupOptions) diff --git a/src/Sitko.Core.Storage.Metadata.Postgres/PostgresStorageMetadataModule.cs b/src/Sitko.Core.Storage.Metadata.Postgres/PostgresStorageMetadataModule.cs index 5243a7f6a..3ab6fc05d 100644 --- a/src/Sitko.Core.Storage.Metadata.Postgres/PostgresStorageMetadataModule.cs +++ b/src/Sitko.Core.Storage.Metadata.Postgres/PostgresStorageMetadataModule.cs @@ -12,6 +12,7 @@ public class where TStorageOptions : StorageOptions { public override string OptionsKey => $"Storage:Metadata:Postgres:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:Metadata:Postgres:Default", OptionsKey }; public override void ConfigureServices(IApplicationContext applicationContext, IServiceCollection services, PostgresStorageMetadataModuleOptions startupOptions) diff --git a/src/Sitko.Core.Storage.Remote/RemoteStorageModule.cs b/src/Sitko.Core.Storage.Remote/RemoteStorageModule.cs index 0fba286b3..f43ac7795 100644 --- a/src/Sitko.Core.Storage.Remote/RemoteStorageModule.cs +++ b/src/Sitko.Core.Storage.Remote/RemoteStorageModule.cs @@ -9,6 +9,7 @@ public class where TStorageOptions : StorageOptions, IRemoteStorageOptions, new() { public override string OptionsKey => $"Storage:Remote:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:Remote:Default", OptionsKey }; public override void ConfigureServices(IApplicationContext applicationContext, IServiceCollection services, TStorageOptions startupOptions) diff --git a/src/Sitko.Core.Storage.S3/S3StorageMetadataModule.cs b/src/Sitko.Core.Storage.S3/S3StorageMetadataModule.cs index 7e7af441e..efe6cf930 100644 --- a/src/Sitko.Core.Storage.S3/S3StorageMetadataModule.cs +++ b/src/Sitko.Core.Storage.S3/S3StorageMetadataModule.cs @@ -7,5 +7,6 @@ public class S3StorageMetadataModule : BaseStorageMetadataModul where TStorageOptions : S3StorageOptions, new() { public override string OptionsKey => $"Storage:Metadata:S3:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:Metadata:S3:Default", OptionsKey }; } diff --git a/src/Sitko.Core.Storage.S3/S3StorageModule.cs b/src/Sitko.Core.Storage.S3/S3StorageModule.cs index 09544b391..fa2f6c774 100644 --- a/src/Sitko.Core.Storage.S3/S3StorageModule.cs +++ b/src/Sitko.Core.Storage.S3/S3StorageModule.cs @@ -14,6 +14,7 @@ public class S3StorageModule : StorageModule $"Storage:S3:{typeof(TS3StorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:S3:Default", OptionsKey }; public override void ConfigureServices(IApplicationContext applicationContext, IServiceCollection services, TS3StorageOptions startupOptions) diff --git a/src/Sitko.Core.Storage/Cache/BaseStorageCacheModule.cs b/src/Sitko.Core.Storage/Cache/BaseStorageCacheModule.cs index aad57aa42..35d2a9882 100644 --- a/src/Sitko.Core.Storage/Cache/BaseStorageCacheModule.cs +++ b/src/Sitko.Core.Storage/Cache/BaseStorageCacheModule.cs @@ -23,6 +23,7 @@ public class FileStorageCacheOptions> where TStorageOptions : StorageOptions { public override string OptionsKey => $"Storage:Cache:FileSystem:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:Cache:FileSystem:Default", OptionsKey }; } public class @@ -31,5 +32,6 @@ public class InMemoryStorageCacheOptions> where TStorageOptions : StorageOptions { public override string OptionsKey => $"Storage:Cache:InMemory:{typeof(TStorageOptions).Name}"; + public override string[] OptionKeys => new[] { "Storage:Cache:InMemory:Default", OptionsKey }; }