127 lines
3.7 KiB
C#
127 lines
3.7 KiB
C#
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;
|
|
}
|
|
}
|