I-Synergy.Framework.AspNetCore.MultiTenancy
                                
                            
                                2025.11102.11228.52-preview
                            
                        
                            
                                
                                
                                    Prefix Reserved
                                
                            
                    See the version list below for details.
dotnet add package I-Synergy.Framework.AspNetCore.MultiTenancy --version 2025.11102.11228.52-preview
NuGet\Install-Package I-Synergy.Framework.AspNetCore.MultiTenancy -Version 2025.11102.11228.52-preview
<PackageReference Include="I-Synergy.Framework.AspNetCore.MultiTenancy" Version="2025.11102.11228.52-preview" />
<PackageVersion Include="I-Synergy.Framework.AspNetCore.MultiTenancy" Version="2025.11102.11228.52-preview" />
<PackageReference Include="I-Synergy.Framework.AspNetCore.MultiTenancy" />
paket add I-Synergy.Framework.AspNetCore.MultiTenancy --version 2025.11102.11228.52-preview
#r "nuget: I-Synergy.Framework.AspNetCore.MultiTenancy, 2025.11102.11228.52-preview"
#:package I-Synergy.Framework.AspNetCore.MultiTenancy@2025.11102.11228.52-preview
#addin nuget:?package=I-Synergy.Framework.AspNetCore.MultiTenancy&version=2025.11102.11228.52-preview&prerelease
#tool nuget:?package=I-Synergy.Framework.AspNetCore.MultiTenancy&version=2025.11102.11228.52-preview&prerelease
I-Synergy Framework AspNetCore Multi-Tenancy
Multi-tenant architecture support for ASP.NET Core applications. This package provides tenant resolution from HTTP context, tenant service for managing tenant-specific operations, and integration with OpenIddict claims for secure multi-tenant applications.
Features
- Tenant resolution from HTTP context and claims
 - Tenant service for accessing current tenant information
 - OpenIddict claims integration for secure tenant identification
 - HttpContext accessor for tenant management across request scope
 - User and tenant isolation with automatic group assignment
 - Programmatic tenant setting for testing and background jobs
 - Thread-safe tenant access throughout the application
 - Support for client roles in multi-tenant scenarios
 
