elsa-core/src/apps/Elsa.ServerAndStudio.Web/Program.cs
Sipke Schoorstra 7e7a899bbf
Implement multitenant HTTP routing (#6031)
* Add tenant awareness to bookmark handling and route resolution

Added tenant ID support across various components, including bookmark updates, route resolution, and middleware processing. This ensures that bookmark and route operations can now appropriately handle tenant-specific data, improving the system's multitenancy capabilities.

* Add Multitenant HTTP Routing feature to Tenants module

Introduced a new MultitenantHttpRoutingFeature class to the Elsa.Tenants.AspNetCore module, enhancing the tenant resolution capabilities. Moved RoutePrefixTenantResolver from Elsa.Http to Elsa.Tenants.AspNetCore and updated relevant project references and namespaces accordingly. This refactor improves modularity and separation of concerns between HTTP and tenancy features.

* Refactor route handling and tenant configuration

Removed redundant `RouteTableExtensions` and replaced with new route providers and updaters, enhancing flexibility and modularity. Introduced tenant-specific HTTP endpoint configurations for better customization and configuration management.

* Rename HttpEndpointBookmarkStimulus to HttpEndpointBookmarkPayload

Refactor various classes and methods to reflect the renaming from `HttpEndpointBookmarkStimulus` to `HttpEndpointBookmarkPayload`. Add and configure new extension methods for tenant route handling, update the route provider to support multi-tenancy, and adjust the tenants provider to bind configuration properly.

* Add HeaderTenantResolver and refactor Http namespace.

Introduce HeaderTenantResolver to resolve tenants via HTTP headers. Refactor multiple classes and interfaces to move from the Elsa.Http.Models namespace directly into Elsa.Http for clarity and consistency.

* Add Host-based tenant resolution

Implemented a HostTenantResolver to resolve tenants based on the request's host and updated tenant configurations with host information. Modified the tenant resolver pipeline and added the new host resolver to the service registrations.

* Add tenant-aware caching and accessor support

Enhanced caching by incorporating tenant identifiers into cache keys for more granular cache management. Introduced ITenantAccessor dependencies in various services to retrieve the current tenant information. This ensures that cache entries are correctly isolated per tenant.

* Reorder tenant resolvers for pipeline setup.

Reordered the tenant resolvers in the pipeline to prioritize HostTenantResolver before RoutePrefixTenantResolver. This ensures that tenant resolution is correctly aligned with host-based resolving before checking the route prefix.

* Remove unused imports

This commit eliminates redundant `using` directives across multiple files to streamline the codebase. This cleanup helps improve code readability and maintainability by removing unnecessary dependencies.
2024-10-14 21:27:11 +02:00

207 lines
8.5 KiB
C#

using Elsa.Agents;
using Elsa.EntityFrameworkCore.Extensions;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.MassTransit.Options;
using Elsa.Extensions;
using Elsa.JavaScript.Libraries.Extensions;
using Elsa.ServerAndStudio.Web.Extensions;
using Elsa.MassTransit.Extensions;
using Elsa.ServerAndStudio.Web.Enums;
using Medallion.Threading.FileSystem;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Proto.Persistence.Sqlite;
using WebhooksCore.Options;
const bool useMassTransit = true;
const bool useProtoActor = true;
const bool useCaching = true;
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.MassTransit;
const MassTransitBroker useMassTransitBroker = MassTransitBroker.Memory;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStaticWebAssets();
var services = builder.Services;
var configuration = builder.Configuration;
var featuresSection = configuration.GetSection("Features");
var databaseProvider = featuresSection.GetValue<string>("DatabaseProvider");
var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!;
var mySqlConnectionString = configuration.GetConnectionString("MySql")!;
var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!;
var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!;
var azureServiceBusConnectionString = configuration.GetConnectionString("AzureServiceBus")!;
var identitySection = configuration.GetSection("Identity");
var identityTokenSection = identitySection.GetSection("Tokens");
var massTransitSection = configuration.GetSection("MassTransit");
var massTransitDispatcherSection = configuration.GetSection("MassTransit.Dispatcher");
var heartbeatSection = configuration.GetSection("Heartbeat");
services.Configure<MassTransitOptions>(massTransitSection);
services.Configure<MassTransitWorkflowDispatcherOptions>(massTransitDispatcherSection);
// Add Elsa services.
services
.AddElsa(elsa =>
{
elsa
.UseSasTokens()
.UseIdentity(identity =>
{
identity.TokenOptions = options => identityTokenSection.Bind(options);
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
})
.UseDefaultAuthentication()
.UseApplicationCluster(x => x.HeartbeatOptions = settings => heartbeatSection.Bind(settings))
.UseWorkflowManagement(management =>
{
if (useMassTransit)
{
management.UseMassTransitDispatcher();
}
if (useCaching)
management.UseCache();
if (databaseProvider == "MySql")
management.UseEntityFrameworkCore(ef => ef.UseMySql(mySqlConnectionString));
else if (databaseProvider == "SqlServer")
management.UseEntityFrameworkCore(ef => ef.UseSqlServer(sqlServerConnectionString));
else if (databaseProvider == "Sqlite")
management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));
})
.UseWorkflowRuntime(runtime =>
{
if (databaseProvider == "MySql")
runtime.UseEntityFrameworkCore(ef => ef.UseMySql(mySqlConnectionString));
else if (databaseProvider == "SqlServer")
runtime.UseEntityFrameworkCore(ef => ef.UseSqlServer(sqlServerConnectionString));
else if (databaseProvider == "Sqlite")
runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));
if (useMassTransit)
{
runtime.UseMassTransitDispatcher();
}
if (useProtoActor)
{
runtime.UseProtoActor();
}
if (useCaching)
runtime.UseCache();
runtime.DistributedLockProvider = _ => new FileDistributedSynchronizationProvider(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "locks")));
runtime.WorkflowInboxCleanupOptions = options => configuration.GetSection("Runtime:WorkflowInboxCleanup").Bind(options);
runtime.WorkflowDispatcherOptions = options => configuration.GetSection("Runtime:WorkflowDispatcher").Bind(options);
})
.UseScheduling()
.UseJavaScript(javaScriptFeature =>
{
javaScriptFeature
.ConfigureJintOptions(jintOptions => jintOptions.AllowClrAccess = true)
.UseLodashFp()
.UseMoment();
})
.UseLiquid()
.UseCSharp()
.UsePython()
.UseHttp(http =>
{
if (useCaching)
http.UseCache();
http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options);
})
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
.UseWebhooks(webhooks => webhooks.ConfigureSinks = options => builder.Configuration.GetSection("Webhooks:Sinks").Bind(options))
.UseWorkflowsApi()
.UseAgentsApi()
.UseAgentPersistence(persistence => persistence.UseEntityFrameworkCore(ef =>
{
if (databaseProvider == "MySql")
ef.UseMySql(mySqlConnectionString);
else if (databaseProvider == "SqlServer")
ef.UseSqlServer(sqlServerConnectionString);
else if (databaseProvider == "Sqlite")
ef.UseSqlite(sqliteConnectionString);
}))
.UseAgentActivities()
.UseRealTimeWorkflows()
.AddActivitiesFrom<Program>()
.AddWorkflowsFrom<Program>();
if (useProtoActor)
{
elsa.UseProtoActor(proto => proto.PersistenceProvider = _ =>
{
return new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString));
});
}
if (useMassTransit)
{
elsa.UseMassTransit(massTransit =>
{
switch (useMassTransitBroker)
{
case MassTransitBroker.AzureServiceBus:
massTransit.UseAzureServiceBus(azureServiceBusConnectionString, asb =>
{
asb.SubscriptionCleanupOptions = options => options.Interval = TimeSpan.FromMinutes(5);
asb.EnableAutomatedSubscriptionCleanup = true;
});
break;
case MassTransitBroker.RabbitMq:
massTransit.UseRabbitMq(rabbitMqConnectionString);
break;
}
}
);
}
if (distributedCachingTransport != DistributedCachingTransport.None)
{
elsa.UseDistributedCache(distributedCaching =>
{
if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit();
});
}
});
services.Configure<WebhookSinksOptions>(options => configuration.GetSection("Webhooks").Bind(options));
services.AddHealthChecks();
services.AddCors(cors => cors.Configure(configuration.GetSection("CorsPolicy")));
// Razor Pages.
services.AddRazorPages(options => options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()));
// Configure middleware pipeline.
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.MapHealthChecks("/health");
app.UseRouting();
app.UseCors();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseWorkflowsApi();
app.UseWorkflows();
app.UseWorkflowsSignalRHubs();
app.MapFallbackToPage("/_Host");
await app.RunAsync();