Add base path providers and standardize tenant accessor property
Introduce DefaultHttpEndpointBasePathProvider and TenantPrefixHttpEndpointBasePathProvider to manage HTTP endpoint base paths. Update ITenantAccessor property from CurrentTenant to Tenant for consistency across the codebase.
This commit is contained in:
parent
b8ab05ce2e
commit
bdaae64e72
|
|
@ -14,7 +14,7 @@ public static class MultitenantConnectionStringServiceProviderExtensions
|
|||
{
|
||||
var tenantAccessor = serviceProvider.GetRequiredService<ITenantAccessor>();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var defaultConnectionString = configuration.GetConnectionString(name)!;
|
||||
var connectionString = tenant?.GetConnectionString(name) ?? defaultConnectionString;
|
||||
return connectionString;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ namespace Elsa.Common.Multitenancy;
|
|||
public interface ITenantAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the current <see cref="Tenant"/>.
|
||||
/// Get the current <see cref="Multitenancy.Tenant"/>.
|
||||
/// </summary>
|
||||
/// <returns>Current tenant or null.</returns>
|
||||
Tenant? CurrentTenant { get; set; }
|
||||
Tenant? Tenant { get; set; }
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ public class DefaultTenantAccessor : ITenantAccessor
|
|||
private static readonly AsyncLocal<Tenant?> CurrentTenantField = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Tenant? CurrentTenant
|
||||
public Tenant? Tenant
|
||||
{
|
||||
get => CurrentTenantField.Value;
|
||||
set => CurrentTenantField.Value = value;
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@ public class TenantScope : IDisposable
|
|||
{
|
||||
_serviceScope = serviceScope;
|
||||
_tenantAccessor = tenantAccessor;
|
||||
_originalTenant = tenantAccessor.CurrentTenant;
|
||||
_tenantAccessor.CurrentTenant = tenant;
|
||||
_originalTenant = tenantAccessor.Tenant;
|
||||
_tenantAccessor.Tenant = tenant;
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider => _serviceScope.ServiceProvider;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_tenantAccessor.CurrentTenant = _originalTenant;
|
||||
_tenantAccessor.Tenant = _originalTenant;
|
||||
_serviceScope.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -512,7 +512,7 @@ public class Store<T>(IDbConnectionProvider dbConnectionProvider, ITenantAccesso
|
|||
if (tenantAgnostic)
|
||||
return;
|
||||
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantId = tenant?.Id;
|
||||
query.Is(nameof(Record.TenantId), (object?)tenantId ?? DBNull.Value);
|
||||
}
|
||||
|
|
@ -522,7 +522,7 @@ public class Store<T>(IDbConnectionProvider dbConnectionProvider, ITenantAccesso
|
|||
if (record is not Record recordWithTenant)
|
||||
return;
|
||||
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantId = tenant?.Id;
|
||||
recordWithTenant.TenantId = tenantId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema
|
|||
Schema = !string.IsNullOrWhiteSpace(elsaDbContextOptions?.SchemaName) ? elsaDbContextOptions.SchemaName : ElsaSchema;
|
||||
|
||||
var tenantAccessor = serviceProvider.GetService<ITenantAccessor>();
|
||||
TenantId = tenantAccessor?.CurrentTenant?.Id;
|
||||
TenantId = tenantAccessor?.Tenant?.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.Http;
|
||||
|
||||
public interface IHttpEndpointBasePathProvider
|
||||
{
|
||||
string GetBasePath();
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ using FluentStorage;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Http.Features;
|
||||
|
|
@ -32,6 +33,7 @@ namespace Elsa.Http.Features;
|
|||
public class HttpFeature(IModule module) : FeatureBase(module)
|
||||
{
|
||||
private Func<IServiceProvider, IHttpEndpointRoutesProvider> _httpEndpointRouteProvider = sp => sp.GetRequiredService<DefaultHttpEndpointRoutesProvider>();
|
||||
private Func<IServiceProvider, IHttpEndpointBasePathProvider> _httpEndpointBasePathProvider = sp => sp.GetRequiredService<DefaultHttpEndpointBasePathProvider>();
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to configure <see cref="HttpActivityOptions"/>.
|
||||
|
|
@ -106,6 +108,18 @@ public class HttpFeature(IModule module) : FeatureBase(module)
|
|||
_httpEndpointRouteProvider = httpEndpointRouteProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpFeature WithHttpEndpointBasePathProvider<T>() where T : class, IHttpEndpointBasePathProvider
|
||||
{
|
||||
Services.TryAddScoped<T>();
|
||||
return WithHttpEndpointBasePathProvider(sp => sp.GetRequiredService<T>());
|
||||
}
|
||||
|
||||
public HttpFeature WithHttpEndpointBasePathProvider(Func<IServiceProvider, IHttpEndpointBasePathProvider> httpEndpointBasePathProvider)
|
||||
{
|
||||
_httpEndpointBasePathProvider = httpEndpointBasePathProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
|
|
@ -178,6 +192,7 @@ public class HttpFeature(IModule module) : FeatureBase(module)
|
|||
|
||||
// Activity property options providers.
|
||||
.AddScoped<IPropertyUIHandler, HttpContentTypeOptionsProvider>()
|
||||
.AddScoped(_httpEndpointBasePathProvider)
|
||||
|
||||
// Port resolvers.
|
||||
.AddScoped<IActivityResolver, SendHttpRequestActivityResolver>()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class CachingHttpWorkflowLookupService(
|
|||
/// <inheritdoc />
|
||||
public async Task<HttpWorkflowLookupResult?> FindWorkflowAsync(string bookmarkHash, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantId = tenant?.Id;
|
||||
var tenantIdPrefix = !string.IsNullOrEmpty(tenantId) ? $"{tenantId}:" : string.Empty;
|
||||
var key = $"{tenantIdPrefix}http-workflow:{bookmarkHash}";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
using Elsa.Http.Options;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Http.Services;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class DefaultHttpEndpointBasePathProvider(IOptions<HttpActivityOptions> options) : IHttpEndpointBasePathProvider
|
||||
{
|
||||
public string GetBasePath()
|
||||
{
|
||||
var baseUrl = options.Value.BaseUrl;
|
||||
var basePath = options.Value.BasePath;
|
||||
var completeBaseUrl = new Uri(baseUrl, basePath);
|
||||
|
||||
return completeBaseUrl.ToString().TrimEnd('/') + '/';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,27 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Http.Options;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.UIHints;
|
||||
using Elsa.Workflows.UIHints.SingleLine;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Http.UIHints;
|
||||
|
||||
/// <summary>
|
||||
/// Provides additional options for the Path input field.
|
||||
/// </summary>
|
||||
public class HttpEndpointPathUIHandler(IOptions<HttpActivityOptions> options) : IPropertyUIHandler
|
||||
public class HttpEndpointPathUIHandler(IHttpEndpointBasePathProvider httpEndpointBasePathProvider) : IPropertyUIHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IDictionary<string, object>> GetUIPropertiesAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var baseUrl = options.Value.BaseUrl;
|
||||
var basePath = options.Value.BasePath;
|
||||
var completeBaseUrl = new Uri(baseUrl, basePath);
|
||||
var completeBaseUrl = httpEndpointBasePathProvider.GetBasePath();
|
||||
|
||||
return new(new Dictionary<string, object>
|
||||
{
|
||||
[InputUIHints.SingleLine] = new SingleLineProps
|
||||
{
|
||||
AdornmentText = completeBaseUrl.ToString().TrimEnd('/') + '/'
|
||||
AdornmentText = completeBaseUrl.TrimEnd('/') + '/'
|
||||
},
|
||||
["Refresh"] = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -395,7 +395,7 @@ public class MongoDbStore<TDocument>(IMongoCollection<TDocument> collection, ITe
|
|||
|
||||
if(typeof(Entity).IsAssignableFrom(typeof(TDocument)))
|
||||
{
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantId = tenant?.Id;
|
||||
queryable = queryable.Where(x => (x as Entity)!.TenantId == tenantId);
|
||||
}
|
||||
|
|
@ -405,7 +405,7 @@ public class MongoDbStore<TDocument>(IMongoCollection<TDocument> collection, ITe
|
|||
|
||||
private void ApplyTenantId(TDocument document)
|
||||
{
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantId = tenant?.Id;
|
||||
|
||||
if (document is Entity tenantDocument)
|
||||
|
|
@ -414,7 +414,7 @@ public class MongoDbStore<TDocument>(IMongoCollection<TDocument> collection, ITe
|
|||
|
||||
private void ApplyTenantId(IEnumerable<TDocument> documents)
|
||||
{
|
||||
var tenant = tenantAccessor.CurrentTenant;
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantId = tenant?.Id;
|
||||
|
||||
foreach (var document in documents)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class MultitenantHttpRoutingFeature(IModule module) : FeatureBase(module)
|
|||
Module.Configure<HttpFeature>(feature =>
|
||||
{
|
||||
feature.WithHttpEndpointRoutesProvider<TenantPrefixHttpEndpointRoutesProvider>();
|
||||
feature.WithHttpEndpointBasePathProvider<TenantPrefixHttpEndpointBasePathProvider>();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.Http;
|
||||
using Elsa.Http.Options;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Tenants.AspNetCore.Services;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class TenantPrefixHttpEndpointBasePathProvider(
|
||||
IOptions<HttpActivityOptions> options,
|
||||
IEnumerable<ITenantResolver> tenantResolvers,
|
||||
ITenantAccessor tenantAccessor) : IHttpEndpointBasePathProvider
|
||||
{
|
||||
public string GetBasePath()
|
||||
{
|
||||
var baseUrl = options.Value.BaseUrl.ToString();
|
||||
var basePath = options.Value.BasePath?.ToString().TrimStart('/');
|
||||
var routePrefixTenantResolverIsEnabled = tenantResolvers.Any(x => x is RoutePrefixTenantResolver);
|
||||
|
||||
if(!routePrefixTenantResolverIsEnabled)
|
||||
return (baseUrl + basePath).TrimEnd('/') + '/';
|
||||
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var tenantPrefix = tenant?.GetRoutePrefix();
|
||||
|
||||
if(string.IsNullOrWhiteSpace(tenantPrefix))
|
||||
return (baseUrl + basePath).TrimEnd('/') + '/';
|
||||
|
||||
var completeBaseUrl = new Uri(baseUrl + tenantPrefix + "/") + basePath;
|
||||
return completeBaseUrl.TrimEnd('/') + '/';
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ public class CachingWorkflowDefinitionStore(IWorkflowDefinitionStore decoratedSt
|
|||
|
||||
private async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory)
|
||||
{
|
||||
var tenantId = tenantAccessor.CurrentTenant?.Id;
|
||||
var tenantId = tenantAccessor.Tenant?.Id;
|
||||
var tenantIdPrefix = !string.IsNullOrEmpty(tenantId) ? $"{tenantId}:" : string.Empty;
|
||||
var internalKey = $"{tenantIdPrefix}{typeof(T).Name}:{key}";
|
||||
return await cacheManager.GetOrCreateAsync(internalKey, async entry =>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class CachingTriggerStore(ITriggerStore decoratedStore, ICacheManager cac
|
|||
|
||||
private async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory)
|
||||
{
|
||||
var tenantId = tenantAccessor.CurrentTenant?.Id;
|
||||
var tenantId = tenantAccessor.Tenant?.Id;
|
||||
var tenantIdPrefix = !string.IsNullOrEmpty(tenantId) ? $"{tenantId}:" : string.Empty;
|
||||
var internalKey = $"{tenantIdPrefix}{typeof(T).Name}:{key}";
|
||||
return await cacheManager.GetOrCreateAsync(internalKey, async entry =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue