Learn to build scalable multi-tenant SaaS apps with ASP.NET Core. Explore data isolation, tenant resolution, security, and deployment strategies with code examples.
Learn to build scalable multi-tenant SaaS apps with ASP.NET Core. Explore data isolation, tenant resolution, security, and deployment strategies with code examples.
Multi-tenant Software-as-a-Service (SaaS) applications are designed to serve multiple customers (tenants) from a single codebase and infrastructure, while keeping their data and configurations isolated. This architecture optimizes resource usage, simplifies maintenance, and scales efficiently—making it a cornerstone of modern SaaS platforms like Salesforce, Shopify, and HubSpot.
In this guide, we’ll explore how to build a robust multi-tenant SaaS application using ASP.NET Core, covering architectural patterns, data isolation, security, scaling, and deployment. Whether you’re launching a B2B platform or a customer-facing tool, this guide provides actionable insights to navigate the complexities of multi-tenancy.
Multi-tenancy allows a single application instance to serve multiple tenants, each with isolated data, configurations, and user management. Key benefits include:
Cost Efficiency: Shared infrastructure reduces operational expenses.
Simplified Updates: Roll out features to all tenants simultaneously.
Scalability: Add tenants without spinning up new instances.
Model | Description | Pros | Cons |
---|---|---|---|
Database per Tenant | Separate database for each tenant. | Strong data isolation. | High operational overhead. |
Shared Database, Separate Schema | Single database with tenant-specific schemas (e.g., tenant1.orders). | Moderate isolation. | Complex migrations. |
Shared Database, Shared Schema | Single database/schema with tenant IDs segregating data (e.g., TenantId column). | Low cost, simple setup. | Risk of data leakage. |
Recommendation: Start with a Shared Database, Shared Schema for simplicity, then evolve as needed.
Organize your solution to support tenant-aware services:
MySaaSApp/ ├─ Tenants/ │ ├─ Tenant.cs # Tenant model │ ├─ ITenantService.cs # Tenant resolution logic ├─ Data/ │ ├─ AppDbContext.cs # EF Core DbContext with tenant filtering ├─ Middleware/ │ ├─ TenantMiddleware.cs # Resolves tenant from request
Subdomain: tenant1.your-app.com
Request Header: X-Tenant-Id: abc123
JWT Claim: "tenant": "tenant1"
Path Parameter: /api/tenant1/orders
Example: Tenant Middleware
public class TenantMiddleware { private readonly RequestDelegate _next; public TenantMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, ITenantService tenantService) { // Extract tenant ID from subdomain (e.g., "tenant1.your-app.com") var subdomain = context.Request.Host.Host.Split('.')[0]; var tenant = await tenantService.GetTenantByIdAsync(subdomain); if (tenant == null) { context.Response.StatusCode = 404; await context.Response.WriteAsync("Tenant not found"); return; } // Store tenant in HttpContext for downstream use context.Items["Tenant"] = tenant; await _next(context); } } // Register middleware in Program.cs: app.UseMiddleware<TenantMiddleware>();
Add tenant-specific filtering to EF Core models:
Use separate migrations for shared and tenant-specific schemas:
dotnet ef migrations AddSharedSchema dotnet ef migrations AddTenantSchema --context TenantDbContext
ASP.NET Core Identity: Extend IdentityUser with a TenantId property.
Roles & Claims: Assign tenant-specific roles (e.g., TenantAdmin).
Example: Tenant User Model
public class AppUser : IdentityUser { public string TenantId { get; set; } }
public class AppUser : IdentityUser { public string TenantId { get; set; } // Link users to tenants } // Policy-based authorization: services.AddAuthorization(options => { options.AddPolicy("TenantAdmin", policy => policy.RequireClaim("TenantId", context.Tenant.Id) .RequireRole("Admin")); });
Use Redis to cache tenant configurations:
// Register Redis in Program.cs: services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; }); // Tenant service with caching: public class TenantService { private readonly IDistributedCache _cache; private readonly AppDbContext _context; public TenantService(IDistributedCache cache, AppDbContext context) { _cache = cache; _context = context; } public async Task<Tenant> GetTenantAsync(string tenantId) { var cachedTenant = await _cache.GetStringAsync($"tenant_{tenantId}"); if (!string.IsNullOrEmpty(cachedTenant)) { return JsonSerializer.Deserialize<Tenant>(cachedTenant); } var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId); await _cache.SetStringAsync($"tenant_{tenantId}", JsonSerializer.Serialize(tenant), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }); return tenant; } }
Use libraries like ShardingCore to distribute tenants across databases.
Data Encryption: Encrypt sensitive fields (e.g., TenantId) using AES or Azure Key Vault.
Row-Level Security: Implement via SQL Server or PostgreSQL policies.
Audit Logs: Track tenant activity with tools like Audit.NET.
Configure Serilog to include TenantId in logs:
// Configure Serilog in Program.cs: Log.Logger = new LoggerConfiguration() .Enrich.WithProperty("TenantId", tenantContext.TenantId) .WriteTo.Console() .CreateLogger(); // Inject ILogger in services: public class OrderService { private readonly ILogger<OrderService> _logger; private readonly ITenantContext _tenantContext; public OrderService(ILogger<OrderService> logger, ITenantContext tenantContext) { _logger = logger; _tenantContext = tenantContext; } public void ProcessOrder() { _logger.LogInformation("Processing order for tenant {TenantId}", _tenantContext.TenantId); } }
Building multi-tenant SaaS applications with ASP.NET Core requires careful planning around data isolation, tenant resolution, and scalability. By leveraging EF Core, middleware, and modern cloud tools, you can create a secure, efficient platform ready to scale with your user base.
🔗 Website: Brick SaaS Starter Kit
Brick SaaS Starter Kit helps early-stage SaaS Founders save time and money by providing ready-to-use features that comes with the most common must-have features. You can consider this Angular SaaS Boilerplate or React SaaS Boilerplate as Brick has options to use Angular or React in front end while using asp.net core as back end APIs.
Here is the feature list: