Enterprise
Multi-Tenancy
Tenant isolation and validation for multi-tenant applications
The multi-tenancy behavior ensures tenant context is present and validated for operations that require it.
Core Interfaces
ITenantContext
Provides current tenant information:
public interface ITenantContext
{
string? TenantId { get; }
bool HasTenant { get; }
string? TenantName { get; }
}ITenantAware
Mark messages that belong to a specific tenant:
public interface ITenantAware
{
string? TenantId { get; }
}Using the Attribute
[RequiresTenant]
public record GetTenantDataQuery(string DataKey) : IQuery<TenantData>;Tenant-Aware Messages
public record CreateTenantDocumentCommand(
string TenantId,
string Title,
string Content
) : ICommand<Document>, ITenantAware;Configuration
builder.Services.AddTurboMediator(m =>
{
m.WithMultiTenancy<IMessage, object>();
m.WithTenantContext<HttpTenantContext>();
});Implementing ITenantContext
public class HttpTenantContext : ITenantContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpTenantContext(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string? TenantId =>
_httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].FirstOrDefault()
?? _httpContextAccessor.HttpContext?.User?.FindFirst("tenant_id")?.Value;
public bool HasTenant => !string.IsNullOrEmpty(TenantId);
public string? TenantName =>
_httpContextAccessor.HttpContext?.User?.FindFirst("tenant_name")?.Value;
}Validation Rules
The TenantBehavior validates:
- If
[RequiresTenant]is present,ITenantContext.HasTenantmust betrue - If the message implements
ITenantAware, the message'sTenantIdmust matchITenantContext.TenantId - Throws an exception if validation fails
Practical Example
// Tenant-isolated queries
[RequiresTenant]
public record GetTenantUsersQuery : IQuery<IReadOnlyList<User>>;
public class GetTenantUsersHandler : IQueryHandler<GetTenantUsersQuery, IReadOnlyList<User>>
{
private readonly ITenantContext _tenantContext;
private readonly IUserRepository _repository;
public GetTenantUsersHandler(ITenantContext tenantContext, IUserRepository repository)
{
_tenantContext = tenantContext;
_repository = repository;
}
public async ValueTask<IReadOnlyList<User>> Handle(
GetTenantUsersQuery query, CancellationToken ct)
{
// TenantId is guaranteed to be present by the behavior
return await _repository.GetByTenantAsync(_tenantContext.TenantId!, ct);
}
}The tenant behavior should be registered before the handler in the pipeline to ensure tenant validation happens early. Register it after authentication/authorization.