Muonroi.Tenancy.SiteProfile
1.0.0-alpha.16
dotnet add package Muonroi.Tenancy.SiteProfile --version 1.0.0-alpha.16
NuGet\Install-Package Muonroi.Tenancy.SiteProfile -Version 1.0.0-alpha.16
<PackageReference Include="Muonroi.Tenancy.SiteProfile" Version="1.0.0-alpha.16" />
<PackageVersion Include="Muonroi.Tenancy.SiteProfile" Version="1.0.0-alpha.16" />
<PackageReference Include="Muonroi.Tenancy.SiteProfile" />
paket add Muonroi.Tenancy.SiteProfile --version 1.0.0-alpha.16
#r "nuget: Muonroi.Tenancy.SiteProfile, 1.0.0-alpha.16"
#:package Muonroi.Tenancy.SiteProfile@1.0.0-alpha.16
#addin nuget:?package=Muonroi.Tenancy.SiteProfile&version=1.0.0-alpha.16&prerelease
#tool nuget:?package=Muonroi.Tenancy.SiteProfile&version=1.0.0-alpha.16&prerelease
Muonroi.Tenancy.SiteProfile
Per-site DI registration pattern for schema-divergent multi-tenancy — wire the correct DbContext, services, and mappers for each site deployment from a single binary.
In multi-tenant SaaS deployments each site can carry a divergent database schema, its own EF Core DbContext subclass, and site-specific business logic. This package solves that by introducing ISiteProfile — a per-site DI registration contract — and the extension methods to activate one or many profiles at startup. A scoped ISiteProfileResolver makes the active profile available inside every HTTP request, background job, or test.
The package sits between Muonroi.Tenancy.Core (ambient TenantContext) and the higher-level Muonroi.Tenancy.SiteProfile.Web (EF Core / Dapper per-site infrastructure, middleware, hot-reload).
Installation
dotnet add package Muonroi.Tenancy.SiteProfile --prerelease
Quick Start
Single-site deployment (1 binary = 1 site)
// Program.cs
builder.Services.AddSiteProfile<Sg01Profile>(builder.Configuration);
// Sg01Profile.cs
public class Sg01Profile : ISiteProfile
{
public string SiteId => "sg01";
public void RegisterServices(IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<Sg01DbContext>(o =>
o.UseNpgsql(configuration.GetConnectionString("Sg01")));
services.AddKeyedScoped<IOrderService, Sg01OrderService>("sg01");
}
}
Multi-site deployment (1 binary = N sites, reflection discovery)
// Program.cs — scans assemblies for ISiteProfile implementations
builder.Services.AddMultiSiteProfiles(
builder.Configuration,
siteCodeAccessor: sp => sp.GetRequiredService<IHttpContextAccessor>()
.HttpContext?.Request.Headers["x-site-code"],
assemblies:
[
typeof(DefaultSiteProfile).Assembly,
typeof(AlphaSiteProfile).Assembly,
typeof(BravoSiteProfile).Assembly,
]);
// Wire a per-request service resolved by site key
builder.Services.AddSiteResolvedService<IOrderService>();
Each ISiteProfile.RegisterServices() registers its implementation as a keyed service:
services.AddKeyedScoped<IOrderService, AlphaOrderService>("ALPHA");
AddSiteResolvedService<IOrderService>() then adds a scoped factory that calls
GetKeyedService<IOrderService>(siteId) at runtime, falling back to "default" if
the site key is not found.
Source-generated profile (AOT-safe)
[GenerateSiteProfile("ALPHA", typeof(AlphaOrderContext))]
public partial class AlphaSiteProfile : ISiteProfile
{
public string SiteId => "ALPHA";
// RegisterServices() is generated by Muonroi.Tenancy.SiteProfile.SourceGenerators
}
Add the generator package alongside this one:
dotnet add package Muonroi.Tenancy.SiteProfile.SourceGenerators --prerelease
Features
ISiteProfilecontract — implement once per site;RegisterServices()wires DbContext, repos, services, and mappers for that site.AddSiteProfile<T>()— single-site registration; one binary, one profile.AddMultiSiteProfiles()— reflection-based discovery of allISiteProfileimplementations across supplied assemblies; wires per-requestISiteProfileResolver.AddMultiSiteProfilesCore()— AOT-safe overload; accepts pre-instantiatedISiteProfile[]from generated manifests.AddSiteResolvedService<TService>()— registers a scoped factory that dispatches to the keyed service matching the currentSiteId.AddSiteConventionServices<TServiceInterface>()— convention-based scan: pairs eachISiteProfilewith a matchingTServiceInterfaceimplementation by namespace proximity and registers them as keyed scoped services.SiteProfileScope—AsyncLocal-backed scope that overrides the resolved profile for tests and background jobs.SiteProfileOptions.StrictMode— whentrue, an unknown site code throwsMInternalExceptioninstead of falling back to"default".ISiteProfileBehavior+[SiteProfileBehavior(typeof(T))]— cross-cutting behaviors (e.g. audit logging) applied declaratively to a profile class.ISiteEntityProfile<TCore, TSite>— compile-time constraint that enforcesTSite : TCorefor site entity hierarchies.[GenerateSiteProfile(siteId, dbContextType)]— marks a partialISiteProfileclass for source-generator scaffolding ofRegisterServices().[SiteProfileAlias(targetSiteId)]— declares one site as an alias of another, reusing its service registrations.[SiteEntityMap(coreType, siteType, table)]— runtime attribute carrying core↔site entity mapping metadata for source generators.SiteProfileStartupValidator— hosted service that validates all registered site profiles at startup and logs failures.AddSiteProfileRuleEngineIntegration()— opt-in bridge that registersISiteProfileFactBagEnricher; injects__site.idand__site.profileintoFactBagbefore rule orchestrator execution.
Configuration
builder.Services.ConfigureSiteProfile(o =>
{
// Throw on unknown site codes instead of falling back to "default"
o.StrictMode = true;
// Assemblies to scan for ISiteProfile implementations (used by SiteProfileStartupValidator)
o.SiteAssemblies = [typeof(AlphaSiteProfile).Assembly];
});
ConfigureSiteProfile calls services.Configure<SiteProfileOptions>(). Options can also
be bound from appsettings.json via the standard services.Configure<SiteProfileOptions>(configuration.GetSection(...)) pattern.
Disable startup validation (testing / dev)
builder.Services.SkipSiteProfileStartupValidation();
Must be called after AddSiteProfile or AddMultiSiteProfiles.
Test isolation with SiteProfileScope
using (SiteProfileScope.ForSite(myTestProfile))
{
// ISiteProfileResolver.Current returns myTestProfile for this async context
var result = await sut.CreateOrderAsync(request);
}
API Reference
| Type | Purpose |
|---|---|
ISiteProfile |
Per-site contract: SiteId, IsEnabled, RegisterServices() |
SiteProfileExtensions |
ConfigureSiteProfile(), AddSiteProfile<T>(), AddSiteProfile(instance), AddMultiSiteProfiles(), AddMultiSiteProfilesCore(), AddSiteResolvedService<T>(), SkipSiteProfileStartupValidation() |
SiteProfileConventionExtensions |
AddSiteConventionServices<TServiceInterface>() — convention-based keyed registration |
ISiteProfileResolver |
Scoped: Current returns the active ISiteProfile for the request |
SiteProfileResolver |
Default implementation of ISiteProfileResolver |
SiteProfileScope |
ForSite(profile) — AsyncLocal override for tests and background jobs |
SiteProfileOptions |
StrictMode, SiteAssemblies |
ISiteProfileBehavior |
Cross-cutting DI behavior applied via [SiteProfileBehavior] |
ISiteEntityProfile<TCore, TSite> |
Compile-time TSite : TCore constraint marker |
ISiteProfileFactBagEnricher |
Rule engine bridge — enriches FactBag with site identity |
GenerateSiteProfileAttribute |
[GenerateSiteProfile(siteId, dbContextType)] — triggers source-generator scaffolding |
SiteProfileAliasAttribute |
[SiteProfileAlias(targetSiteId)] — site-to-site alias |
SiteEntityMapAttribute |
[SiteEntityMap(coreType, siteType, table)] — entity mapping metadata |
SiteProfileBehaviorAttribute |
[SiteProfileBehavior(typeof(T))] — attaches a cross-cutting behavior |
SiteProfileStartupValidator |
IHostedService — validates all profiles at startup |
Samples
- TestProject.Service — multi-site service with Alpha / Bravo / Charlie / Default profiles,
[GenerateSiteProfile],[SiteProfileAlias], convention services, and gRPC site resolution
Compatibility
- Target framework:
net8.0 - License: Apache-2.0 (OSS)
Related Packages
Muonroi.Tenancy.Core— providesTenantContext(ambientCurrentTenantId),DefaultTenantIdResolver, andTenantSchemaSelectorthat SiteProfile builds on top ofMuonroi.Tenancy.SiteProfile.Web— adds per-site EF Core / Dapper infrastructure,SiteProfileStateMiddleware, hot-reload, andAddSiteDbInfrastructure()Muonroi.Tenancy.SiteProfile.SourceGenerators— source generator that scaffoldsRegisterServices()for[GenerateSiteProfile]-annotated partial classes
License
Apache-2.0. See LICENSE-APACHE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Muonroi.Logging.Abstractions (>= 1.0.0-alpha.16)
- Muonroi.Tenancy.Core (>= 1.0.0-alpha.16)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Muonroi.Tenancy.SiteProfile:
| Package | Downloads |
|---|---|
|
Muonroi.Tenancy.SiteProfile.Web
ASP.NET Core middleware and SignalR hot-reload for Muonroi.Tenancy.SiteProfile. |
|
|
Muonroi.Tenancy.SiteProfile.Grpc
gRPC site resolution interceptor for multi-site services. Extracts SiteCode from gRPC metadata and resolves ISiteProfile per request. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-alpha.16 | 57 | 6/22/2026 |
| 1.0.0-alpha.15 | 96 | 5/31/2026 |
| 1.0.0-alpha.14 | 82 | 5/15/2026 |
| 1.0.0-alpha.13 | 93 | 5/2/2026 |
| 1.0.0-alpha.12 | 81 | 4/2/2026 |
| 1.0.0-alpha.11 | 122 | 4/2/2026 |
| 1.0.0-alpha.9 | 83 | 3/30/2026 |
| 1.0.0-alpha.8 | 324 | 3/28/2026 |
| 1.0.0-alpha.7 | 73 | 3/27/2026 |
| 1.0.0-alpha.5 | 66 | 3/27/2026 |
| 1.0.0-alpha.4 | 66 | 3/27/2026 |
| 1.0.0-alpha.3 | 63 | 3/27/2026 |
| 1.0.0-alpha.2 | 66 | 3/26/2026 |