Making.MultiTenancy
1.0.4-preview
This is a prerelease version of Making.MultiTenancy.
dotnet add package Making.MultiTenancy --version 1.0.4-preview
NuGet\Install-Package Making.MultiTenancy -Version 1.0.4-preview
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="Making.MultiTenancy" Version="1.0.4-preview" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Making.MultiTenancy" Version="1.0.4-preview" />
<PackageReference Include="Making.MultiTenancy" />
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 Making.MultiTenancy --version 1.0.4-preview
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Making.MultiTenancy, 1.0.4-preview"
#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 Making.MultiTenancy@1.0.4-preview
#: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=Making.MultiTenancy&version=1.0.4-preview&prerelease
#tool nuget:?package=Making.MultiTenancy&version=1.0.4-preview&prerelease
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Making.MultiTenancy
Multi-tenancy implementation and services for the Making framework.
Overview
Making.MultiTenancy provides concrete implementations for multi-tenant applications in the Making framework. It includes current tenant management, async local tenant accessor, and dependency injection setup for building scalable multi-tenant applications.
Features
- Current Tenant Implementation: Concrete implementation of current tenant access
- Async Local Accessor: Thread-safe tenant context using AsyncLocal
- Tenant Context Switching: Safe tenant context changes with disposal pattern
- Dependency Injection: Full DI container integration
- Thread-Safe Operations: Concurrent tenant access support
Installation
dotnet add package Making.MultiTenancy
Usage
Register Services
services.AddMakingMultiTenancy();
Basic Multi-Tenancy Setup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMakingMultiTenancy();
// Register tenant-aware services
services.AddScoped<ITenantAwareService, TenantAwareService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Add tenant resolution middleware
app.UseMakingTenantResolution();
}
}
Tenant-Aware Service Implementation
public class ProductService
{
private readonly ICurrentTenant _currentTenant;
private readonly IProductRepository _productRepository;
public ProductService(ICurrentTenant currentTenant, IProductRepository productRepository)
{
_currentTenant = currentTenant;
_productRepository = productRepository;
}
public async Task<List<Product>> GetProductsAsync()
{
if (!_currentTenant.IsAvailable)
{
throw new InvalidOperationException("No tenant context available");
}
var tenantId = _currentTenant.Id;
return await _productRepository.GetByTenantAsync(tenantId);
}
public async Task<Product> CreateProductAsync(CreateProductRequest request)
{
var product = new Product
{
TenantId = _currentTenant.Id,
Name = request.Name,
Price = request.Price,
CreatedAt = DateTime.UtcNow
};
return await _productRepository.CreateAsync(product);
}
public async Task<Product> GetProductByIdAsync(int productId)
{
var product = await _productRepository.GetByIdAsync(productId);
// Ensure product belongs to current tenant
if (product?.TenantId != _currentTenant.Id)
{
throw new UnauthorizedAccessException("Product not accessible for current tenant");
}
return product;
}
}
Tenant Context Switching
public class ReportingService
{
private readonly ICurrentTenant _currentTenant;
private readonly IOrderService _orderService;
public ReportingService(ICurrentTenant currentTenant, IOrderService orderService)
{
_currentTenant = currentTenant;
_orderService = orderService;
}
public async Task<TenantReport> GenerateTenantReportAsync(string tenantId)
{
// Switch to specific tenant context
using (_currentTenant.Change(tenantId))
{
var orders = await _orderService.GetOrdersAsync();
var totalRevenue = orders.Sum(o => o.TotalAmount);
var orderCount = orders.Count;
return new TenantReport
{
TenantId = tenantId,
TenantName = _currentTenant.Name,
TotalOrders = orderCount,
TotalRevenue = totalRevenue,
GeneratedAt = DateTime.UtcNow
};
}
}
public async Task<List<TenantReport>> GenerateAllTenantsReportAsync(List<string> tenantIds)
{
var reports = new List<TenantReport>();
foreach (var tenantId in tenantIds)
{
var report = await GenerateTenantReportAsync(tenantId);
reports.Add(report);
}
return reports;
}
}
Multi-Tenant Database Context
public class ApplicationDbContext : DbContext
{
private readonly ICurrentTenant _currentTenant;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ICurrentTenant currentTenant)
: base(options)
{
_currentTenant = currentTenant;
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure global query filters for multi-tenancy
modelBuilder.Entity<Product>()
.HasQueryFilter(p => p.TenantId == _currentTenant.Id);
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _currentTenant.Id);
modelBuilder.Entity<Customer>()
.HasQueryFilter(c => c.TenantId == _currentTenant.Id);
base.OnModelCreating(modelBuilder);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Automatically set tenant ID for new entities
var tenantId = _currentTenant.Id;
foreach (var entry in ChangeTracker.Entries<ITenantEntity>())
{
if (entry.State == EntityState.Added && string.IsNullOrEmpty(entry.Entity.TenantId))
{
entry.Entity.TenantId = tenantId;
}
}
return await base.SaveChangesAsync(cancellationToken);
}
}
Tenant Resolution Middleware
public class TenantResolutionMiddleware
{
private readonly RequestDelegate _next;
private readonly ITenantResolver _tenantResolver;
private readonly ICurrentTenantAccessor _tenantAccessor;
public TenantResolutionMiddleware(
RequestDelegate next,
ITenantResolver tenantResolver,
ICurrentTenantAccessor tenantAccessor)
{
_next = next;
_tenantResolver = tenantResolver;
_tenantAccessor = tenantAccessor;
}
public async Task InvokeAsync(HttpContext context)
{
var tenantInfo = await _tenantResolver.ResolveAsync(context);
if (tenantInfo != null)
{
_tenantAccessor.Current = tenantInfo;
}
await _next(context);
}
}
public static class TenantResolutionMiddlewareExtensions
{
public static IApplicationBuilder UseMakingTenantResolution(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TenantResolutionMiddleware>();
}
}
Custom Tenant Resolver
public interface ITenantResolver
{
Task<ITenantInfo> ResolveAsync(HttpContext context);
}
public class HeaderTenantResolver : ITenantResolver
{
private readonly ITenantStore _tenantStore;
public HeaderTenantResolver(ITenantStore tenantStore)
{
_tenantStore = tenantStore;
}
public async Task<ITenantInfo> ResolveAsync(HttpContext context)
{
// Resolve tenant from custom header
if (context.Request.Headers.TryGetValue("X-Tenant-Id", out var tenantId))
{
return await _tenantStore.GetByIdAsync(tenantId);
}
// Resolve tenant from subdomain
var host = context.Request.Host.Host;
var subdomain = host.Split('.').FirstOrDefault();
if (!string.IsNullOrEmpty(subdomain))
{
return await _tenantStore.GetBySubdomainAsync(subdomain);
}
return null;
}
}
Requirements
- .NET Standard 2.0+
- Microsoft.Extensions.DependencyInjection.Abstractions
- Making.Core
- Making.MultiTenancy.Abstractions
License
This project is part of the Making framework.
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 is compatible. 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. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net8.0
- Making.Core (>= 1.0.4-preview)
- Making.MultiTenancy.Abstractions (>= 1.0.4-preview)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
-
net9.0
- Making.Core (>= 1.0.4-preview)
- Making.MultiTenancy.Abstractions (>= 1.0.4-preview)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Making.MultiTenancy:
Package | Downloads |
---|---|
Making.Ddd.Domain
Making framework for building modern applications |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.4-preview | 10 | 8/10/2025 |
1.0.1-preview | 322 | 7/25/2025 |
1.0.0-preview | 394 | 7/25/2025 |