From ef60157330d2c5fae4eb05ee259fffcad75f7202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Fri, 2 Aug 2019 17:53:13 +0200 Subject: [PATCH] Connection settings are separated from connection string (#8) --- README.md | 21 ++++- src/ConfigurationExtensions.cs | 37 ++++++++ src/KormBuilder.cs | 10 ++- src/Kros.KORM.Extensions.Asp.csproj | 2 +- src/Properties/Resources.Designer.cs | 9 -- src/Properties/Resources.resx | 3 - src/ServiceCollectionExtensions.cs | 98 +++++++++++++++++----- tests/ConfigurationExtensionsShould.cs | 41 +++++++++ tests/KormBuilderShould.cs | 10 +-- tests/ServiceCollectionExtensionsShould.cs | 33 -------- tests/appsettings.json | 28 ++++++- 11 files changed, 213 insertions(+), 79 deletions(-) create mode 100644 src/ConfigurationExtensions.cs create mode 100644 tests/ConfigurationExtensionsShould.cs diff --git a/README.md b/README.md index 5d946eb..15fc633 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ public void ConfigureServices(IServiceCollection services) } ``` -The configuration file *(typically `appsettings.json`)* must contain a standard connection strings section (`ConnectionStrings`) and there must be connection string named `DefaultConnection`. Name of the connection string can be specified as second parameter. +The configuration file *(typically `appsettings.json`)* must contain a standard connection strings section (`ConnectionStrings`). Name of the connection string can be specified as second parameter. If the name is not specified, default name `DefaultConnection` will be used. ``` json "ConnectionStrings": { "DefaultConnection": "Server=ServerName\\InstanceName; Initial Catalog=database; Integrated Security=true", - "localConnection": "Server=Server2\\Instance; Integrated Security=true; KormAutoMigrate=true; KormProvider=System.Data.SqlClient;" + "localConnection": "Server=Server2\\Instance; Integrated Security=true;" } ``` @@ -55,11 +55,24 @@ public void ConfigureServices(IServiceCollection services) } ``` -`AddKorm` extension methods supports additional keys in connection string, which will be used by KORM and **will be removed from the connection string**. These keys are: +KORM supports additional settings for connections: -* `KormAutoMigrate`: The value is boolean `true`/`false`. If not set (or the value is invalid), the default value is `false`. If it is `true`, it allows automatic [database migrations](#database-migrations). +* `AutoMigrate`: The value is boolean `true`/`false`. If not set (or the value is invalid), the default value is `false`. If it is `true`, it allows automatic [database migrations](#database-migrations). * `KormProvider`: This specifies database provider which will be used. If not set, the value `System.Data.SqlClient` will be used. KORM currently supports only Microsoft SQL Server, so there is no need to use this parameter. +These settings (if needed) can also be set in configuration file under the `KormSettings` section. Settings are identified by connection string name. + +``` json +"KormSettings": { + "DefaultConnection": { + "AutoMigrate": true + }, + "localConnection": { + "AutoMigrate": true + } +} +``` + ### Database Migrations For simple database migration, you must call: diff --git a/src/ConfigurationExtensions.cs b/src/ConfigurationExtensions.cs new file mode 100644 index 0000000..5481e0e --- /dev/null +++ b/src/ConfigurationExtensions.cs @@ -0,0 +1,37 @@ +using Kros.KORM; +using Kros.KORM.Extensions.Asp; + +namespace Microsoft.Extensions.Configuration +{ + /// + /// Extensions for . + /// + public static class ConfigurationExtensions + { + /// + /// Returns connection settings with from configuration. Connection settings are merged from + /// sections ConnectionStrings and KormSettings. If key does not exist + /// in either section, is returned. + /// + /// The configuration. + /// The connection string key. + /// or . + public static KormConnectionSettings GetKormConnectionString(this IConfiguration configuration, string name) + { + string connectionString = configuration.GetConnectionString(name); + IConfigurationSection section = configuration.GetSection(KormBuilder.KormSettingsSectionName + ":" + name); + + if (section.Exists()) + { + KormConnectionSettings settings = section.Get(); + settings.ConnectionString = connectionString; + return settings; + } + else if (connectionString != null) + { + return new KormConnectionSettings() { ConnectionString = connectionString }; + } + return null; + } + } +} diff --git a/src/KormBuilder.cs b/src/KormBuilder.cs index 2d9c9e3..01ec376 100644 --- a/src/KormBuilder.cs +++ b/src/KormBuilder.cs @@ -19,6 +19,12 @@ public class KormBuilder /// public const string DefaultConnectionStringName = "DefaultConnection"; + /// + /// Name of the section in configuration file (ususally appsettings.json), where settings for connections + /// are configured. + /// + public const string KormSettingsSectionName = "KormSettings"; + private readonly IDatabaseBuilder _builder; private IMigrationsRunner _migrationsRunner; @@ -28,7 +34,7 @@ public class KormBuilder /// The service collection. /// The database connection string. public KormBuilder(IServiceCollection services, string connectionString) - : this(services, new KormConnectionSettings(connectionString)) + : this(services, new KormConnectionSettings() { ConnectionString = connectionString }) { } @@ -107,7 +113,7 @@ public KormBuilder AddKormMigrations(Action setupAction = null { Services.AddMemoryCache(); MigrationOptions options = SetupMigrationOptions(setupAction); - _migrationsRunner = new MigrationsRunner(ConnectionSettings.GetFullConnectionString(), options); + _migrationsRunner = new MigrationsRunner(ConnectionSettings.ConnectionString, options); return this; } diff --git a/src/Kros.KORM.Extensions.Asp.csproj b/src/Kros.KORM.Extensions.Asp.csproj index cf3de62..5a72bf0 100644 --- a/src/Kros.KORM.Extensions.Asp.csproj +++ b/src/Kros.KORM.Extensions.Asp.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/Properties/Resources.Designer.cs b/src/Properties/Resources.Designer.cs index 384757b..d44a6b1 100644 --- a/src/Properties/Resources.Designer.cs +++ b/src/Properties/Resources.Designer.cs @@ -60,15 +60,6 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to Connection string contains only KORM keys. These are removed and so connection string is empty.. - /// - internal static string ConnectionStringContainsOnlyKormKeys { - get { - return ResourceManager.GetString("ConnectionStringContainsOnlyKormKeys", resourceCulture); - } - } - /// /// Looks up a localized string similar to Database with name "{0}" was already added.. /// diff --git a/src/Properties/Resources.resx b/src/Properties/Resources.resx index 5cb848d..cee8697 100644 --- a/src/Properties/Resources.resx +++ b/src/Properties/Resources.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Connection string contains only KORM keys. These are removed and so connection string is empty. - Database with name "{0}" was already added. 0 - name of the database/connection string diff --git a/src/ServiceCollectionExtensions.cs b/src/ServiceCollectionExtensions.cs index 7afe5ff..1d5982d 100644 --- a/src/ServiceCollectionExtensions.cs +++ b/src/ServiceCollectionExtensions.cs @@ -26,7 +26,7 @@ public static class ServiceCollectionExtensions /// (connection string names in appsettings). /// /// - /// The first database added by any + /// The first database added by any /// method is registered in the DI container also as scoped dependency. So this database can be /// used directly as , without using . /// @@ -35,12 +35,12 @@ public static KormBuilder AddKorm(this IServiceCollection services, IConfigurati => AddKorm(services, configuration, KormBuilder.DefaultConnectionStringName); /// - /// Register KORM into DI container. The connection string with name + /// Register KORM into DI container. The connection string with name /// from is used for database. /// /// The service collection. /// The configuration settings. - /// Name of the connection string in configuration. + /// Name of the connection string in configuration. /// for initialization. /// /// @@ -49,24 +49,20 @@ public static KormBuilder AddKorm(this IServiceCollection services, IConfigurati /// (connection string names in appsettings). /// /// - /// The first database added by any + /// The first database added by any /// method is registered in the DI container also as scoped dependency. So this database can be /// used directly as , without using . /// /// - public static KormBuilder AddKorm( - this IServiceCollection services, - IConfiguration configuration, - string connectionStringName) + public static KormBuilder AddKorm(this IServiceCollection services, IConfiguration configuration, string name) { - Check.NotNullOrWhiteSpace(connectionStringName, nameof(connectionStringName)); - string connectionString = configuration.GetConnectionString(connectionStringName); - if (connectionString is null) + Check.NotNullOrWhiteSpace(name, nameof(name)); + KormConnectionSettings connectionSettings = configuration.GetKormConnectionString(name); + if (connectionSettings is null) { - throw new ArgumentException( - string.Format(Resources.InvalidConnectionStringName, connectionStringName), nameof(connectionStringName)); + throw new ArgumentException(string.Format(Resources.InvalidConnectionStringName, name), nameof(name)); } - return AddKorm(services, connectionString, connectionStringName); + return AddKormBuilder(services, name, connectionSettings); } /// @@ -96,7 +92,7 @@ public static KormBuilder AddKorm( /// (connection string names in appsettings). /// /// - /// The first database added by any + /// The first database added by any /// method is registered in the DI container also as scoped dependency. So this database can be /// used directly as , without using . /// @@ -133,7 +129,7 @@ public static KormBuilder AddKorm(this IServiceCollection services, string conne /// (connection string names in appsettings). /// /// - /// The first database added by any + /// The first database added by any /// method is registered in the DI container also as scoped dependency. So this database can be /// used directly as , without using . /// @@ -143,13 +139,73 @@ public static KormBuilder AddKorm(this IServiceCollection services, string conne Check.NotNull(services, nameof(services)); Check.NotNullOrWhiteSpace(connectionString, nameof(connectionString)); Check.NotNullOrWhiteSpace(name, nameof(name)); + return AddKormBuilder(services, name, new KormConnectionSettings() { ConnectionString = connectionString }); + } - var connectionSettings = new KormConnectionSettings(connectionString); - if (string.IsNullOrWhiteSpace(connectionSettings.ConnectionString)) - { - throw new ArgumentException(Resources.ConnectionStringContainsOnlyKormKeys, nameof(connectionString)); - } + /// + /// Register KORM for database into DI container. + /// + /// The service collection. + /// Connection settings for database. + /// for initialization. + /// + /// The value of any argument is . + /// + /// + /// + /// + /// + /// Method adds an as scoped dependency into DI container. + /// Database () can be retrieved using that factory. Databases are identified by names + /// (connection string names in appsettings). + /// + /// + /// The first database added by any + /// method is registered in the DI container also as scoped dependency. So this database can be + /// used directly as , without using . + /// + /// + public static KormBuilder AddKorm(this IServiceCollection services, KormConnectionSettings connectionSettings) + => AddKorm(services, connectionSettings, KormBuilder.DefaultConnectionStringName); + /// + /// Register KORM for database into DI container. + /// + /// The service collection. + /// Connection settings for database. + /// Name of the database. When the database is added from appsettings, + /// this is the connection string name in the settings. + /// for initialization. + /// + /// The value of any argument is . + /// + /// + /// + /// + /// The value of is empty string, or contains only whitespace characters. + /// + /// + /// + /// + /// + /// Method adds an as scoped dependency into DI container. + /// Database () can be retrieved using that factory. Databases are identified by names + /// (connection string names in appsettings). + /// + /// + /// The first database added by any + /// method is registered in the DI container also as scoped dependency. So this database can be + /// used directly as , without using . + /// + /// + public static KormBuilder AddKorm( + this IServiceCollection services, + KormConnectionSettings connectionSettings, + string name) + { + Check.NotNull(services, nameof(services)); + Check.NotNull(connectionSettings, nameof(connectionSettings)); + Check.NotNullOrWhiteSpace(name, nameof(name)); return AddKormBuilder(services, name, connectionSettings); } diff --git a/tests/ConfigurationExtensionsShould.cs b/tests/ConfigurationExtensionsShould.cs new file mode 100644 index 0000000..96a4ee5 --- /dev/null +++ b/tests/ConfigurationExtensionsShould.cs @@ -0,0 +1,41 @@ +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Kros.KORM.Extensions.Api.UnitTests +{ + public class ConfigurationExtensionsShould + { + private const string TestConnectionString = "server=servername\\instancename;initial catalog=database"; + private const string DefaultKormProvider = Kros.Data.SqlServer.SqlServerDataHelper.ClientId; + private const bool DefaultAutoMigrate = false; + + [Theory] + [InlineData("NoKormSettings", TestConnectionString, DefaultKormProvider, DefaultAutoMigrate)] + [InlineData("NoConnectionString", null, "DolorSitAmet", DefaultAutoMigrate)] + [InlineData("AutoMigrateTrue", TestConnectionString, DefaultKormProvider, true)] + [InlineData("CustomProvider", TestConnectionString, "LoremIpsum", true)] + [InlineData("InvalidSettings", TestConnectionString, DefaultKormProvider, DefaultAutoMigrate)] + public void LoadCorrectKormConnectionSetings(string name, string connectionString, string kormProvider, bool autoMigrate) + { + IConfigurationRoot configuration = ConfigurationHelper.GetConfiguration(); + var expected = new KormConnectionSettings() + { + ConnectionString = connectionString, + KormProvider = kormProvider, + AutoMigrate = autoMigrate + }; + + KormConnectionSettings actual = configuration.GetKormConnectionString(name); + + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void ReturnNullIfNameIsNotInEitherSection() + { + IConfigurationRoot configuration = ConfigurationHelper.GetConfiguration(); + configuration.GetKormConnectionString("ThisNameDoesNotExist").Should().BeNull(); + } + } +} diff --git a/tests/KormBuilderShould.cs b/tests/KormBuilderShould.cs index c40c83a..6d5fa38 100644 --- a/tests/KormBuilderShould.cs +++ b/tests/KormBuilderShould.cs @@ -19,17 +19,17 @@ public void ThrowArgumentExceptionWhenArgumentsAreInvalid() Action action = () => new KormBuilder(null, connectionString); action.Should().Throw().And.ParamName.Should().Be("services"); - action = () => new KormBuilder(services, (string)null); - action.Should().Throw().And.ParamName.Should().Be("connectionString"); - action = () => new KormBuilder(services, (KormConnectionSettings)null); action.Should().Throw().And.ParamName.Should().Be("connectionSettings"); + action = () => new KormBuilder(services, (string)null); + action.Should().Throw().And.ParamName.Should().Be("connectionSettings"); + action = () => new KormBuilder(services, string.Empty); - action.Should().Throw().And.ParamName.Should().Be("connectionString"); + action.Should().Throw().And.ParamName.Should().Be("connectionSettings"); action = () => new KormBuilder(services, " \t "); - action.Should().Throw().And.ParamName.Should().Be("connectionString"); + action.Should().Throw().And.ParamName.Should().Be("connectionSettings"); } [Fact] diff --git a/tests/ServiceCollectionExtensionsShould.cs b/tests/ServiceCollectionExtensionsShould.cs index b0b8b7a..fabc8f2 100644 --- a/tests/ServiceCollectionExtensionsShould.cs +++ b/tests/ServiceCollectionExtensionsShould.cs @@ -64,39 +64,6 @@ public void ThrowArgumentExceptionIfDefaultConnectionStringNameDoesNotExist() action.Should().Throw().WithMessage($"*{KormBuilder.DefaultConnectionStringName}*"); } - [Fact] - public void ThrowArgumentExceptionIfConnectionStringContainsOnlyKormValues() - { - Action action = () => - { - var services = new ServiceCollection(); - services.AddKorm("KormProvider=LoremIpsum;KormAutoMigrate=false"); - }; - - action.Should().Throw().And.ParamName.Should().Be("connectionString"); - } - - [Theory] - [InlineData("server=localhost;", DefaultProviderName, false)] - [InlineData("server=localhost;KormProvider=", DefaultProviderName, false)] - [InlineData("server=localhost;KormProvider=' \t '", DefaultProviderName, false)] - [InlineData("server=localhost;KormProvider=LoremIpsum", "LoremIpsum", false)] - [InlineData("server=localhost;KormAutoMigrate=true", DefaultProviderName, true)] - [InlineData("server=localhost;KormAutoMigrate=false", DefaultProviderName, false)] - [InlineData("server=localhost;KormAutoMigrate=InvalidValue", DefaultProviderName, false)] - [InlineData("server=localhost;KormAutoMigrate=", DefaultProviderName, false)] - [InlineData("server=localhost;KormAutoMigrate=' \t '", DefaultProviderName, false)] - public void ParseKormConnectionStringKeys(string connectionString, string provider, bool autoMigrate) - { - var services = new ServiceCollection(); - - KormBuilder builder = services.AddKorm(connectionString); - - builder.ConnectionSettings.KormProvider.Should().Be(provider); - builder.ConnectionSettings.AutoMigrate.Should().Be(autoMigrate); - builder.ConnectionSettings.ConnectionString.Should().Be("server=localhost"); - } - [Fact] public void AddFirstDatabaseAsIDatabaseToServiceCollection() { diff --git a/tests/appsettings.json b/tests/appsettings.json index 0318079..6d0c555 100644 --- a/tests/appsettings.json +++ b/tests/appsettings.json @@ -12,10 +12,36 @@ } } }, + "ConnectionStrings": { "DefaultConnection": "server=servername\\instancename;initial catalog=database", - // Connection string for tests. + "NoKormSettings": "server=servername\\instancename;initial catalog=database", + "AutoMigrateTrue": "server=servername\\instancename;initial catalog=database", + "CustomProvider": "server=servername\\instancename;initial catalog=database", + "InvalidSettings": "server=servername\\instancename;initial catalog=database", + + // Connection string for database tests. "IdGenerator": "Server=CENSQL\\SQL16ENT; User Id=KrosPlus; Password=7040; Persist Security Info = true" + }, + + "KormSettings": { + "AutoMigrateTrue": { + "AutoMigrate": true + }, + + "CustomProvider": { + "AutoMigrate": true, + "KormProvider": "LoremIpsum" + }, + + "NoConnectionString": { + "KormProvider": "DolorSitAmet" + }, + + "InvalidSettings": { + "NonExistingProp1": true, + "NonExistingProp2": "LoremIpsum" + } } }