Multi-Tenancy¶
Package: Asdamir.Core · Namespace: Asdamir.Core.MultiTenancy
Introduction¶
Multi-tenancy lets one deployment serve many tenants while keeping their data and configuration isolated. Asdamir resolves the current tenant per request and exposes it through an ambient ITenantContext that any service can inject.
Registration¶
builder.Services.AddMultiTenancy();
...
app.UseMultiTenancy(); // resolves the tenant early in the pipeline
UseMultiTenancy runs the configured resolver and populates ITenantContext for the rest of the request.
Resolving the tenant¶
A resolver implements ITenantResolver. Two implementations ship out of the box:
| Resolver | Source |
|---|---|
ClaimsTenantResolver |
a TenantId claim on the authenticated principal |
HeaderTenantResolver |
a request header (e.g. X-Tenant-Id) |
Provide your own by registering a custom ITenantResolver.
Using the tenant context¶
public sealed class ReportService(ITenantContext tenant)
{
public Task<Report> BuildAsync()
{
var tenantId = tenant.TenantId; // ambient, per-request
// scope queries / configuration to this tenant …
}
}
Configuration¶
Bind options through AddMultiTenancy (it accepts an optional Action<TenancyOptions>).
Tenant-aware features¶
Other building blocks honour the resolved tenant — for example database-backed configuration / feature flags can return tenant-specific values via IFeatureManager.GetConfigurationAsync<T>(key, tenantId).
AppManagement: the company (firma) model¶
The framework primitives above (ITenantResolver/ITenantContext) are the generic, request-scoped
tenant abstraction. AppManagement builds a concrete multi-company control plane on top of them —
one management database per company, every app's admin data sliced by AppId:
Company (firma) one management DB per company
└── App (Apps row) CLI-registered; `asdamir app register`
└── User company-unique identity
└── Permissions role + menu, scoped by AppId (Tier-2)
How a request resolves its slice:
| Dimension | Carried by | Resolved into | Picks |
|---|---|---|---|
| Company | JWT company claim (chosen pre-login) |
ICompanyContext |
which management database (company-aware IDbConnectionFactory) |
| App | X-App-Id request header (chosen post-login) |
IAppContext |
which AppId slice of Roles/Permissions/Menus/AppConfig/Localization |
- Catalog. Companies + their (encrypted) connection strings live in a bootstrap catalog
outside any company DB — config-backed today (
ICompanyCatalog), with aCompaniestable as the DB-backed alternative. A single-company deployment degenerates to the loneConnectionStrings:AsdamirVault, so nothing changes for one company. - Admin authz. AppManagement admins are company-level full authority
(
Users.IsFullAuthority) — no per-admin role/permission machinery. The role/permission/menu tables are exclusively Tier-2 (the managed apps' end-user RBAC), keyed byAppId. - Selection flow.
open → pick company → login → pick app → AppId-scoped reads. Both pickers auto-hide when there's only one choice. - Background jobs. Apps sharing a company's single Hangfire schema isolate via a per-app queue
{appCode}:{jobName}recurring-id prefix (see Background Jobs).
Full multi-company architecture is covered in the AppManagement documentation.