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" />
                    
Directory.Packages.props
<PackageReference Include="Making.MultiTenancy" />
                    
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 Making.MultiTenancy --version 1.0.4-preview
                    
#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
                    
Install as a Cake Addin
#tool nuget:?package=Making.MultiTenancy&version=1.0.4-preview&prerelease
                    
Install as a Cake Tool

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 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.

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