Installation
Install the package via NuGet:
dotnet add package I-Synergy.Framework.AspNetCore.MultiTenancy
Quick Start
1. Configure Multi-Tenancy Services
In your Program.cs:
using ISynergy.Framework.AspNetCore.MultiTenancy.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add multi-tenancy support
builder.Services.AddMultiTenancyIntegration();
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. Using the Tenant Service
Access tenant information in your controllers and services:
using ISynergy.Framework.Core.Abstractions.Services;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class OrdersController : ControllerBase
{
    private readonly ITenantService _tenantService;
    private readonly IOrderRepository _orderRepository;
    public OrdersController(
        ITenantService tenantService,
        IOrderRepository orderRepository)
    {
        _tenantService = tenantService;
        _orderRepository = orderRepository;
    }
    [HttpGet]
    public async Task<IActionResult> GetOrders()
    {
        // Automatically filter by current tenant
        var tenantId = _tenantService.TenantId;
        var orders = await _orderRepository.GetByTenantAsync(tenantId);
        return Ok(orders);
    }
    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
    {
        var order = new Order
        {
            TenantId = _tenantService.TenantId,
            CreatedBy = _tenantService.UserName,
            // ... other properties
        };
        await _orderRepository.CreateAsync(order);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
    [HttpGet("{id}")]
    public async Task<IActionResult> GetOrder(int id)
    {
        var order = await _orderRepository.GetByIdAsync(id);
        // Verify tenant ownership
        if (order == null || order.TenantId != _tenantService.TenantId)
            return NotFound();
        return Ok(order);
    }
}
3. Setting Tenant Programmatically
For testing or background jobs, set the tenant context manually:
using ISynergy.Framework.Core.Abstractions.Services;
public class BackgroundJobService
{
    private readonly ITenantService _tenantService;
    public BackgroundJobService(ITenantService tenantService)
    {
        _tenantService = tenantService;
    }
    public async Task ProcessTenantDataAsync(Guid tenantId, string username)
    {
        // Set tenant context for this operation
        _tenantService.SetTenant(tenantId, username);
        // Now all operations will use this tenant context
        await ProcessDataAsync();
    }
    public async Task ProcessWithMinimalContextAsync(Guid tenantId)
    {
        // Set only tenant ID (no username)
        _tenantService.SetTenant(tenantId);
        await ProcessDataAsync();
    }
}
Core Components
Services
ISynergy.Framework.AspNetCore.MultiTenancy.Services/
└── TenantService                 # Tenant resolution and management
Extensions
ISynergy.Framework.AspNetCore.MultiTenancy.Extensions/
├── ServiceCollectionExtensions   # DI configuration
└── HostApplicationBuilderExtensions  # Builder configuration
Advanced Features
Tenant-Aware Entity Framework
Automatically filter queries by tenant:
using ISynergy.Framework.Core.Abstractions.Services;
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
    private readonly ITenantService _tenantService;
    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        ITenantService tenantService)
        : base(options)
    {
        _tenantService = tenantService;
    }
    public DbSet<Order> Orders { get; set; }
    public DbSet<Product> Products { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        // Add global query filter for multi-tenancy
        modelBuilder.Entity<Order>()
            .HasQueryFilter(e => e.TenantId == _tenantService.TenantId);
        modelBuilder.Entity<Product>()
            .HasQueryFilter(e => e.TenantId == _tenantService.TenantId);
    }
    public override int SaveChanges()
    {
        ApplyTenantId();
        return base.SaveChanges();
    }
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        ApplyTenantId();
        return await base.SaveChangesAsync(cancellationToken);
    }
    private void ApplyTenantId()
    {
        var tenantId = _tenantService.TenantId;
        foreach (var entry in ChangeTracker.Entries<ITenantEntity>())
        {
            if (entry.State == EntityState.Added)
            {
                entry.Entity.TenantId = tenantId;
            }
        }
    }
}
// Tenant entity interface
public interface ITenantEntity
{
    Guid TenantId { get; set; }
}
// Example entity
public class Order : ITenantEntity
{
    public int Id { get; set; }
    public Guid TenantId { get; set; }
    public string OrderNumber { get; set; }
    public decimal Amount { get; set; }
}
Tenant Resolution Middleware
Create custom middleware for tenant resolution:
using ISynergy.Framework.Core.Abstractions.Services;
public class TenantResolutionMiddleware
{
    private readonly RequestDelegate _next;
    public TenantResolutionMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
    {
        // Option 1: From subdomain
        var host = context.Request.Host.Host;
        var subdomain = host.Split('.').FirstOrDefault();
        if (!string.IsNullOrEmpty(subdomain) && subdomain != "www")
        {
            var tenantId = await ResolveTenantFromSubdomainAsync(subdomain);
            if (tenantId.HasValue)
            {
                tenantService.SetTenant(tenantId.Value);
            }
        }
        // Option 2: From custom header
        if (context.Request.Headers.TryGetValue("X-Tenant-ID", out var tenantHeader))
        {
            if (Guid.TryParse(tenantHeader, out var tenantId))
            {
                tenantService.SetTenant(tenantId);
            }
        }
        // Option 3: Already set from claims by the framework
        // No action needed - TenantService reads from User claims
        await _next(context);
    }
    private async Task<Guid?> ResolveTenantFromSubdomainAsync(string subdomain)
    {
        // Lookup tenant by subdomain
        // Return tenant ID or null
        return Guid.Empty; // Placeholder
    }
}
// Register middleware
app.UseMiddleware<TenantResolutionMiddleware>();
Tenant-Specific Configuration
Manage tenant-specific settings:
using ISynergy.Framework.Core.Abstractions.Services;
public class TenantConfigurationService
{
    private readonly ITenantService _tenantService;
    private readonly IConfiguration _configuration;
    public TenantConfigurationService(
        ITenantService tenantService,
        IConfiguration configuration)
    {
        _tenantService = tenantService;
        _configuration = configuration;
    }
    public string GetTenantSetting(string key)
    {
        var tenantId = _tenantService.TenantId;
        var tenantKey = $"Tenants:{tenantId}:{key}";
        return _configuration[tenantKey] ?? _configuration[key];
    }
    public T GetTenantSetting<T>(string key, T defaultValue = default)
    {
        var value = GetTenantSetting(key);
        if (string.IsNullOrEmpty(value))
            return defaultValue;
        return (T)Convert.ChangeType(value, typeof(T));
    }
}
// appsettings.json
{
  "Tenants": {
    "123e4567-e89b-12d3-a456-426614174000": {
      "MaxUsers": 100,
      "StorageQuotaGB": 50,
      "Features": ["AdvancedReporting", "API"]
    },
    "987fcdeb-51a2-43f1-9876-543210fedcba": {
      "MaxUsers": 10,
      "StorageQuotaGB": 5,
      "Features": ["BasicReporting"]
    }
  }
}
Tenant Isolation Validation
Ensure tenant isolation in critical operations:
using ISynergy.Framework.Core.Abstractions.Services;
public class TenantIsolationValidator
{
    private readonly ITenantService _tenantService;
    public TenantIsolationValidator(ITenantService tenantService)
    {
        _tenantService = tenantService;
    }
    public void ValidateTenantAccess<T>(T entity) where T : ITenantEntity
    {
        if (entity.TenantId != _tenantService.TenantId)
        {
            throw new UnauthorizedAccessException(
                $"Access denied. Entity belongs to different tenant.");
        }
    }
    public void ValidateTenantAccess(Guid tenantId)
    {
        if (tenantId != _tenantService.TenantId)
        {
            throw new UnauthorizedAccessException(
                $"Access denied. Operation for different tenant.");
        }
    }
}
// Usage in service
public class OrderService
{
    private readonly ITenantService _tenantService;
    private readonly TenantIsolationValidator _validator;
    private readonly IOrderRepository _repository;
    public OrderService(
        ITenantService tenantService,
        TenantIsolationValidator validator,
        IOrderRepository repository)
    {
        _tenantService = tenantService;
        _validator = validator;
        _repository = repository;
    }
    public async Task DeleteOrderAsync(int orderId)
    {
        var order = await _repository.GetByIdAsync(orderId);
        if (order == null)
            throw new NotFoundException("Order not found");
        // Validate tenant isolation
        _validator.ValidateTenantAccess(order);
        await _repository.DeleteAsync(order);
    }
}
Usage Examples
Multi-Tenant SaaS Application
Complete example of a multi-tenant SaaS application:
using ISynergy.Framework.AspNetCore.MultiTenancy.Extensions;
using ISynergy.Framework.Core.Abstractions.Services;
var builder = WebApplication.CreateBuilder(args);
// Add multi-tenancy
builder.Services.AddMultiTenancyIntegration();
// Add authentication with OpenIddict
builder.Services.AddAuthentication("OpenIddict.Validation.AspNetCore")
    .AddOpenIddictValidation(options =>
    {
        options.SetIssuer("https://auth.example.com");
    });
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
// Customer Controller
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class CustomersController : ControllerBase
{
    private readonly ITenantService _tenantService;
    private readonly ICustomerRepository _customerRepository;
    public CustomersController(
        ITenantService tenantService,
        ICustomerRepository customerRepository)
    {
        _tenantService = tenantService;
        _customerRepository = customerRepository;
    }
    [HttpGet]
    public async Task<IActionResult> GetCustomers()
    {
        // Automatically scoped to current tenant
        var customers = await _customerRepository
            .GetAllAsync(_tenantService.TenantId);
        return Ok(customers);
    }
    [HttpPost]
    public async Task<IActionResult> CreateCustomer([FromBody] CreateCustomerRequest request)
    {
        var customer = new Customer
        {
            TenantId = _tenantService.TenantId,
            Name = request.Name,
            Email = request.Email,
            CreatedBy = _tenantService.UserName,
            CreatedAt = DateTime.UtcNow
        };
        await _customerRepository.CreateAsync(customer);
        return CreatedAtAction(nameof(GetCustomer), new { id = customer.Id }, customer);
    }
    [HttpGet("{id}")]
    public async Task<IActionResult> GetCustomer(int id)
    {
        var customer = await _customerRepository.GetByIdAsync(id);
        // Verify tenant ownership
        if (customer == null || customer.TenantId != _tenantService.TenantId)
            return NotFound();
        return Ok(customer);
    }
    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateCustomer(int id, [FromBody] UpdateCustomerRequest request)
    {
        var customer = await _customerRepository.GetByIdAsync(id);
        if (customer == null || customer.TenantId != _tenantService.TenantId)
            return NotFound();
        customer.Name = request.Name;
        customer.Email = request.Email;
        customer.UpdatedBy = _tenantService.UserName;
        customer.UpdatedAt = DateTime.UtcNow;
        await _customerRepository.UpdateAsync(customer);
        return Ok(customer);
    }
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteCustomer(int id)
    {
        var customer = await _customerRepository.GetByIdAsync(id);
        if (customer == null || customer.TenantId != _tenantService.TenantId)
            return NotFound();
        await _customerRepository.DeleteAsync(customer);
        return NoContent();
    }
}
Background Job with Tenant Context
Process tenant-specific background jobs:
using ISynergy.Framework.Core.Abstractions.Services;
public class TenantReportGenerator
{
    private readonly ITenantService _tenantService;
    private readonly IReportService _reportService;
    private readonly IEmailService _emailService;
    public TenantReportGenerator(
        ITenantService tenantService,
        IReportService reportService,
        IEmailService emailService)
    {
        _tenantService = tenantService;
        _reportService = reportService;
        _emailService = emailService;
    }
    public async Task GenerateMonthlyReportsAsync()
    {
        var tenants = await GetAllTenantsAsync();
        foreach (var tenant in tenants)
        {
            // Set tenant context for this iteration
            _tenantService.SetTenant(tenant.Id, "system");
            try
            {
                // Generate report (automatically scoped to tenant)
                var report = await _reportService.GenerateMonthlyReportAsync();
                // Send email to tenant administrators
                await _emailService.SendReportAsync(tenant.AdminEmail, report);
            }
            catch (Exception ex)
            {
                // Log error for this tenant
                Console.WriteLine($"Error generating report for tenant {tenant.Id}: {ex.Message}");
            }
        }
    }
}
Integration Testing with Tenant Context
Test multi-tenant features:
using ISynergy.Framework.Core.Abstractions.Services;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public class CustomerControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    public CustomerControllerTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }
    [Fact]
    public async Task GetCustomers_ReturnOnlyTenantCustomers()
    {
        // Arrange
        var tenant1Id = Guid.NewGuid();
        var tenant2Id = Guid.NewGuid();
        var client = _factory.CreateClient();
        // Seed data for two tenants
        await SeedCustomersAsync(tenant1Id, 5);
        await SeedCustomersAsync(tenant2Id, 3);
        // Act - Request with tenant1 context
        var response = await client.GetAsync($"/api/customers");
        // Assert
        response.EnsureSuccessStatusCode();
        var customers = await response.Content.ReadFromJsonAsync<List<Customer>>();
        Assert.Equal(5, customers.Count);
        Assert.All(customers, c => Assert.Equal(tenant1Id, c.TenantId));
    }
    [Fact]
    public async Task CreateCustomer_AssignCurrentTenant()
    {
        // Arrange
        var tenantId = Guid.NewGuid();
        var client = _factory.CreateClient();
        // Act
        var response = await client.PostAsJsonAsync("/api/customers", new
        {
            Name = "Test Customer",
            Email = "test@example.com"
        });
        // Assert
        response.EnsureSuccessStatusCode();
        var customer = await response.Content.ReadFromJsonAsync<Customer>();
        Assert.Equal(tenantId, customer.TenantId);
    }
}
Best Practices
Use global query filters in Entity Framework to automatically scope queries to the current tenant.
Always validate tenant ownership before modifying or deleting entities to prevent cross-tenant data access.
Set the tenant context early in the request pipeline (authentication/authorization middleware) for consistent behavior.
Tenant Resolution
- Resolve tenant from claims when using authentication
 - Use subdomain or custom headers for public APIs
 - Set tenant context in middleware for consistency
 - Cache tenant information to avoid repeated lookups
 - Handle missing or invalid tenant gracefully
 - Log tenant context for audit trails
 
