diff --git a/README.md b/README.md
index b8facc9..40c58c0 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
-# tinyconfig
-TinyConfig is a simple library to build custom configuration provider in .NET with reload support.
+# TinyConfig
+
+TinyConfig is a simple library to democratize building custom configuration provider in .NET.
diff --git a/TinyConfig.sln b/TinyConfig.sln
new file mode 100644
index 0000000..6f16465
--- /dev/null
+++ b/TinyConfig.sln
@@ -0,0 +1,18 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/TinyConfig.Abstractions/ITinyConfigOptions.cs b/src/TinyConfig.Abstractions/ITinyConfigOptions.cs
new file mode 100644
index 0000000..bfb5197
--- /dev/null
+++ b/src/TinyConfig.Abstractions/ITinyConfigOptions.cs
@@ -0,0 +1,26 @@
+
+namespace TinyConfig.Abstractions
+{
+ ///
+ /// tiny config options
+ ///
+ public interface ITinyConfigOptions
+ {
+ ///
+ /// indicate whether tiny config is enabled, false by default
+ ///
+ bool Enabled { get => false; }
+
+ ///
+ /// indicate whether configuration should be reloaded when a change occurs
+ /// Has default value "false"
+ ///
+ bool ReloadOnChange { get => false; }
+
+ ///
+ /// number of seconds to wait before reloading after a change
+ /// Has default value "60" seconds
+ ///
+ int ReloadDelay { get => 60; }
+ }
+}
diff --git a/src/TinyConfig.Abstractions/ITinyConfigStore.cs b/src/TinyConfig.Abstractions/ITinyConfigStore.cs
new file mode 100644
index 0000000..65aeb93
--- /dev/null
+++ b/src/TinyConfig.Abstractions/ITinyConfigStore.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace TinyConfig.Abstractions
+{
+ ///
+ /// wraps a configuration store, providing access to the actual configuration values
+ ///
+ public interface ITinyConfigStore
+ {
+ ///
+ /// check if a change has occurred on the configuration store
+ ///
+ ///
+ /// true is settings has changed
+ Task HasChanged(object versionToken = null);
+
+ ///
+ /// get all settings from configuration store
+ ///
+ /// a list containing all configuration key value pairs, with table version token
+ Task<(IEnumerable, object)> GetAllWithVersionToken();
+ }
+}
diff --git a/src/TinyConfig.Abstractions/ITinySetting.cs b/src/TinyConfig.Abstractions/ITinySetting.cs
new file mode 100644
index 0000000..1ff8e61
--- /dev/null
+++ b/src/TinyConfig.Abstractions/ITinySetting.cs
@@ -0,0 +1,24 @@
+
+namespace TinyConfig.Abstractions
+{
+ ///
+ /// settings model
+ ///
+ public interface ITinySetting
+ {
+ ///
+ /// settings id (key)
+ ///
+ string Id { get; }
+
+ ///
+ /// settings value
+ ///
+ string Value { get; }
+
+ ///
+ /// settings version
+ ///
+ byte[] Version { get; }
+ }
+}
diff --git a/src/TinyConfig.Abstractions/TinyConfig.Abstractions.csproj b/src/TinyConfig.Abstractions/TinyConfig.Abstractions.csproj
new file mode 100644
index 0000000..f51c9c8
--- /dev/null
+++ b/src/TinyConfig.Abstractions/TinyConfig.Abstractions.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.1
+
+
+
diff --git a/src/TinyConfig.Core/Extensions/ConfigurationBuilderExtensions.cs b/src/TinyConfig.Core/Extensions/ConfigurationBuilderExtensions.cs
new file mode 100644
index 0000000..d6517d3
--- /dev/null
+++ b/src/TinyConfig.Core/Extensions/ConfigurationBuilderExtensions.cs
@@ -0,0 +1,38 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.Core.Extensions
+{
+ ///
+ /// configuration builder extensions
+ ///
+ public static class ConfigurationBuilderExtensions
+ {
+ private const string ENABLED_FLAG = "TinyConfig:Enabled";
+ private const string CONFIG_SECTION = "TinyConfig";
+
+ ///
+ /// add db configuration to list of config
+ ///
+ ///
+ /// existing configuration value
+ /// logger instance
+ ///
+ public static IConfigurationBuilder AddTinyConfig(
+ this IConfigurationBuilder builder, IConfiguration configuration,
+ ILogger logger = null)
+ where TOptions : class, ITinyConfigOptions, new()
+ where TStore : class, ITinyConfigStore
+ {
+ if (configuration.GetValue(ENABLED_FLAG))
+ {
+ var config = new TOptions();
+ configuration.Bind(CONFIG_SECTION, config);
+ return builder.Add(new TinyConfigSource(config, logger));
+ }
+
+ return builder;
+ }
+ }
+}
diff --git a/src/TinyConfig.Core/Extensions/ServiceCollectionExtensions.cs b/src/TinyConfig.Core/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..c42fabd
--- /dev/null
+++ b/src/TinyConfig.Core/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.Core.Extensions
+{
+ ///
+ /// service collection extensions
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// add tiny configuration reloader
+ ///
+ ///
+ ///
+ /// concrete implementation for config
+ /// concrete implementation for store
+ ///
+ public static IServiceCollection AddTinyConfigReloader(this IServiceCollection services,
+ IConfiguration configuration)
+ where TOptions : class, ITinyConfigOptions, new()
+ where TStore : class, ITinyConfigStore =>
+ services.AddSingleton((IConfigurationRoot) configuration)
+ .AddHostedService>();
+ }
+}
diff --git a/src/TinyConfig.Core/TinyConfig.Core.csproj b/src/TinyConfig.Core/TinyConfig.Core.csproj
new file mode 100644
index 0000000..fcfc1eb
--- /dev/null
+++ b/src/TinyConfig.Core/TinyConfig.Core.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.1
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TinyConfig.Core/TinyConfigChangeWatcher.cs b/src/TinyConfig.Core/TinyConfigChangeWatcher.cs
new file mode 100644
index 0000000..115c1df
--- /dev/null
+++ b/src/TinyConfig.Core/TinyConfigChangeWatcher.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.Core
+{
+ ///
+ /// Background service to notify about configuration data changes.
+ ///
+ public class TinyConfigChangeWatcher : BackgroundService
+ where TOptions : class, ITinyConfigOptions
+ where TStore : class, ITinyConfigStore
+ {
+ private readonly ILogger> _logger;
+ private readonly IEnumerable> _configProviders;
+
+ ///
+ /// Initializes a new instance of the class.
+ /// test.
+ ///
+ ///
+ ///
+ public TinyConfigChangeWatcher(IConfigurationRoot configurationRoot, ILogger> logger)
+ {
+ if (configurationRoot == null)
+ {
+ throw new ArgumentNullException(nameof(configurationRoot));
+ }
+
+ _logger = logger;
+ _configProviders = configurationRoot.Providers.OfType>().Where(p => p.ConfigurationSource.Options.ReloadOnChange).ToList() !;
+ }
+
+ ///
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ var timers = new Dictionary(); // key - index of config provider, value - timer
+ var minTime = 6000;
+ var i = 0;
+ foreach (var provider in _configProviders)
+ {
+ var waitForSec = provider.ConfigurationSource.Options.ReloadDelay;
+ minTime = Math.Min(minTime, waitForSec);
+ timers[i] = waitForSec;
+ i++;
+ }
+
+ _logger.LogInformation($"DbConfigChangeWatcher will use {minTime} seconds interval");
+
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(minTime), stoppingToken).ConfigureAwait(false);
+ if (stoppingToken.IsCancellationRequested)
+ {
+ break;
+ }
+
+ for (var j = 0; j < _configProviders.Count(); j++)
+ {
+ var timer = timers[j];
+ timer -= minTime;
+ if (timer <= 0)
+ {
+ _configProviders.ElementAt(j).Load();
+ timers[j] = _configProviders.ElementAt(j).ConfigurationSource.Options.ReloadDelay;
+ }
+ else
+ {
+ timers[j] = timer;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/TinyConfig.Core/TinyConfigProvider.cs b/src/TinyConfig.Core/TinyConfigProvider.cs
new file mode 100644
index 0000000..a5caf27
--- /dev/null
+++ b/src/TinyConfig.Core/TinyConfigProvider.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.Core
+{
+ ///
+ /// tiny configuration provider
+ ///
+ /// concrete implementation of configuration option
+ /// concrete implementation of configuration store
+ public class TinyConfigProvider : ConfigurationProvider
+ where TOptions : class, ITinyConfigOptions
+ where TStore : class, ITinyConfigStore
+ {
+ ///
+ /// configuration source
+ ///
+ public readonly TinyConfigSource ConfigurationSource;
+ private readonly ITinyConfigStore _store;
+ private readonly ILogger _logger;
+ private readonly Dictionary _versionsCache = new Dictionary();
+ private object VersionToken;
+
+ ///
+ /// ctor
+ ///
+ ///
+ ///
+ ///
+ public TinyConfigProvider(ITinyConfigStore store,
+ TinyConfigSource source,
+ ILogger logger = null)
+ {
+ ConfigurationSource = source;
+ _store = store;
+ _logger = logger;
+ }
+
+ ///
+ /// load configuration from database
+ ///
+ public override void Load()
+ {
+ var anyCheck = _store.HasChanged(VersionToken);
+ anyCheck.Wait();
+ if (anyCheck.Result)
+ {
+ _logger?.LogDebug("loading configuration from store: {0}", typeof(TStore));
+ var reload = LoadDatabaseConfigs();
+ _logger?.LogDebug("loaded configuration from store: {0}", typeof(TStore));
+ if (reload) OnReload();
+ }
+ }
+
+ ///
+ /// reload config data
+ ///
+ public bool LoadDatabaseConfigs()
+ {
+ var reload = false;
+ var allConfigs = _store.GetAllWithVersionToken();
+ allConfigs.Wait();
+
+ var (settings, lastVersion) = allConfigs.Result;
+
+ foreach (var config in settings)
+ {
+ if (!_versionsCache.ContainsKey(config.Id))
+ {
+ Set(config.Id, config.Value);
+ _versionsCache[config.Id] = config.Version;
+ reload = true;
+ } else
+ {
+ if (_versionsCache.TryGetValue(config.Id, out byte[] version)
+ && !version.SequenceEqual(config.Version))
+ {
+ Set(config.Id, config.Value);
+ _versionsCache[config.Id] = config.Version;
+ reload = true;
+ }
+ }
+ }
+
+ VersionToken = lastVersion;
+
+ return reload;
+ }
+ }
+}
diff --git a/src/TinyConfig.Core/TinyConfigSource.cs b/src/TinyConfig.Core/TinyConfigSource.cs
new file mode 100644
index 0000000..fc3d14c
--- /dev/null
+++ b/src/TinyConfig.Core/TinyConfigSource.cs
@@ -0,0 +1,46 @@
+using System;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.Core
+{
+ ///
+ /// configuration source builder
+ ///
+ /// concrete implementation of configuration options
+ /// concrete implementation of configuration store
+ public class TinyConfigSource : IConfigurationSource
+ where TOptions : class, ITinyConfigOptions
+ where TStore : class, ITinyConfigStore
+ {
+ ///
+ /// configuration options
+ ///
+ public TOptions Options;
+ private readonly ILogger _logger;
+
+ ///
+ /// ctor
+ ///
+ ///
+ ///
+ public TinyConfigSource(TOptions options, ILogger logger = null)
+ {
+ Options = options;
+ _logger = logger;
+ }
+
+ ///
+ /// build configuration source
+ ///
+ ///
+ ///
+ public IConfigurationProvider Build(IConfigurationBuilder builder)
+ {
+ var constructor = typeof(TStore).GetConstructor(new Type[] { typeof(TOptions) });
+ ITinyConfigStore store = constructor.Invoke(new object[] { Options }) as ITinyConfigStore;
+ return new TinyConfigProvider(store, this, _logger);
+ }
+ }
+}
diff --git a/src/TinyConfig.SqlServer/Core/SimpleSetting.cs b/src/TinyConfig.SqlServer/Core/SimpleSetting.cs
new file mode 100644
index 0000000..6f474d4
--- /dev/null
+++ b/src/TinyConfig.SqlServer/Core/SimpleSetting.cs
@@ -0,0 +1,30 @@
+using System;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.SqlServer.Core
+{
+ ///
+ /// simple setting model
+ ///
+ public class SimpleSetting : ITinySetting
+ {
+ ///
+ public string Id { get; set; }
+
+ ///
+ public string Value { get; set; }
+
+ ///
+ public byte[] Version { get; set; }
+
+ ///
+ /// indicate if field is a secret
+ ///
+ public bool IsSecret { get; set; }
+
+ ///
+ /// date entry was last modified
+ ///
+ public DateTime LastModifiedOn { get; set; }
+ }
+}
diff --git a/src/TinyConfig.SqlServer/Core/SqlServerConfigOptions.cs b/src/TinyConfig.SqlServer/Core/SqlServerConfigOptions.cs
new file mode 100644
index 0000000..83190cc
--- /dev/null
+++ b/src/TinyConfig.SqlServer/Core/SqlServerConfigOptions.cs
@@ -0,0 +1,39 @@
+using System.Diagnostics.CodeAnalysis;
+using TinyConfig.Abstractions;
+
+namespace TinyConfig.SqlServer.Core
+{
+ ///
+ /// simple db config options
+ ///
+ public class SqlServerConfigOptions : ITinyConfigOptions
+ {
+ ///
+ /// connection string
+ ///
+ [NotNull]
+ public string ConnectionString { get; }
+
+ ///
+ /// table name for storing settings
+ ///
+ [NotNull]
+ public string TableName { get; }
+
+ ///
+ /// encryption key for sensitive credentials
+ ///
+ [NotNull]
+ public string EncryptionKey { get; set; }
+
+ ///
+ /// indicate whether to reload on change
+ ///
+ public bool ReloadOnChange { get; set; }
+
+ ///
+ /// wait time before retrying reloading for changes
+ ///
+ public int ReloadDelay { get; set; } = 60;
+ }
+}
diff --git a/src/TinyConfig.SqlServer/Core/SqlServerConfigStore.cs b/src/TinyConfig.SqlServer/Core/SqlServerConfigStore.cs
new file mode 100644
index 0000000..0b63991
--- /dev/null
+++ b/src/TinyConfig.SqlServer/Core/SqlServerConfigStore.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Data.SqlClient;
+using Dapper;
+using TinyConfig.Abstractions;
+using TinyConfig.SqlServer.Cryptography;
+
+namespace TinyConfig.SqlServer.Core
+{
+ ///
+ /// simple db config store
+ ///
+ public class SqlServerConfigStore : ITinyConfigStore
+ {
+ private readonly SqlServerConfigOptions _options;
+ private readonly AesCrypto _aes;
+
+ public SqlServerConfigStore(SqlServerConfigOptions options)
+ {
+ _options = options;
+ _aes = new AesCrypto(options.EncryptionKey);
+ }
+
+ ///
+ public async Task<(IEnumerable, object)> GetAllWithVersionToken()
+ {
+ using var conn = CreateConnection();
+ var result = await conn.QueryMultipleAsync($"{SelectAllQuery}; {LastVersionQuery}");
+ var settings = await result.ReadAsync();
+ var lastVersion = await result.ReadAsync();
+
+ return (settings.Select(entry => {
+ if (entry.IsSecret) entry.Value = _aes.Decrypt(entry.Value);
+ return entry;
+ }), lastVersion);
+ }
+
+ ///
+ public async Task HasChanged(object versionToken = null)
+ {
+ using var conn = CreateConnection();
+ var mostRecentLastModified = await conn.ExecuteScalarAsync(LastVersionQuery);
+ if (mostRecentLastModified.HasValue && versionToken != null)
+ return mostRecentLastModified.Value.CompareTo(versionToken) != 0;
+ return mostRecentLastModified.HasValue;
+ }
+
+ ///
+ /// create new
+ ///
+ ///
+ private SqlConnection CreateConnection() => new SqlConnection(_options.ConnectionString);
+
+ private string SelectAllQuery => $"SELECT * FROM {_options.TableName}";
+
+ private string LastVersionQuery => $"SELECT TOP 1 LastModifiedOn FROM {_options.TableName} ORDER BY LastModifiedOn DESC";
+ }
+}
diff --git a/src/TinyConfig.SqlServer/Cryptography/AesCrypto.cs b/src/TinyConfig.SqlServer/Cryptography/AesCrypto.cs
new file mode 100644
index 0000000..ebf832c
--- /dev/null
+++ b/src/TinyConfig.SqlServer/Cryptography/AesCrypto.cs
@@ -0,0 +1,114 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace TinyConfig.SqlServer.Cryptography
+{
+ ///
+ /// AES helper
+ ///
+ public class AesCrypto
+ {
+ private readonly byte[] _passwordBytes;
+ private const int SALT_SIZE = 16;
+
+ ///
+ /// creates a new
+ ///
+ ///
+ public AesCrypto(string password)
+ {
+ _passwordBytes = Encoding.UTF8.GetBytes(password);
+ }
+
+ #region helper methods
+ private static byte[] GenerateSalt()
+ {
+ // initialize a byte array to hold salt
+ var data = new byte[SALT_SIZE];
+ // create an instance of random number generator
+ using var rng = new RNGCryptoServiceProvider();
+ // generate random numbers and fill salt with the generated value
+ for (int i = 0; i < 10; i++) rng.GetBytes(data);
+ // return filled byte array
+ return data;
+ }
+
+ private RijndaelManaged CreateAes(byte[] salt)
+ {
+ const int keySize = 256;
+ const int blockSize = 128;
+ const int ITERATIONS = 200;
+ // create key from password and salt
+ var key = new Rfc2898DeriveBytes(_passwordBytes, salt, ITERATIONS);
+
+ // create an managed aes instance
+ return new RijndaelManaged()
+ {
+ KeySize = keySize,
+ BlockSize = blockSize,
+ Mode = CipherMode.CBC,
+ Padding = PaddingMode.PKCS7,
+ Key = key.GetBytes(keySize / 8),
+ IV = key.GetBytes(blockSize / 8)
+ };
+ }
+ #endregion
+
+ #region Encrypt
+ ///
+ /// encrypt and return encrypted string
+ ///
+ ///
+ public string Encrypt(string data)
+ {
+ // generate salt, unique per call
+ var salt = GenerateSalt();
+
+ // create an managed aes instance
+ var aes = CreateAes(salt);
+
+ // write salt to output file
+ using var resultStream = new MemoryStream();
+ resultStream.Write(salt, 0, salt.Length);
+
+ // create a crypto stream in write mode with outputfile and configured aes instance
+ using(var cryptoStream = new CryptoStream(resultStream, aes.CreateEncryptor(), CryptoStreamMode.Write, true))
+ {
+ using var swEncrypt = new StreamWriter(cryptoStream, Encoding.UTF8);
+ //Write all data to the stream.
+ swEncrypt.Write(data);
+ }
+
+ resultStream.Position = 0;
+ return Convert.ToBase64String(resultStream.ToArray());
+ }
+ #endregion
+
+ #region Decrypt
+ ///
+ /// decrypt and return decrypted string
+ ///
+ public string Decrypt(string data)
+ {
+ var plainText = string.Empty;
+ var salt = new byte[SALT_SIZE];
+ using var inputStream = new MemoryStream(Convert.FromBase64String(data));
+ inputStream.Read(salt, 0, salt.Length);
+ // create an managed aes instance
+ var aes = CreateAes(salt);
+
+ // create a crypto stream in read mode with input file and configured aes instance
+ using(var cryptoStream = new CryptoStream(inputStream, aes.CreateDecryptor(), CryptoStreamMode.Read, true))
+ {
+ using var srDecrypt = new StreamReader(cryptoStream);
+ // Read the decrypted bytes from the decrypting stream
+ // and place them in a string.
+ plainText = srDecrypt.ReadToEnd();
+ }
+ return plainText;
+ }
+ #endregion
+ }
+}
diff --git a/src/TinyConfig.SqlServer/Extensions/ConfigurationBuilderExtensions.cs b/src/TinyConfig.SqlServer/Extensions/ConfigurationBuilderExtensions.cs
new file mode 100644
index 0000000..718f67e
--- /dev/null
+++ b/src/TinyConfig.SqlServer/Extensions/ConfigurationBuilderExtensions.cs
@@ -0,0 +1,25 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using TinyConfig.Core.Extensions;
+using TinyConfig.SqlServer.Core;
+
+namespace TinyConfig.SqlServer.Extensions
+{
+ ///
+ /// configuration builder extensions
+ ///
+ public static class ConfigurationBuilderExtensions
+ {
+ ///
+ /// add db configuration to list of config
+ ///
+ ///
+ /// existing configuration value
+ /// logger instance
+ ///
+ public static IConfigurationBuilder AddSqlServerConfig(
+ this IConfigurationBuilder builder, IConfiguration configuration,
+ ILogger logger = null) =>
+ builder.AddTinyConfig(configuration, logger);
+ }
+}
diff --git a/src/TinyConfig.SqlServer/Extensions/ServiceCollectionExtensions.cs b/src/TinyConfig.SqlServer/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..1d82e63
--- /dev/null
+++ b/src/TinyConfig.SqlServer/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using TinyConfig.Core.Extensions;
+using TinyConfig.SqlServer.Core;
+
+namespace TinyConfig.SqlServer.Extensions
+{
+ ///
+ /// service collection extensions
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// add database configuration reloader
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection AddSqlServerConfigReloader(this IServiceCollection services,
+ IConfiguration configuration) =>
+ services.AddTinyConfigReloader(configuration);
+ }
+}
diff --git a/src/TinyConfig.SqlServer/TinyConfig.SqlServer.csproj b/src/TinyConfig.SqlServer/TinyConfig.SqlServer.csproj
new file mode 100644
index 0000000..c9bdd63
--- /dev/null
+++ b/src/TinyConfig.SqlServer/TinyConfig.SqlServer.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.1
+
+
+
+
+
+
+
+
+
+
+
+
+