Skip to content

Commit

Permalink
Connection settings are separated from connection string (Kros-sk#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
satano authored Aug 2, 2019
1 parent e2437eb commit ef60157
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 79 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;"
}
```

Expand All @@ -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:
Expand Down
37 changes: 37 additions & 0 deletions src/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Kros.KORM;
using Kros.KORM.Extensions.Asp;

namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// Extensions for <see cref="IConfiguration"/>.
/// </summary>
public static class ConfigurationExtensions
{
/// <summary>
/// Returns connection settings with <paramref name="name"/> from configuration. Connection settings are merged from
/// sections <c>ConnectionStrings</c> and <c>KormSettings</c>. If key <paramref name="name"/> does not exist
/// in either section, <see langword="null"/> is returned.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="name">The connection string key.</param>
/// <returns><see cref="KormConnectionSettings"/> or <see langword="null"/>.</returns>
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<KormConnectionSettings>();
settings.ConnectionString = connectionString;
return settings;
}
else if (connectionString != null)
{
return new KormConnectionSettings() { ConnectionString = connectionString };
}
return null;
}
}
}
10 changes: 8 additions & 2 deletions src/KormBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public class KormBuilder
/// </summary>
public const string DefaultConnectionStringName = "DefaultConnection";

/// <summary>
/// Name of the section in configuration file (ususally <c>appsettings.json</c>), where settings for connections
/// are configured.
/// </summary>
public const string KormSettingsSectionName = "KormSettings";

private readonly IDatabaseBuilder _builder;
private IMigrationsRunner _migrationsRunner;

Expand All @@ -28,7 +34,7 @@ public class KormBuilder
/// <param name="services">The service collection.</param>
/// <param name="connectionString">The database connection string.</param>
public KormBuilder(IServiceCollection services, string connectionString)
: this(services, new KormConnectionSettings(connectionString))
: this(services, new KormConnectionSettings() { ConnectionString = connectionString })
{
}

Expand Down Expand Up @@ -107,7 +113,7 @@ public KormBuilder AddKormMigrations(Action<MigrationOptions> setupAction = null
{
Services.AddMemoryCache();
MigrationOptions options = SetupMigrationOptions(setupAction);
_migrationsRunner = new MigrationsRunner(ConnectionSettings.GetFullConnectionString(), options);
_migrationsRunner = new MigrationsRunner(ConnectionSettings.ConnectionString, options);
return this;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Kros.KORM.Extensions.Asp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Kros.KORM" Version="4.0.0-alpha.8" />
<PackageReference Include="Kros.KORM" Version="4.0.0-alpha.9" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
Expand Down
9 changes: 0 additions & 9 deletions src/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions src/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConnectionStringContainsOnlyKormKeys" xml:space="preserve">
<value>Connection string contains only KORM keys. These are removed and so connection string is empty.</value>
</data>
<data name="DuplicateDatabaseName" xml:space="preserve">
<value>Database with name "{0}" was already added.</value>
<comment>0 - name of the database/connection string</comment>
Expand Down
98 changes: 77 additions & 21 deletions src/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static class ServiceCollectionExtensions
/// (connection string names in <c>appsettings</c>).
/// </para>
/// <para>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, string, string)" autoupgrade="true"/>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, IConfiguration)" autoupgrade="true"/>
/// method is registered in the DI container also as <see cref="IDatabase"/> scoped dependency. So this database can be
/// used directly as <see cref="IDatabase"/>, without using <see cref="IDatabaseFactory"/>.
/// </para>
Expand All @@ -35,12 +35,12 @@ public static KormBuilder AddKorm(this IServiceCollection services, IConfigurati
=> AddKorm(services, configuration, KormBuilder.DefaultConnectionStringName);

/// <summary>
/// Register KORM into DI container. The connection string with name <paramref name="connectionStringName"/>
/// Register KORM into DI container. The connection string with name <paramref name="name"/>
/// from <paramref name="configuration"/> is used for database.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configuration">The configuration settings.</param>
/// <param name="connectionStringName">Name of the connection string in configuration.</param>
/// <param name="name">Name of the connection string in configuration.</param>
/// <returns><see cref="KormBuilder"/> for <see cref="IDatabase"/> initialization.</returns>
/// <remarks>
/// <para>
Expand All @@ -49,24 +49,20 @@ public static KormBuilder AddKorm(this IServiceCollection services, IConfigurati
/// (connection string names in <c>appsettings</c>).
/// </para>
/// <para>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, string, string)" autoupgrade="true"/>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, IConfiguration)" autoupgrade="true"/>
/// method is registered in the DI container also as <see cref="IDatabase"/> scoped dependency. So this database can be
/// used directly as <see cref="IDatabase"/>, without using <see cref="IDatabaseFactory"/>.
/// </para>
/// </remarks>
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);
}

/// <summary>
Expand Down Expand Up @@ -96,7 +92,7 @@ public static KormBuilder AddKorm(
/// (connection string names in <c>appsettings</c>).
/// </para>
/// <para>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, string, string)" autoupgrade="true"/>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, IConfiguration)" autoupgrade="true"/>
/// method is registered in the DI container also as <see cref="IDatabase"/> scoped dependency. So this database can be
/// used directly as <see cref="IDatabase"/>, without using <see cref="IDatabaseFactory"/>.
/// </para>
Expand Down Expand Up @@ -133,7 +129,7 @@ public static KormBuilder AddKorm(this IServiceCollection services, string conne
/// (connection string names in <c>appsettings</c>).
/// </para>
/// <para>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, string, string)" autoupgrade="true"/>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, IConfiguration)" autoupgrade="true"/>
/// method is registered in the DI container also as <see cref="IDatabase"/> scoped dependency. So this database can be
/// used directly as <see cref="IDatabase"/>, without using <see cref="IDatabaseFactory"/>.
/// </para>
Expand All @@ -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));
}
/// <summary>
/// Register KORM for database <paramref name="connectionSettings"/> into DI container.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="connectionSettings">Connection settings for database.</param>
/// <returns><see cref="KormBuilder"/> for <see cref="IDatabase"/> initialization.</returns>
/// <exception cref="ArgumentNullException">
/// The value of any argument is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
/// <remarks>
/// <para>
/// Method adds an <see cref="IDatabaseFactory"/> as scoped dependency into DI container.
/// Database (<see cref="IDatabase"/>) can be retrieved using that factory. Databases are identified by names
/// (connection string names in <c>appsettings</c>).
/// </para>
/// <para>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, IConfiguration)" autoupgrade="true"/>
/// method is registered in the DI container also as <see cref="IDatabase"/> scoped dependency. So this database can be
/// used directly as <see cref="IDatabase"/>, without using <see cref="IDatabaseFactory"/>.
/// </para>
/// </remarks>
public static KormBuilder AddKorm(this IServiceCollection services, KormConnectionSettings connectionSettings)
=> AddKorm(services, connectionSettings, KormBuilder.DefaultConnectionStringName);

/// <summary>
/// Register KORM for database <paramref name="connectionSettings"/> into DI container.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="connectionSettings">Connection settings for database.</param>
/// <param name="name">Name of the database. When the database is added from <c>appsettings</c>,
/// this is the connection string name in the settings.</param>
/// <returns><see cref="KormBuilder"/> for <see cref="IDatabase"/> initialization.</returns>
/// <exception cref="ArgumentNullException">
/// The value of any argument is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <list type="bullet">
/// <item>
/// The value of <paramref name="name"/> is empty string, or contains only whitespace characters.
/// </item>
/// </list>
/// </exception>
/// <remarks>
/// <para>
/// Method adds an <see cref="IDatabaseFactory"/> as scoped dependency into DI container.
/// Database (<see cref="IDatabase"/>) can be retrieved using that factory. Databases are identified by names
/// (connection string names in <c>appsettings</c>).
/// </para>
/// <para>
/// <b>The first</b> database added by any <see cref="AddKorm(IServiceCollection, IConfiguration)" autoupgrade="true"/>
/// method is registered in the DI container also as <see cref="IDatabase"/> scoped dependency. So this database can be
/// used directly as <see cref="IDatabase"/>, without using <see cref="IDatabaseFactory"/>.
/// </para>
/// </remarks>
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);
}

Expand Down
41 changes: 41 additions & 0 deletions tests/ConfigurationExtensionsShould.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
10 changes: 5 additions & 5 deletions tests/KormBuilderShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ public void ThrowArgumentExceptionWhenArgumentsAreInvalid()
Action action = () => new KormBuilder(null, connectionString);
action.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("services");

action = () => new KormBuilder(services, (string)null);
action.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("connectionString");

action = () => new KormBuilder(services, (KormConnectionSettings)null);
action.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("connectionSettings");

action = () => new KormBuilder(services, (string)null);
action.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("connectionSettings");

action = () => new KormBuilder(services, string.Empty);
action.Should().Throw<ArgumentException>().And.ParamName.Should().Be("connectionString");
action.Should().Throw<ArgumentException>().And.ParamName.Should().Be("connectionSettings");

action = () => new KormBuilder(services, " \t ");
action.Should().Throw<ArgumentException>().And.ParamName.Should().Be("connectionString");
action.Should().Throw<ArgumentException>().And.ParamName.Should().Be("connectionSettings");
}

[Fact]
Expand Down
Loading

0 comments on commit ef60157

Please sign in to comment.