Multitenancy core: resolution pipeline¶
This page explains how ITenantResolver evaluates providers and how to customize ordering and consensus rules.
Resolution inputs¶
Tenant resolution is driven by TenantResolutionContext, which you populate in your host adapter:
using CleanArchitecture.Extensions.Multitenancy;
var context = new TenantResolutionContext
{
Host = httpContext.Request.Host.Host,
CorrelationId = httpContext.TraceIdentifier
};
foreach (var header in httpContext.Request.Headers)
{
context.Headers[header.Key] = header.Value.ToString();
}
foreach (var route in httpContext.Request.RouteValues)
{
if (route.Value is not null)
{
context.RouteValues[route.Key] = route.Value.ToString()!;
}
}
foreach (var query in httpContext.Request.Query)
{
context.Query[query.Key] = query.Value.ToString();
}
foreach (var claim in httpContext.User.Claims)
{
context.Claims[claim.Type] = claim.Value;
}
Built-in providers¶
The core package registers these providers by default:
RouteTenantProvider(usesRouteParameterName, high confidence)HostTenantProvider(usesHostTenantSelector, medium confidence)HeaderTenantProvider(usesHeaderNames, medium confidence)QueryTenantProvider(usesQueryParameterName, medium confidence)ClaimTenantProvider(usesClaimType, medium confidence)DefaultTenantProvider(usesFallbackTenant/FallbackTenantId, low confidence)
Header, query, and claim providers accept multiple candidates separated by , or ;. Multiple candidates are treated as ambiguous and do not resolve a tenant.
Ordering behavior¶
CompositeTenantResolutionStrategy orders providers using MultitenancyOptions.ResolutionOrder:
using CleanArchitecture.Extensions.Multitenancy.Configuration;
builder.Services.Configure<MultitenancyOptions>(options =>
{
options.ResolutionOrder = new List<TenantResolutionSource>
{
TenantResolutionSource.Route,
TenantResolutionSource.Host,
TenantResolutionSource.Header,
TenantResolutionSource.QueryString,
TenantResolutionSource.Claim,
TenantResolutionSource.Default
};
});
Providers not listed in ResolutionOrder are still evaluated when IncludeUnorderedProviders is true (default).
Consensus mode¶
When RequireMatchAcrossSources is enabled, the strategy collects candidates from all providers and resolves only if a single unique tenant ID exists:
builder.Services.Configure<MultitenancyOptions>(options =>
{
options.RequireMatchAcrossSources = true;
});
This mode is useful when multiple sources can be present (for example, route + header) and you want strict consistency.
Host parsing rules¶
The default host selector resolves the first subdomain:
tenant.app.com->tenantlocalhost-> not resolved- IP addresses -> not resolved
Override with a custom selector when needed:
builder.Services.Configure<MultitenancyOptions>(options =>
{
options.HostTenantSelector = host =>
{
if (host.EndsWith(".internal", StringComparison.OrdinalIgnoreCase))
{
return "internal";
}
return host.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.FirstOrDefault();
};
});
Custom providers¶
Add custom providers for environment-specific resolution:
using CleanArchitecture.Extensions.Multitenancy.Abstractions;
using CleanArchitecture.Extensions.Multitenancy.Providers;
builder.Services.AddSingleton<ITenantProvider>(
new DelegateTenantProvider(context =>
{
if (context.Items.TryGetValue("tenant_override", out var value))
{
return value?.ToString();
}
return null;
}, source: TenantResolutionSource.Custom, confidence: TenantResolutionConfidence.High));
Timeouts and cancellation¶
Set ResolutionTimeout to guard against slow providers:
builder.Services.Configure<MultitenancyOptions>(options =>
{
options.ResolutionTimeout = TimeSpan.FromMilliseconds(50);
});
The timeout is linked with the request cancellation token.
Diagnostics tips¶
- Inspect
TenantResolutionResult.Source,Confidence, andCandidatesin logs. - Ambiguous candidates return
IsAmbiguous == trueand do not resolve a tenant. - In consensus mode, a single unique candidate is required.