Introduction
As Software-as-a-Service (SaaS) continues to dominate the tech landscape, building applications that serve multiple customers (tenants) efficiently from a single codebase becomes essential. ASP.NET Core, with its modular and scalable architecture, is a powerful framework for implementing multi-tenant SaaS applications.
This article will guide you through core principles, patterns, and a practical approach to building a multi-tenant ASP.NET Core app.
What is Multi-Tenancy?
In a multi-tenant architecture, a single application instance serves multiple tenants, with each tenant having isolated data and configurations. There are three common multi-tenancy models.
- Single Database, Shared Schema: All tenants share the same database and tables. Tenant data is filtered per request.
- Single Database, Separate Schema per Tenant: One database, but each tenant has its own schema.
- Separate Database per Tenant: Each tenant has a completely separate database. Most secure, but also complex.
Core Components of a Multi-Tenant ASP.NET Core App
1. Tenant Identification Middleware
You must identify the tenant from the request, commonly through.
- Subdomain (e.g., tenant1.app.com)
- Header (e.g., X-Tenant-ID)
- URL path (e.g., /tenant1/dashboard)
public class TenantResolutionMiddleware
{
private readonly RequestDelegate _next;
public TenantResolutionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ITenantService tenantService)
{
var tenantId = context.Request.Headers["X-Tenant-ID"].FirstOrDefault();
if (string.IsNullOrEmpty(tenantId))
{
context.Response.StatusCode = 400; // Bad Request
await context.Response.WriteAsync("Tenant ID missing.");
return;
}
await tenantService.SetCurrentTenantAsync(tenantId);
await _next(context);
}
}
2. Tenant Context & Service
Create a scoped service to store tenant information per request.
public interface ITenantService
{
TenantInfo CurrentTenant { get; }
Task SetCurrentTenantAsync(string tenantId);
}
public class TenantService : ITenantService
{
public TenantInfo CurrentTenant { get; private set; }
public Task SetCurrentTenantAsync(string tenantId)
{
// Fetch tenant info from a DB or config
CurrentTenant = new TenantInfo { Id = tenantId, Name = $"Tenant {tenantId}" };
return Task.CompletedTask;
}
}
3. Data Isolation Using EF Core
Use a global query filter in Entity Framework Core to isolate data.
public class AppDbContext : DbContext
{
private readonly ITenantService _tenantService;
public AppDbContext(DbContextOptions<AppDbContext> options, ITenantService tenantService)
: base(options)
{
_tenantService = tenantService;
}
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var tenantId = _tenantService.CurrentTenant?.Id;
modelBuilder.Entity<Order>().HasQueryFilter(o => o.TenantId == tenantId);
}
}
Managing Per-Tenant Configuration
Store configurations like feature toggles, limits, or branding in a TenantSettings table, and cache them using a memory cache or Redis.
public class TenantInfo
{
public string Id { get; set; }
public string Name { get; set; }
public string ThemeColor { get; set; }
public string DbConnectionString { get; set; } // for DB-per-tenant model
}
Tenant-Based Dependency Injection
ASP.NET Core allows scoped services per request; use it to register tenant-specific services dynamically if needed.
Authentication & Authorization
Use IdentityServer or JWT tokens that carry the tenant ID. This ensures that tokens are scoped to a particular tenant and can’t be used across them.
Testing and Debugging Multi-Tenant Logic
- Use integration tests to simulate tenant-specific scenarios.
- Enable verbose logging during tenant resolution.
- Protect tenant boundaries using middleware and filters.
Best Practices
- Isolate tenant data at all layers, including caching and messaging.
- Use caching wisely, with tenant-scoped keys.
- Implement strict authorization and rate limits per tenant.
- Monitor and log tenant-specific performance metrics.
Conclusion
Building a multi-tenant SaaS app in ASP.NET Core isn’t just about shared data. It’s about smart architecture, secure isolation, and tenant-aware design patterns. By combining middleware, scoped services, EF Core filters, and modern deployment practices, you can scale your SaaS app with confidence.
Happy coding!