Moongazing.MultiTenant 1.0.0

dotnet add package Moongazing.MultiTenant --version 1.0.0
                    
NuGet\Install-Package Moongazing.MultiTenant -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Moongazing.MultiTenant" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Moongazing.MultiTenant" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Moongazing.MultiTenant" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Moongazing.MultiTenant --version 1.0.0
                    
#r "nuget: Moongazing.MultiTenant, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Moongazing.MultiTenant@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Moongazing.MultiTenant&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Moongazing.MultiTenant&version=1.0.0
                    
Install as a Cake Tool

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-typed TenantId)
  • ?? 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
  • ITenantScoped marker 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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