multi-tenant implementierung
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using timetracker.Shared;
|
||||
|
||||
namespace timetracker.Data;
|
||||
|
||||
public class TenantProvider : ITenantProvider
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IDbContextFactory<TimetrackerDbContext> _dbContextFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public TenantProvider(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IDbContextFactory<TimetrackerDbContext> dbContextFactory,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public int? TenantId
|
||||
{
|
||||
get
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
if (httpContext == null) return null;
|
||||
|
||||
if (httpContext.Items.TryGetValue("TenantId", out var cachedId))
|
||||
{
|
||||
return (int?)cachedId;
|
||||
}
|
||||
|
||||
// Resolve and cache in HttpContext.Items
|
||||
ResolveTenantAsync().GetAwaiter().GetResult();
|
||||
|
||||
if (httpContext.Items.TryGetValue("TenantId", out cachedId))
|
||||
{
|
||||
return (int?)cachedId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Tenant?> GetCurrentTenantAsync()
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
if (httpContext == null) return null;
|
||||
|
||||
if (httpContext.Items.TryGetValue("CurrentTenant", out var cachedTenant))
|
||||
{
|
||||
return (Tenant?)cachedTenant;
|
||||
}
|
||||
|
||||
await ResolveTenantAsync();
|
||||
|
||||
if (httpContext.Items.TryGetValue("CurrentTenant", out cachedTenant))
|
||||
{
|
||||
return (Tenant?)cachedTenant;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task ResolveTenantAsync()
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
if (httpContext == null) return;
|
||||
|
||||
// Prevent double resolution in the same request
|
||||
if (httpContext.Items.ContainsKey("TenantResolved")) return;
|
||||
httpContext.Items["TenantResolved"] = true;
|
||||
|
||||
var host = httpContext.Request.Host.Host;
|
||||
var subdomain = GetSubdomain(host);
|
||||
|
||||
if (!string.IsNullOrEmpty(subdomain))
|
||||
{
|
||||
await using var db = await _dbContextFactory.CreateDbContextAsync();
|
||||
var tenant = await db.Tenants
|
||||
.FirstOrDefaultAsync(t => t.Subdomain.ToLower() == subdomain.ToLower());
|
||||
|
||||
if (tenant != null)
|
||||
{
|
||||
httpContext.Items["CurrentTenant"] = tenant;
|
||||
httpContext.Items["TenantId"] = tenant.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetSubdomain(string host)
|
||||
{
|
||||
var baseDomain = _configuration["TenantSettings:BaseDomain"] ?? "localhost";
|
||||
|
||||
// Ignore port if included in host
|
||||
var hostName = host.Split(':')[0];
|
||||
|
||||
if (hostName.Equals(baseDomain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null; // Main domain
|
||||
}
|
||||
|
||||
if (hostName.EndsWith("." + baseDomain, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return hostName.Substring(0, hostName.Length - baseDomain.Length - 1);
|
||||
}
|
||||
|
||||
// Fallback for localhost testing
|
||||
if (baseDomain.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var parts = hostName.Split('.');
|
||||
if (parts.Length > 1 && !hostName.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return parts[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user