Data Isolation
- Use global query filters in Entity Framework
 - Always validate tenant ownership before operations
 - Implement tenant checks in repository layer
 - Use database schemas or separate databases for strict isolation
 - Audit cross-tenant access attempts
 - Test tenant isolation thoroughly
 
Security
- Never trust tenant ID from client input
 - Always validate tenant from authenticated claims
 - Implement role-based access within tenants
 - Monitor suspicious cross-tenant access patterns
 - Use separate connection strings for high-security tenants
 - Encrypt tenant-specific sensitive data
 
Performance
- Cache tenant configuration and settings
 - Use indexes on TenantId columns
 - Consider database-per-tenant for large tenants
 - Implement tenant-aware caching strategies
 - Monitor query performance across tenants
 - Optimize global query filters
 
Testing
Example unit tests for multi-tenancy:
using ISynergy.Framework.Core.Abstractions.Services;
using Microsoft.AspNetCore.Http;
using Moq;
using System.Security.Claims;
using Xunit;
public class TenantServiceTests
{
    [Fact]
    public void TenantId_WithValidClaim_ReturnsTenantId()
    {
        // Arrange
        var tenantId = Guid.NewGuid();
        var claims = new[]
        {
            new Claim(Claims.KeyId, tenantId.ToString())
        };
        var identity = new ClaimsIdentity(claims);
        var principal = new ClaimsPrincipal(identity);
        var httpContext = new DefaultHttpContext
        {
            User = principal
        };
        var httpContextAccessor = new Mock<IHttpContextAccessor>();
        httpContextAccessor.Setup(a => a.HttpContext).Returns(httpContext);
        var tenantService = new TenantService(httpContextAccessor.Object);
        // Act
        var result = tenantService.TenantId;
        // Assert
        Assert.Equal(tenantId, result);
    }
    [Fact]
    public void SetTenant_SetsClaimsPrincipal()
    {
        // Arrange
        var tenantId = Guid.NewGuid();
        var username = "testuser";
        var httpContext = new DefaultHttpContext();
        var httpContextAccessor = new Mock<IHttpContextAccessor>();
        httpContextAccessor.Setup(a => a.HttpContext).Returns(httpContext);
        var tenantService = new TenantService(httpContextAccessor.Object);
        // Act
        tenantService.SetTenant(tenantId, username);
        // Assert
        Assert.Equal(tenantId.ToString(), httpContext.User.FindFirst(Claims.KeyId)?.Value);
        Assert.Equal(username, httpContext.User.FindFirst(Claims.Username)?.Value);
    }
}
Dependencies
- Microsoft.AspNetCore.Http - HTTP context abstractions
 - OpenIddict.Abstractions - OAuth 2.0/OpenID Connect claims
 - ISynergy.Framework.Core - Core framework utilities
 
Documentation
For more information about the I-Synergy Framework:
Related Packages
- I-Synergy.Framework.Core - Core framework components
 - I-Synergy.Framework.AspNetCore - Base ASP.NET Core integration
 - I-Synergy.Framework.AspNetCore.Authentication - Authentication utilities
 - I-Synergy.Framework.EntityFramework - Entity Framework with tenant support
 - I-Synergy.Framework.AspNetCore.Monitoring - SignalR monitoring with tenant isolation
 
Support
For issues, questions, or contributions, please visit the GitHub repository.
| 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
- I-Synergy.Framework.AspNetCore.Authentication (>= 2025.11102.11228.52-preview)
 
 
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.