diff --git a/Elsa.sln b/Elsa.sln
index c99bab03f..edef8869e 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -198,6 +198,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.ProtoAc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.HttpEndpoints", "src\samples\aspnet\Elsa.Samples.AspNet.HttpEndpoints\Elsa.Samples.AspNet.HttpEndpoints.csproj", "{165ACC9D-B67C-4B49-A818-ECFD247C899C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Environments", "src\modules\Elsa.Environments\Elsa.Environments.csproj", "{B5CDF747-8066-40D5-9BAE-CBE397BC9B51}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -504,6 +506,10 @@ Global
{165ACC9D-B67C-4B49-A818-ECFD247C899C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{165ACC9D-B67C-4B49-A818-ECFD247C899C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{165ACC9D-B67C-4B49-A818-ECFD247C899C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
@@ -592,5 +598,6 @@ Global
{044C3108-FE79-460A-9C31-A03C30228836} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{3212A999-4AC4-4911-9AA4-92AB906BCB5E} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
{165ACC9D-B67C-4B49-A818-ECFD247C899C} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
+ {B5CDF747-8066-40D5-9BAE-CBE397BC9B51} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
EndGlobalSection
EndGlobal
diff --git a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj
index b93d2e1d4..e9f32223e 100644
--- a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj
+++ b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs
index e92083e99..0826c5d2a 100644
--- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs
+++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs
@@ -42,7 +42,7 @@ services
runtime.UseAsyncWorkflowStateExporter();
runtime.UseMassTransitDispatcher();
})
- .UseLabels(labels => labels.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
+ .UseEnvironments(environments => environments.EnvironmentsOptions = options => configuration.GetSection("Environments").Bind(options))
.UseScheduling()
.UseWorkflowsApi(api => api.AddFastEndpointsAssembly())
.UseJavaScript()
diff --git a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json
index a9d882b0c..3356f5478 100644
--- a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json
+++ b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json
@@ -61,5 +61,21 @@
"hashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU="
}
]
+ },
+ "Environments": {
+ "Environments": [
+ {
+ "Name": "Local",
+ "ServerUrl": "https://localhost:5001/elsa/api"
+ },
+ {
+ "Name": "Staging",
+ "ServerUrl": "https://staging.acme.com/elsa/api"
+ },
+ {
+ "Name": "Production",
+ "ServerUrl": "https://production.acme.com/elsa/api"
+ }
+ ]
}
}
diff --git a/src/modules/Elsa.Environments/Contracts/IEnvironmentsManager.cs b/src/modules/Elsa.Environments/Contracts/IEnvironmentsManager.cs
new file mode 100644
index 000000000..e29aeeb2a
--- /dev/null
+++ b/src/modules/Elsa.Environments/Contracts/IEnvironmentsManager.cs
@@ -0,0 +1,16 @@
+using Elsa.Environments.Models;
+
+namespace Elsa.Environments.Contracts;
+
+///
+/// Manages environments.
+///
+public interface IEnvironmentsManager
+{
+ ///
+ /// Returns all environments.
+ ///
+ /// The cancellation token.
+ /// All environments.
+ Task> ListEnvironmentsAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Contracts/IEnvironmentsProvider.cs b/src/modules/Elsa.Environments/Contracts/IEnvironmentsProvider.cs
new file mode 100644
index 000000000..e1b672de9
--- /dev/null
+++ b/src/modules/Elsa.Environments/Contracts/IEnvironmentsProvider.cs
@@ -0,0 +1,16 @@
+using Elsa.Environments.Models;
+
+namespace Elsa.Environments.Contracts;
+
+///
+/// Provides environments to the system.
+///
+public interface IEnvironmentsProvider
+{
+ ///
+ /// Returns a list of workflow environments.
+ ///
+ /// The cancellation token.
+ /// A list of workflow environments.
+ ValueTask> GetEnvironmentsAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Elsa.Environments.csproj b/src/modules/Elsa.Environments/Elsa.Environments.csproj
new file mode 100644
index 000000000..555c9558f
--- /dev/null
+++ b/src/modules/Elsa.Environments/Elsa.Environments.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ net6.0;net7.0
+ 3.0.0
+
+ Provides API endpoints for client applications to enumerate available environments.
+
+ elsa workflows environments api
+
+
+
+
+
+
+
diff --git a/src/modules/Elsa.Environments/Endpoints/Environments/List/Endpoint.cs b/src/modules/Elsa.Environments/Endpoints/Environments/List/Endpoint.cs
new file mode 100644
index 000000000..58c6d42ce
--- /dev/null
+++ b/src/modules/Elsa.Environments/Endpoints/Environments/List/Endpoint.cs
@@ -0,0 +1,32 @@
+using Elsa.Abstractions;
+using Elsa.Environments.Contracts;
+using Elsa.Environments.Models;
+using JetBrains.Annotations;
+
+namespace Elsa.Environments.Endpoints.Environments.List;
+
+[PublicAPI]
+internal class Endpoint : ElsaEndpointWithoutRequest
+{
+ private readonly IEnvironmentsManager _environmentsManager;
+
+ public Endpoint(IEnvironmentsManager environmentsManager)
+ {
+ _environmentsManager = environmentsManager;
+ }
+
+ public override void Configure()
+ {
+ Get("/environments");
+ ConfigurePermissions("read:environments");
+ }
+
+ public override async Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var environments = await _environmentsManager.ListEnvironmentsAsync(cancellationToken);
+ return new(environments.ToList());
+ }
+}
+
+[PublicAPI]
+internal record Response(ICollection Environments);
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Environments/Extensions/ModuleExtensions.cs
new file mode 100644
index 000000000..28a6f16ec
--- /dev/null
+++ b/src/modules/Elsa.Environments/Extensions/ModuleExtensions.cs
@@ -0,0 +1,25 @@
+using Elsa.Environments.Features;
+using Elsa.Features.Services;
+using JetBrains.Annotations;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Extension methods for to add environment services.
+///
+[PublicAPI]
+public static class ModuleExtensions
+{
+ ///
+ /// Adds support for environments.
+ ///
+ /// The module.
+ /// The configuration delegate.
+ /// The module.
+ public static IModule UseEnvironments(this IModule module, Action? configure = default)
+ {
+ module.Use(configure);
+ return module;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Features/EnvironmentsFeature.cs b/src/modules/Elsa.Environments/Features/EnvironmentsFeature.cs
new file mode 100644
index 000000000..91897c818
--- /dev/null
+++ b/src/modules/Elsa.Environments/Features/EnvironmentsFeature.cs
@@ -0,0 +1,43 @@
+using Elsa.Environments.Contracts;
+using Elsa.Environments.Options;
+using Elsa.Environments.Providers;
+using Elsa.Environments.Services;
+using Elsa.Extensions;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Services;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.Environments.Features;
+
+///
+/// Installs the feature.
+///
+[PublicAPI]
+public class EnvironmentsFeature : FeatureBase
+{
+ ///
+ public EnvironmentsFeature(IModule module) : base(module)
+ {
+ }
+
+ ///
+ /// A delegate to configure the .
+ ///
+ public Action EnvironmentsOptions { get; set; } = _ => { };
+
+ ///
+ public override void Configure()
+ {
+ Module.AddFastEndpointsAssembly(GetType());
+ }
+
+ ///
+ public override void Apply()
+ {
+ Services.Configure(EnvironmentsOptions);
+
+ Services.AddSingleton();
+ Services.AddSingleton();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/FodyWeavers.xml b/src/modules/Elsa.Environments/FodyWeavers.xml
new file mode 100644
index 000000000..00e1d9a1c
--- /dev/null
+++ b/src/modules/Elsa.Environments/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Models/WorkflowsEnvironment.cs b/src/modules/Elsa.Environments/Models/WorkflowsEnvironment.cs
new file mode 100644
index 000000000..607be96bc
--- /dev/null
+++ b/src/modules/Elsa.Environments/Models/WorkflowsEnvironment.cs
@@ -0,0 +1,22 @@
+namespace Elsa.Environments.Models;
+
+///
+/// Represents the environment in which the workflow engine is running.
+///
+public class WorkflowsEnvironment
+{
+ ///
+ /// The name of the environment.
+ ///
+ public string Name { get; set; } = default!;
+
+ ///
+ /// The URL of the server hosting the workflow engine.
+ ///
+ public Uri ServerUrl { get; set; } = default!;
+
+ ///
+ /// A dictionary of custom properties.
+ ///
+ public IDictionary CustomProperties { get; set; } = new Dictionary();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Options/EnvironmentsOptions.cs b/src/modules/Elsa.Environments/Options/EnvironmentsOptions.cs
new file mode 100644
index 000000000..a0a970375
--- /dev/null
+++ b/src/modules/Elsa.Environments/Options/EnvironmentsOptions.cs
@@ -0,0 +1,14 @@
+using Elsa.Environments.Models;
+
+namespace Elsa.Environments.Options;
+
+///
+/// EnvironmentsOptions for providing environments.
+///
+public class EnvironmentsOptions
+{
+ ///
+ /// Gets or sets a list of environments.
+ ///
+ public ICollection Environments { get; set; } = new List();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Providers/ConfigurationEnvironmentsProvider.cs b/src/modules/Elsa.Environments/Providers/ConfigurationEnvironmentsProvider.cs
new file mode 100644
index 000000000..bef5fa3bd
--- /dev/null
+++ b/src/modules/Elsa.Environments/Providers/ConfigurationEnvironmentsProvider.cs
@@ -0,0 +1,28 @@
+using Elsa.Environments.Contracts;
+using Elsa.Environments.Models;
+using Elsa.Environments.Options;
+using Microsoft.Extensions.Options;
+
+namespace Elsa.Environments.Providers;
+
+///
+/// An implementation of that reads environment definitions from configuration.
+///
+public class ConfigurationEnvironmentsProvider : IEnvironmentsProvider
+{
+ private readonly IOptions _options;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public ConfigurationEnvironmentsProvider(IOptions options)
+ {
+ _options = options;
+ }
+
+ ///
+ public ValueTask> GetEnvironmentsAsync(CancellationToken cancellationToken = default)
+ {
+ return new (_options.Value.Environments);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Environments/Services/DefaultEnvironmentsManager.cs b/src/modules/Elsa.Environments/Services/DefaultEnvironmentsManager.cs
new file mode 100644
index 000000000..c94170933
--- /dev/null
+++ b/src/modules/Elsa.Environments/Services/DefaultEnvironmentsManager.cs
@@ -0,0 +1,32 @@
+using Elsa.Environments.Contracts;
+using Elsa.Environments.Models;
+
+namespace Elsa.Environments.Services;
+
+///
+public class DefaultEnvironmentsManager : IEnvironmentsManager
+{
+ private readonly IEnumerable _providers;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public DefaultEnvironmentsManager(IEnumerable providers)
+ {
+ _providers = providers;
+ }
+
+ ///
+ public async Task> ListEnvironmentsAsync(CancellationToken cancellationToken = default)
+ {
+ var allEnvironments = new List();
+
+ foreach (var provider in _providers)
+ {
+ var environments = await provider.GetEnvironmentsAsync(cancellationToken);
+ allEnvironments.AddRange(environments);
+ }
+
+ return allEnvironments;
+ }
+}
\ No newline at end of file