Merge pull request #1228 from elsa-workflows/final_appsettings_design

Refactor appsettings.json #1226
This commit is contained in:
Sipke Schoorstra 2021-07-09 12:47:18 +02:00 committed by GitHub
commit 2592885be1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 223 additions and 112 deletions

View file

@ -16,8 +16,8 @@ namespace Elsa.Webhooks.Persistence.EntityFramework.Core
public override void ConfigureElsa(ElsaOptionsBuilder elsa, IConfiguration configuration)
{
var services = elsa.Services;
var section = configuration.GetSection($"Elsa:Features:WebhooksPersistenceEntityFrameworkCore{ProviderName}");
var connectionStringName = section.GetValue<string>("ConnectionStringName");
var section = configuration.GetSection($"Elsa:Features:Webhooks");
var connectionStringName = section.GetValue<string>("ConnectionStringIdentifier");
var connectionString = section.GetValue<string>("ConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Webhooks.Persistence.EntityFramework.MySql
{
[Feature("WebhooksPersistenceEntityFrameworkCoreMySql")]
[Feature("Webhooks:EntityFrameworkCore:MySql")]
public class Startup : EntityFrameworkWebhookStartupBase
{
protected override string ProviderName => "MySql";

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Webhooks.Persistence.EntityFramework.PostgreSql
{
[Feature("WebhooksPersistenceEntityFrameworkCorePostgreSql")]
[Feature("Webhooks:EntityFrameworkCore:PostgreSql")]
public class Startup : EntityFrameworkWebhookStartupBase
{
protected override string ProviderName => "PostgreSql";

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Webhooks.Persistence.EntityFramework.SqlServer
{
[Feature("WebhooksPersistenceEntityFrameworkCoreSqlServer")]
[Feature("Webhooks:EntityFrameworkCore:SqlServer")]
public class Startup : EntityFrameworkWebhookStartupBase
{
protected override string ProviderName => "SqlServer";

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Webhooks.Persistence.EntityFramework.Sqlite
{
[Feature("WebhooksPersistenceEntityFrameworkCoreSqlite")]
[Feature("Webhooks:EntityFrameworkCore:Sqlite")]
public class Startup : EntityFrameworkWebhookStartupBase
{
protected override string ProviderName => "Sqlite";

View file

@ -8,14 +8,14 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Webhooks.Persistence.MongoDb
{
[Feature("WebhooksPersistenceMongoDb")]
[Feature("Webhooks:MongoDb")]
public class Startup : StartupBase
{
public override void ConfigureElsa(ElsaOptionsBuilder elsa, IConfiguration configuration)
{
var services = elsa.Services;
var section = configuration.GetSection($"Elsa:Features:WebhooksPersistenceMongoDb");
var connectionStringName = section.GetValue<string>("ConnectionStringName");
var section = configuration.GetSection($"Elsa:Features:Webhooks");
var connectionStringName = section.GetValue<string>("ConnectionStringIdentifier");
var connectionString = section.GetValue<string>("ConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))

View file

@ -13,7 +13,7 @@ using YesSql.Provider.SqlServer;
namespace Elsa.Webhooks.Persistence.YesSql
{
[Feature("WebhooksPersistenceYesSqlSqlite")]
[Feature("Webhooks:YesSql:Sqlite")]
public class SqliteStartup : YesSqlStartupBase
{
protected override string ProviderName => "Sqlite";
@ -21,21 +21,21 @@ namespace Elsa.Webhooks.Persistence.YesSql
protected override void Configure(global::YesSql.IConfiguration options, string connectionString) => options.UseSqLite(connectionString);
}
[Feature("WebhooksPersistenceYesSqlSqlServer")]
[Feature("Webhooks:YesSql:SqlServer")]
public class SqlServerStartup : YesSqlStartupBase
{
protected override string ProviderName => "SqlServer";
protected override void Configure(global::YesSql.IConfiguration options, string connectionString) => options.UseSqlServer(connectionString);
}
[Feature("WebhooksPersistenceYesSqlMySql")]
[Feature("Webhooks:YesSql:MySql")]
public class MySqlStartup : YesSqlStartupBase
{
protected override string ProviderName => "MySql";
protected override void Configure(global::YesSql.IConfiguration options, string connectionString) => options.UseMySql(connectionString);
}
[Feature("WebhooksPersistenceYesSqlPostgreSql")]
[Feature("Webhooks:YesSql:PostgreSql")]
public class PostgreSqlStartup : YesSqlStartupBase
{
protected override string ProviderName => "PostgreSql";
@ -49,8 +49,8 @@ namespace Elsa.Webhooks.Persistence.YesSql
public override void ConfigureElsa(ElsaOptionsBuilder elsa, IConfiguration configuration)
{
var services = elsa.Services;
var section = configuration.GetSection($"Elsa:Features:WebhooksPersistenceYesSql{ProviderName}");
var connectionStringName = section.GetValue<string>("ConnectionStringName");
var section = configuration.GetSection($"Elsa:Features:Webhooks");
var connectionStringName = section.GetValue<string>("ConnectionStringIdentifier");
var connectionString = section.GetValue<string>("ConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))

View file

@ -12,11 +12,19 @@ namespace Elsa
{
public static ElsaOptionsBuilder AddFeatures(this ElsaOptionsBuilder builder, IEnumerable<Type> assemblyMarkerTypes, IConfiguration configuration) => AddFeatures(builder, GetAssemblies(assemblyMarkerTypes), configuration);
/// <summary>
/// Parse all features from the appsettings.json, filter only enabled features,
/// find all start up classes with matching attribute and create their instances.
/// </summary>
/// <param name="builder">ElsaOptionsBuilder</param>
/// <param name="assemblies">Available assembly collection</param>
/// <param name="configuration">IConfiguration</param>
/// <returns>ElsaOptionsBuilder</returns>
public static ElsaOptionsBuilder AddFeatures(this ElsaOptionsBuilder builder, IEnumerable<Assembly> assemblies, IConfiguration configuration)
{
var enabledFeatures = ParseFeatures(configuration);
if (enabledFeatures == null!) // Null when configuration binding finds an empty array.
if (enabledFeatures == null!)
return builder;
enabledFeatures = enabledFeatures.ToHashSet();
@ -40,35 +48,157 @@ namespace Elsa
return builder;
}
/// <summary>
/// Parse all features from the appsettings.json and popualte enabled feature collection
/// </summary>
/// <param name="configuration">IConfiguration</param>
/// <returns>Enabled feature collection</returns>
private static IEnumerable<string> ParseFeatures(IConfiguration configuration)
{
var elsaFeaturesSection = "Elsa:Features";
var features = configuration.GetSection(elsaFeaturesSection).AsEnumerable();
var enabledFeatures = new List<string>();
return
from feature in features
let isEnabled = ParseFeatureFlag(configuration, feature.Key)
where isEnabled
select feature.Key.Replace($"{elsaFeaturesSection}:", string.Empty);
foreach (var feature in features)
{
var featureOptions = ParseFeatureSection(configuration, feature.Key);
if (!featureOptions.Enabled) continue;
var key = feature.Key.Replace($"{elsaFeaturesSection}:", string.Empty);
enabledFeatures.Add(key);
if (featureOptions.Items == null) continue;
var values = featureOptions.Items.Values.ToArray();
GetPermutations(key, values, enabledFeatures, 0, values.Length - 1);
}
return enabledFeatures;
}
private static bool ParseFeatureFlag(IConfiguration configuration, string feature)
/// <summary>
/// Parse single feature section from the appsettings.json and popualte feature model
/// </summary>
/// <param name="configuration">IConfiguration</param>
/// <param name="feature">Feature name</param>
/// <returns>Populated feature model</returns>
private static FeatureModel ParseFeatureSection(IConfiguration configuration, string feature)
{
var featureModel = new FeatureModel();
if (configuration.GetSection($"{feature}:Enabled").Exists())
{
bool.TryParse(configuration.GetValue<string>($"{feature}:Enabled"), out var enabled);
return enabled;
var featureItems = configuration.GetSection($"{feature}").AsEnumerable();
ParseFeatureItems(feature, featureModel, featureItems);
ParseFeatureOptions(configuration, feature, featureModel);
return featureModel;
}
if (!feature.EndsWith(":Enabled"))
{
bool.TryParse(configuration.GetValue<string>($"{feature}"), out var enabled);
return enabled;
featureModel.Enabled = enabled;
}
return featureModel;
}
/// <summary>
/// Parse feature section key/value collection except Enabled and Options keys from the appsetting.json and popualte feature model
/// </summary>
/// <param name="feature">Feature name</param>
/// <param name="featureModel">Feature model</param>
/// <param name="featureItems">Feature section kay/value collection</param>
private static void ParseFeatureItems(string feature, FeatureModel featureModel, IEnumerable<KeyValuePair<string, string>> featureItems)
{
featureModel.Items = new Dictionary<string, string>();
foreach (var featureItem in featureItems)
{
if (featureItem.Value == null) continue;
var itemKey = featureItem.Key.Replace($"{feature}:", string.Empty);
if (itemKey.Contains(":")) continue;
if (itemKey == "Enabled")
{
bool.TryParse(featureItem.Value, out var enabled);
featureModel.Enabled = enabled;
}
else
{
featureModel.Items.Add(itemKey, featureItem.Value);
}
}
}
/// <summary>
/// Parse feature Options section from the appsetting.json and populate feature model
/// </summary>
/// <param name="configuration">IConfiguration</param>
/// <param name="feature">Feature name</param>
/// <param name="featureModel">Feature model</param>
private static void ParseFeatureOptions(IConfiguration configuration, string feature, FeatureModel featureModel)
{
if (configuration.GetSection($"{feature}:Options").Exists())
{
var options = new Dictionary<string, string>();
configuration.GetSection($"{feature}:Options").Bind(options);
featureModel.Options = options;
}
}
/// <summary>
/// Permutate all possible order combinations in Feature section key/value collection
/// </summary>
/// <param name="feature">Feature name</param>
/// <param name="values">Feature values array to permutate</param>
/// <param name="enabledFeatures">Enabled feature collection</param>
/// <param name="start">Start index</param>
/// <param name="end">End index</param>
private static void GetPermutations(string feature, string[] values, ICollection<string> enabledFeatures, int start, int end)
{
if (start == end)
{
foreach (var item in values)
{
feature += $":{item}";
if (enabledFeatures.Contains(feature)) continue;
enabledFeatures.Add(feature);
}
}
return false;
for (int i = start; i <= end; i++)
{
Swap(ref values[start], ref values[i]);
GetPermutations(feature, values, enabledFeatures, start + 1, end);
Swap(ref values[start], ref values[i]);
}
}
/// <summary>
/// Swap two string values
/// </summary>
/// <param name="item1"></param>
/// <param name="item2"></param>
private static void Swap(ref string item1, ref string item2)
{
if (item1 == item2) return;
var temp = item1;
item1 = item2;
item2 = temp;
}
private static IEnumerable<Assembly> GetAssemblies(IEnumerable<Type> assemblyMarkerTypes) => assemblyMarkerTypes.Select(x => x.Assembly).Distinct();
private class FeatureModel
{
public bool Enabled { get; set; }
public Dictionary<string, string>? Items { get; set; }
public Dictionary<string, string>? Options { get; set; }
}
}
}

View file

@ -12,8 +12,8 @@ namespace Elsa.Persistence.EntityFramework.Core
public override void ConfigureElsa(ElsaOptionsBuilder elsa, IConfiguration configuration)
{
var section = configuration.GetSection($"Elsa:Features:PersistenceEntityFrameworkCore{ProviderName}");
var connectionStringName = section.GetValue<string>("ConnectionStringName");
var section = configuration.GetSection($"Elsa:Features:DefaultPersistence");
var connectionStringName = section.GetValue<string>("ConnectionStringIdentifier");
var connectionString = section.GetValue<string>("ConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Persistence.EntityFramework.MySql
{
[Feature("PersistenceEntityFrameworkCoreMySql")]
[Feature("DefaultPersistence:EntityFrameworkCore:MySql")]
public class Startup : EntityFrameworkCoreStartupBase
{
protected override string ProviderName => "MySql";

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Persistence.EntityFramework.PostgreSql
{
[Feature("PersistenceEntityFrameworkCorePostgreSql")]
[Feature("DefaultPersistence:EntityFrameworkCore:PostgreSql")]
public class Startup : EntityFrameworkCoreStartupBase
{
protected override string ProviderName => "PostgreSql";

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Persistence.EntityFramework.SqlServer
{
[Feature("PersistenceEntityFrameworkCoreSqlServer")]
[Feature("DefaultPersistence:EntityFrameworkCore:SqlServer")]
public class Startup : EntityFrameworkCoreStartupBase
{
protected override string ProviderName => "SqlServer";

View file

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Elsa.Persistence.EntityFramework.Sqlite
{
[Feature("PersistenceEntityFrameworkCoreSqlite")]
[Feature("DefaultPersistence:EntityFrameworkCore:Sqlite")]
public class Startup : EntityFrameworkCoreStartupBase
{
protected override string ProviderName => "Sqlite";

View file

@ -4,13 +4,13 @@ using Microsoft.Extensions.Configuration;
namespace Elsa.Persistence.MongoDb
{
[Feature("PersistenceMongoDb")]
[Feature("DefaultPersistence:MongoDb")]
public class Startup : StartupBase
{
public override void ConfigureElsa(ElsaOptionsBuilder elsa, IConfiguration configuration)
{
var section = configuration.GetSection($"Elsa:Features:PersistenceMongoDb");
var connectionStringName = section.GetValue<string>("ConnectionStringName");
var section = configuration.GetSection($"Elsa:Features:DefaultPersistence");
var connectionStringName = section.GetValue<string>("ConnectionStringIdentifier");
var connectionString = section.GetValue<string>("ConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))

View file

@ -9,7 +9,7 @@ using YesSql.Provider.SqlServer;
namespace Elsa.Persistence.YesSql
{
[Feature("PersistenceYesSqlSqlite")]
[Feature("DefaultPersistence:YesSql:Sqlite")]
public class SqliteStartup : YesSqlStartupBase
{
protected override string ProviderName => "Sqlite";
@ -17,21 +17,21 @@ namespace Elsa.Persistence.YesSql
protected override void Configure(global::YesSql.IConfiguration options, string connectionString) => options.UseSqLite(connectionString);
}
[Feature("PersistenceYesSqlSqlServer")]
[Feature("DefaultPersistence:YesSql:SqlServer")]
public class SqlServerStartup : YesSqlStartupBase
{
protected override string ProviderName => "SqlServer";
protected override void Configure(global::YesSql.IConfiguration options, string connectionString) => options.UseSqlServer(connectionString);
}
[Feature("PersistenceYesSqlMySql")]
[Feature("DefaultPersistence:YesSql:MySql")]
public class MySqlStartup : YesSqlStartupBase
{
protected override string ProviderName => "MySql";
protected override void Configure(global::YesSql.IConfiguration options, string connectionString) => options.UseMySql(connectionString);
}
[Feature("PersistenceYesSqlPostgreSql")]
[Feature("DefaultPersistence:YesSql:PostgreSql")]
public class PostgreSqlStartup : YesSqlStartupBase
{
protected override string ProviderName => "PostgreSql";
@ -44,8 +44,8 @@ namespace Elsa.Persistence.YesSql
public override void ConfigureElsa(ElsaOptionsBuilder elsa, IConfiguration configuration)
{
var section = configuration.GetSection($"Elsa:Features:PersistenceYesSql{ProviderName}");
var connectionStringName = section.GetValue<string>("ConnectionStringName");
var section = configuration.GetSection($"Elsa:Features:DefaultPersistence");
var connectionStringName = section.GetValue<string>("ConnectionStringIdentifier");
var connectionString = section.GetValue<string>("ConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))

View file

@ -1,14 +1,24 @@
### Configure persistence for Elsa Core in the appsettings.json
In order to specify the persistence it's required to set enabled status and connection string name in Features node
A simple way to enable a feature without persistence:
```
"Elsa": {
"Features": {
"Console": true
...
```
In order to specify persistence for a feature it's required to set enabled status, framework (has to be an empty space for MongoDb) and connection string identifier:
EF Sqlite
```
"Elsa": {
"Features": {
"PersistenceEntityFrameworkCoreSqlite": {
"Enabled": true,
"ConnectionStringName": "Sqlite"
"DefaultPersistence": {
"Enabled": true,
"Framework": "EntityFrameworkCore",
"ConnectionStringIdentifier": "Sqlite"
},
...
```
@ -17,9 +27,10 @@ EF MySQL
```
"Elsa": {
"Features": {
"PersistenceEntityFrameworkCoreMySql": {
"Enabled": true,
"ConnectionStringName": "MySql"
"DefaultPersistence": {
"Enabled": true,
"Framework": "EntityFrameworkCore",
"ConnectionStringIdentifier": "MySql"
},
...
```
@ -28,9 +39,10 @@ EF SQL Server
```
"Elsa": {
"Features": {
"PersistenceEntityFrameworkCoreSqlServer": {
"Enabled": true,
"ConnectionStringName": "SqlServer"
"DefaultPersistence": {
"Enabled": true,
"Framework": "EntityFrameworkCore",
"ConnectionStringIdentifier": "SqlServer"
},
...
```
@ -39,20 +51,22 @@ EF Postgre
```
"Elsa": {
"Features": {
"PersistenceEntityFrameworkCorePostgreSql": {
"Enabled": "true",
"ConnectionStringName": "PostgreSql"
"DefaultPersistence": {
"Enabled": "true",
"Framework": "EntityFrameworkCore",
"ConnectionStringIdentifier": "PostgreSql"
},
...
```
EF MongoDB
MongoDB
```
"Elsa": {
"Features": {
"PersistenceMongoDb": {
"Enabled": true,
"ConnectionStringName": "MongoDb"
"DefaultPersistence": {
"Enabled": true,
"Framework": "",
"ConnectionStringIdentifier": "MongoDb"
},
...
```
@ -61,9 +75,10 @@ YesSql Sqlite
```
"Elsa": {
"Features": {
"PersistenceYesSqlSqlite": {
"Enabled": true,
"ConnectionStringName": "Sqlite"
"DefaultPersistence": {
"Enabled": true,
"Framework": "YesSql",
"ConnectionStringIdentifier": "Sqlite"
},
...
```
@ -72,9 +87,10 @@ YesSql MySQL
```
"Elsa": {
"Features": {
"PersistenceYesSqlMySql": {
"Enabled": true,
"ConnectionStringName": "MySql"
"DefaultPersistence": {
"Enabled": true,
"Framework": "YesSql",
"ConnectionStringIdentifier": "MySql"
},
...
```
@ -83,9 +99,10 @@ YesSql SQL Server
```
"Elsa": {
"Features": {
"PersistenceYesSqlSqlServer": {
"Enabled": true,
"ConnectionStringName": "SqlServer"
"DefaultPersistence": {
"Enabled": true,
"Framework": "YesSql",
"ConnectionStringIdentifier": "SqlServer"
},
...
```
@ -94,48 +111,11 @@ YesSql Postgre
```
"Elsa": {
"Features": {
"PersistenceYesSqlPostgreSql": {
"Enabled": true,
"ConnectionStringName": "PostgreSql"
},
...
```
### Configure modular activity providers such as Webhooks
It is required to add modular activity provider and additional persistence feature for your modular activity provider.
Two examples below are given to set up Elsa Core and Webhooks persistence as follow.
EF Sqlite
```
"Elsa": {
"Features": {
"PersistenceEntityFrameworkCoreSqlite": {
"DefaultPersistence": {
"Enabled": true,
"ConnectionStringName": "Sqlite"
"Framework": "YesSql",
"ConnectionStringIdentifier": "PostgreSql"
},
"Webhooks": true
},
"WebhooksPersistenceEntityFrameworkCoreSqlite": {
"Enabled": true,
"ConnectionStringName": "Sqlite"
}
...
```
EF MongoDB
```
"Elsa": {
"Features": {
"PersistenceMongoDb": {
"Enabled": true,
"ConnectionStringName": "MongoDb"
},
"Webhooks": true
},
"WebhooksPersistenceMongoDb": {
"Enabled": true,
"ConnectionStringName": "MongoDb"
}
...
```

View file

@ -19,9 +19,10 @@
},
"Elsa": {
"Features": {
"PersistenceEntityFrameworkCoreSqlite": {
"DefaultPersistence": {
"Enabled": true,
"ConnectionStringName": "Sqlite"
"Framework": "EntityFrameworkCore",
"ConnectionStringIdentifier": "Sqlite"
},
"DispatcherHangfire": true,
"Console": true,
@ -32,10 +33,10 @@
"UserTask": true,
"Conductor": true,
"Telnyx": true,
"Webhooks": true,
"WebhooksPersistenceEntityFrameworkCoreSqlite": {
"Webhooks": {
"Enabled": true,
"ConnectionStringName": "Sqlite"
"Framework": "EntityFrameworkCore",
"ConnectionStringIdentifier": "Sqlite"
}
},
"WorkflowChannels": {