Moongazing.MultiTenant
1.0.0
dotnet add package Moongazing.MultiTenant --version 1.0.0
NuGet\Install-Package Moongazing.MultiTenant -Version 1.0.0
<PackageReference Include="Moongazing.MultiTenant" Version="1.0.0" />
<PackageVersion Include="Moongazing.MultiTenant" Version="1.0.0" />
<PackageReference Include="Moongazing.MultiTenant" />
paket add Moongazing.MultiTenant --version 1.0.0
#r "nuget: Moongazing.MultiTenant, 1.0.0"
#:package Moongazing.MultiTenant@1.0.0
#addin nuget:?package=Moongazing.MultiTenant&version=1.0.0
#tool nuget:?package=Moongazing.MultiTenant&version=1.0.0
Moongazing.MultiTenant
Moongazing.MultiTenant: Lightweight, flexible, production-ready multi-tenant framework for ASP.NET Core.
- Strongly-typed pipeline (
TenantId,ITenantContext) - Extensible resolution strategies (
ITenantResolutionStrategy) - Zero reflection at runtime
- Clean DI + middleware integration
Core + ASP.NET Core only. EF Core integration ships separately as
Moongazing.MultiTenant.EFCore.
? Features
- ?? Header-based tenant resolution (default header
X-Tenant-Id, configurable) - ?? Scoped
ITenantContext(with strongly-typedTenantId) - ?? Tenant store abstraction (
ITenantStore) - ?? In-memory tenant store implementation
- ?? Pluggable resolution pipeline (strategies executed in registration order)
- ?? Middleware for per-request resolution
- ? No hidden static state / globals
- ?? Designed to extend (EF Core, gRPC, custom stores, custom strategies)
?? Installation
# From your ASP.NET Core project directory
dotnet add package Moongazing.MultiTenant
?? Quick Start
1?? Register multi-tenant services
builder.Services.AddMoongazingMultiTenant(options =>
{
options.NotFoundMode = TenantNotFoundMode.Throw; // or UseDefault / Anonymous
options.HeaderName = "X-Tenant-Id"; // customize if needed
});
2?? Provide tenant definitions
var tenants = new[]
{
new TenantInfo
{
Id = new TenantId("tenant-a"),
Name = "Tenant A",
ConnectionString = "Host=...;Database=A;"
},
new TenantInfo
{
Id = new TenantId("tenant-b"),
Name = "Tenant B",
ConnectionString = "Host=...;Database=B;"
}
};
builder.Services.AddInMemoryTenantStore(tenants);
3?? Add middleware
app.UseMoongazingMultiTenant();
4?? Consume ITenantContext
app.MapGet("/whoami", (ITenantContext tenant) => new { Tenant = tenant.Id });
Request example:
GET /whoami
X-Tenant-Id: tenant-a
?? How It Works
Resolution pipeline runs once per request:
[Strategy 1] ? [Strategy 2] ? [Strategy 3] ? First Match ? Resolved ITenantContext
Stops at first successful strategy.
Built-in strategy: HeaderTenantResolutionStrategy.
Add your own (host, path, JWT claim, custom logic) by implementing the interface below.
?? Custom Resolution Strategy
public sealed class CustomStrategy : ITenantResolutionStrategy
{
public Task<TenantResolutionResult> ResolveAsync(
TenantResolutionContext context,
CancellationToken cancellationToken = default)
{
var tenantId = context.Items.TryGetValue("Custom", out var value) ? value?.ToString() : null;
if (!string.IsNullOrWhiteSpace(tenantId))
{
return Task.FromResult(
TenantResolutionResult.FromSource(new TenantId(tenantId), "Custom")
);
}
return Task.FromResult(TenantResolutionResult.NotFound());
}
}
// Registration (order matters):
builder.Services.AddScoped<ITenantResolutionStrategy, CustomStrategy>();
?? TenantNotFoundMode
| Mode | Behavior |
|---|---|
| Throw | Throws if tenant cannot be resolved (default) |
| UseDefault | Uses DefaultTenantId if set |
| Anonymous | Continues with unresolved context (IsResolved = false) |
options.NotFoundMode = TenantNotFoundMode.UseDefault;
options.DefaultTenantId = new TenantId("tenant-a");
?? Key Abstractions
public interface ITenantContext
{
bool IsResolved { get; }
TenantId Id { get; }
ITenantInfo Info { get; }
}
public interface ITenantStore
{
Task<ITenantInfo?> FindByIdAsync(TenantId id, CancellationToken ct = default);
Task<IReadOnlyList<ITenantInfo>> GetAllAsync(CancellationToken ct = default);
}
public sealed class MultiTenantOptions
{
public TenantNotFoundMode NotFoundMode { get; set; } = TenantNotFoundMode.Throw;
public TenantId? DefaultTenantId { get; set; }
public string? HeaderName { get; set; } = "X-Tenant-Id";
}
?? EF Core Integration (Separate Package)
Planned features (in Moongazing.MultiTenant.EFCore):
- Tenant-scoped DbContext base class
- Per-tenant connection strings / schema
- Global query filters
ITenantScopedmarker interface
?? Testing
// Direct construction
var tenantInfo = new TenantInfo { Id = new TenantId("test"), Name = "Test", ConnectionString = "..." };
var tenantContext = new TenantContext(tenantInfo);
// Mocking store (example using NSubstitute)
var store = Substitute.For<ITenantStore>();
store.FindByIdAsync(new TenantId("t1")).Returns(tenantInfo);
?? Roadmap
v1.0.0
- ? Header-based strategy
- ? In-memory store
- ? Tenant context + middleware
- ? Namespace separation (Abstractions / Core / AspNetCore)
v1.1.0
- Host-based resolution
- Path-based resolution
v1.2.0
- JWT claim resolution
- Policy attributes (
[TenantRequired],[TenantMatch])
v2.0
- Multi-tenant health checks
- Tenant feature flags
- Redis tenant store
?? Contributing
Contributions welcome.
Guidelines:
- Follow
.editorconfig - XML docs for public APIs
- Unit tests for new logic
- Meaningful commit messages
?? License
MIT License. See LICENSE file.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 248 | 11/14/2025 |