diff --git a/Directory.Packages.props b/Directory.Packages.props
index ca4477da27..8709d39471 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,6 +5,9 @@
+
+
+
@@ -31,18 +34,21 @@
-
-
-
+
+
+
+
+
+
@@ -94,14 +100,17 @@
+
+
+
+
+
+
-
-
-
diff --git a/Elsa.sln b/Elsa.sln
index 6602771a47..c043709eed 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -327,6 +327,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issue_templates", "issue_te
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dsl", "dsl", "{477C2416-312D-46AE-BCD6-8FA1FAB43624}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.ModularServer.Web", "src\apps\Elsa.ModularServer.Web\Elsa.ModularServer.Web.csproj", "{E9CA9A0B-6180-4CA7-814C-FA60D1F1B6EC}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Resilience.Core.UnitTests", "test\unit\Elsa.Resilience.Core.UnitTests\Elsa.Resilience.Core.UnitTests.csproj", "{B8006D70-1630-43DB-A043-FA89FAC70F37}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Common.UnitTests", "test\unit\Elsa.Common.UnitTests\Elsa.Common.UnitTests.csproj", "{A3C07D5B-2A30-494E-B9BC-4B1594B31ABC}"
@@ -591,6 +593,10 @@ Global
{2B7FB49D-E4B6-4AD5-981B-3D85B94F6F48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B7FB49D-E4B6-4AD5-981B-3D85B94F6F48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B7FB49D-E4B6-4AD5-981B-3D85B94F6F48}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E9CA9A0B-6180-4CA7-814C-FA60D1F1B6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E9CA9A0B-6180-4CA7-814C-FA60D1F1B6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E9CA9A0B-6180-4CA7-814C-FA60D1F1B6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E9CA9A0B-6180-4CA7-814C-FA60D1F1B6EC}.Release|Any CPU.Build.0 = Release|Any CPU
{B8006D70-1630-43DB-A043-FA89FAC70F37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8006D70-1630-43DB-A043-FA89FAC70F37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8006D70-1630-43DB-A043-FA89FAC70F37}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -708,6 +714,7 @@ Global
{2B7FB49D-E4B6-4AD5-981B-3D85B94F6F48} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
{477C2416-312D-46AE-BCD6-8FA1FAB43624} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{874F5A44-DB06-47AB-A18C-2D13942E0147} = {477C2416-312D-46AE-BCD6-8FA1FAB43624}
+ {E9CA9A0B-6180-4CA7-814C-FA60D1F1B6EC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
{B8006D70-1630-43DB-A043-FA89FAC70F37} = {18453B51-25EB-4317-A4B3-B10518252E92}
{A3C07D5B-2A30-494E-B9BC-4B1594B31ABC} = {18453B51-25EB-4317-A4B3-B10518252E92}
{8C4F6A2D-1E9F-4B3C-9D8E-7F5A6B4C3D2E} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
diff --git a/NuGet.Config b/NuGet.Config
index 54f245bd31..d188f6e5a0 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -4,10 +4,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/Elsa.ModularServer.Web/Elsa.ModularServer.Web.csproj b/src/apps/Elsa.ModularServer.Web/Elsa.ModularServer.Web.csproj
new file mode 100644
index 0000000000..d2f791b735
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/Elsa.ModularServer.Web.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/Elsa.ModularServer.Web/FodyWeavers.xml b/src/apps/Elsa.ModularServer.Web/FodyWeavers.xml
new file mode 100644
index 0000000000..00e1d9a1c1
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/apps/Elsa.ModularServer.Web/Program.cs b/src/apps/Elsa.ModularServer.Web/Program.cs
new file mode 100644
index 0000000000..a5251a00ff
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/Program.cs
@@ -0,0 +1,25 @@
+using CShells.AspNetCore.Configuration;
+using CShells.AspNetCore.Extensions;
+
+var builder = WebApplication.CreateBuilder(args);
+var services = builder.Services;
+
+// Configure CShells for multi-tenancy with ASP.NET Core integration
+// This automatically registers shell-aware authentication and authorization providers
+builder.AddShells(shells => shells
+ .WithWebRouting(options => options.EnablePathRouting = true)
+ .WithAuthenticationAndAuthorization());
+services.AddHealthChecks();
+
+// Add minimal authentication and authorization services in root
+// These are required for middleware validation - shells provide the actual configurations
+services.AddAuthentication();
+services.AddAuthorization();
+
+var app = builder.Build();
+
+app.MapHealthChecks("/");
+app.MapShells(); // Sets HttpContext.RequestServices to shell's scoped provider
+app.UseAuthentication(); // Runs after MapShells to access shell-specific auth schemes
+app.UseAuthorization(); // Runs after MapShells to access shell-specific policies
+app.Run();
\ No newline at end of file
diff --git a/src/apps/Elsa.ModularServer.Web/Properties/launchSettings.json b/src/apps/Elsa.ModularServer.Web/Properties/launchSettings.json
new file mode 100644
index 0000000000..49af15d915
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5002",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7293;http://localhost:5002",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/apps/Elsa.ModularServer.Web/appsettings.Development.json b/src/apps/Elsa.ModularServer.Web/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/src/apps/Elsa.ModularServer.Web/appsettings.Example.json b/src/apps/Elsa.ModularServer.Web/appsettings.Example.json
new file mode 100644
index 0000000000..86ca822c4c
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/appsettings.Example.json
@@ -0,0 +1,76 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "CShells": {
+ "Shells": [
+ {
+ "Name": "Default",
+ "Properties": {
+ "WebRouting": {
+ "Path": ""
+ }
+ },
+ "Settings": {
+ "FastEndpoints": {
+ "GlobalRoutePrefix": "elsa/api"
+ },
+ "SqliteWorkflowDefinitionPersistence": {
+ "ConnectionString": "Data Source=elsa_workflows.db;Cache=Shared",
+ "DbContextOptions": {
+ "EnableSensitiveDataLogging": false,
+ "EnableDetailedErrors": false
+ }
+ },
+ "SqliteWorkflowInstancePersistence": {
+ "ConnectionString": "Data Source=elsa_workflows.db;Cache=Shared"
+ },
+ "SqliteWorkflowRuntimePersistence": {
+ "ConnectionString": "Data Source=elsa_workflows.db;Cache=Shared"
+ }
+ },
+ "Features": [
+ "Elsa",
+ "WorkflowsApi",
+ "Identity",
+ "DefaultAuthentication",
+ "SqliteWorkflowDefinitionPersistence",
+ "SqliteWorkflowInstancePersistence",
+ "SqliteWorkflowRuntimePersistence"
+ ]
+ },
+ {
+ "Name": "Tenant1",
+ "Properties": {
+ "WebRouting": {
+ "Path": "tenant1"
+ }
+ },
+ "Settings": {
+ "FastEndpoints": {
+ "GlobalRoutePrefix": "api"
+ },
+ "SqlServerWorkflowDefinitionPersistence": {
+ "ConnectionString": "${ConnectionStrings:Tenant1}"
+ },
+ "SqlServerWorkflowInstancePersistence": {
+ "ConnectionString": "${ConnectionStrings:Tenant1}"
+ }
+ },
+ "Features": [
+ "Elsa",
+ "WorkflowsApi",
+ "SqlServerWorkflowDefinitionPersistence",
+ "SqlServerWorkflowInstancePersistence"
+ ]
+ }
+ ]
+ },
+ "ConnectionStrings": {
+ "Tenant1": "Server=localhost;Database=ElsaTenant1;Integrated Security=true;TrustServerCertificate=true"
+ }
+}
diff --git a/src/apps/Elsa.ModularServer.Web/appsettings.json b/src/apps/Elsa.ModularServer.Web/appsettings.json
new file mode 100644
index 0000000000..e3ca4a7be2
--- /dev/null
+++ b/src/apps/Elsa.ModularServer.Web/appsettings.json
@@ -0,0 +1,55 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "CShells": {
+ "Shells": [
+ {
+ "Name": "Default",
+ "Properties": {
+ "WebRouting": {
+ "Path": "foo"
+ }
+ },
+ "Settings": {
+ "FastEndpoints": {
+ "GlobalRoutePrefix": "elsa/api"
+ },
+ "SqliteWorkflowDefinitionPersistence": {
+ "ConnectionString": "Data Source=elsa_workflows.db;Cache=Shared",
+ "DbContextOptions": {
+ "EnableSensitiveDataLogging": false,
+ "EnableDetailedErrors": false
+ }
+ },
+ "SqliteWorkflowInstancePersistence": {
+ "ConnectionString": "Data Source=elsa_workflows.db;Cache=Shared",
+ "DbContextOptions": {
+ "EnableSensitiveDataLogging": false,
+ "EnableDetailedErrors": false
+ }
+ },
+ "SqliteWorkflowRuntimePersistence": {
+ "ConnectionString": "Data Source=elsa_workflows.db;Cache=Shared",
+ "DbContextOptions": {
+ "EnableSensitiveDataLogging": false,
+ "EnableDetailedErrors": false
+ }
+ }
+ },
+ "Features": [
+ "Elsa",
+ "WorkflowsApi",
+ "Identity",
+ "DefaultAuthentication",
+ "Resilience",
+ "SqliteWorkflowPersistence"
+ ]
+ }
+ ]
+ }
+}
diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs
index a1a8de9c7e..c020fcbfba 100644
--- a/src/apps/Elsa.Server.Web/Program.cs
+++ b/src/apps/Elsa.Server.Web/Program.cs
@@ -42,7 +42,6 @@
var identitySection = configuration.GetSection("Identity");
var identityTokenSection = identitySection.GetSection("Tokens");
-// Add Elsa services.
services
.AddElsa(elsa =>
{
diff --git a/src/common/Elsa.Api.Common/Elsa.Api.Common.csproj b/src/common/Elsa.Api.Common/Elsa.Api.Common.csproj
index 8288f16c23..ff63d15173 100644
--- a/src/common/Elsa.Api.Common/Elsa.Api.Common.csproj
+++ b/src/common/Elsa.Api.Common/Elsa.Api.Common.csproj
@@ -9,6 +9,8 @@
+
+
diff --git a/src/common/Elsa.Api.Common/FastEndpointConfigurators/ElsaFastEndpointsConfigurator.cs b/src/common/Elsa.Api.Common/FastEndpointConfigurators/ElsaFastEndpointsConfigurator.cs
new file mode 100644
index 0000000000..947399a206
--- /dev/null
+++ b/src/common/Elsa.Api.Common/FastEndpointConfigurators/ElsaFastEndpointsConfigurator.cs
@@ -0,0 +1,50 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using CShells.FastEndpoints.Contracts;
+using Elsa.Workflows;
+using FastEndpoints;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.FastEndpointConfigurators;
+
+///
+/// Configures FastEndpoints with Elsa-specific serialization options.
+/// Uses the same serialization settings as .
+///
+[UsedImplicitly]
+public class ElsaFastEndpointsConfigurator : IFastEndpointsConfigurator
+{
+ ///
+ public void Configure(Config config)
+ {
+ config.Serializer.RequestDeserializer = DeserializeRequestAsync;
+ config.Serializer.ResponseSerializer = SerializeResponseAsync;
+
+ config.Binding.ValueParserFor(s =>
+ new(DateTimeOffset.TryParse(s.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result), result));
+ }
+
+ private static ValueTask