diff --git a/common.props b/common.props index 0f5b44bce2..8e25ef7520 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 10.5.0-rc.2 - 5.5.0-rc.2 + 10.6.0-preview + 5.6.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Community-Articles/2026-06-02-how-to-implement-multitenancy-in-abp/Post.md b/docs/en/Community-Articles/2026-06-02-how-to-implement-multitenancy-in-abp/Post.md new file mode 100644 index 0000000000..ae8aae7dbd --- /dev/null +++ b/docs/en/Community-Articles/2026-06-02-how-to-implement-multitenancy-in-abp/Post.md @@ -0,0 +1,148 @@ +Multi-tenancy sounds simple until the first data leak, migration issue, or noisy-neighbor incident shows up. The hard part is not adding a `TenantId` column. The hard part is enforcing isolation consistently across authentication, data access, caching, logging, and operations. + +If you are building with ABP, the good news is that multi-tenancy is already a first-class concept. You still need to choose the right architecture and apply a few guardrails, but ABP removes much of the plumbing. + +## Start with the right tenancy model + +There are three common approaches: + +### 1. Shared database, shared schema +All tenants use the same tables, usually separated by a `TenantId` column. + +Use this when: +- You want the simplest setup +- Cost matters more than strict isolation +- You expect many small tenants + +Watch out for: +- Missing tenant filters +- Noisy-neighbor performance issues +- Harder compliance stories + +### 2. Shared database, separate schemas +Each tenant gets its own schema in the same database. + +Use this when: +- You need better isolation than shared tables +- Your database supports schema-based separation well + +Watch out for: +- More complex migrations +- Operational overhead as tenant count grows + +### 3. Database per tenant +Each tenant gets its own database. + +Use this when: +- You need strong isolation +- Compliance or data residency matters +- Some tenants pay for premium isolation + +Watch out for: +- Higher cost +- More deployment and migration work +- More monitoring complexity + +In practice, many SaaS products start with shared schema and move large tenants to dedicated databases later. + +## How ABP helps + +ABP has built-in multi-tenancy support based on the current tenant context. The usual flow is: + +- Resolve the current tenant from subdomain, domain, header, or user context +- Store tenant information in the current unit of work +- Automatically filter tenant-owned entities +- Optionally use separate connection strings per tenant + +For tenant-owned entities, implement ABP's multi-tenant contract so the framework can apply filtering automatically. + +```csharp +public class Product : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } + public string Name { get; set; } + + protected Product() {} + + public Product(Guid id, string name, Guid? tenantId) + : base(id) + { + Name = name; + TenantId = tenantId; + } +} +``` + +That gives you a strong default for shared-schema applications. + +## Tenant resolution and authentication + +A multi-tenant app must know who the tenant is before it touches business data. + +Common strategies: +- Subdomain: `tenant1.myapp.com` +- Custom domain: `app.customer.com` +- Header: useful for APIs +- Login-time selection: common in back-office apps + +In ABP, tenant resolution can be configured through contributors. Keep it predictable and avoid mixing too many strategies unless you really need them. + +Also make sure: +- Tenant ID is included securely in claims when appropriate +- Authorization is scoped per tenant +- Admin users cannot accidentally cross tenant boundaries + +## Data isolation rules that matter + +No matter which model you choose, these rules are worth treating as non-negotiable: + +- Always propagate tenant context through application and background jobs +- Include tenant ID in logs and traces +- Index `TenantId` in shared-schema tables +- Add rate limits or quotas for heavy tenants +- Encrypt data at rest and in transit + +If you use shared schema, database-level protections such as row-level security can add another safety net. Application filters are good. Defense in depth is better. + +## Separate database per tenant in ABP + +ABP also supports tenant-specific connection strings. That makes database-per-tenant possible without rewriting your application model. + +Typical setup: +- Keep host data in the main database +- Store tenant connection strings in tenant management +- Resolve the current tenant +- Let ABP switch to the tenant database automatically + +This is a good fit when you want one codebase but stronger tenant isolation. + +## When to use and when not to use + +### Use multi-tenancy when +- You are building a SaaS product for multiple customers +- Tenants need isolated users, roles, and data +- You want one platform with centralized deployment + +### Do not use it when +- You only have one customer environment +- Every customer requires fully custom code and infrastructure +- Your team is not ready to operate tenant-aware monitoring, migrations, and support + +## Common mistakes + +A few mistakes show up repeatedly: + +- Adding multi-tenancy too late +- Relying only on UI-level filtering +- Writing tenant-specific `if` statements everywhere instead of configuration or features +- Forgetting tenant context in background workers and integration events +- Having no migration path from shared to dedicated databases + +ABP helps with the framework side, but architecture decisions are still yours. + +## TL;DR + +- Start with shared schema unless compliance or isolation requirements push you further. +- In ABP, use `IMultiTenant`, tenant resolution, and built-in data filters. +- For stronger isolation, use tenant-specific connection strings and move to database per tenant. +- Always treat tenant context, logging, indexing, and authorization as core design concerns. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-02-how-to-implement-multitenancy-in-abp/cover.png b/docs/en/Community-Articles/2026-06-02-how-to-implement-multitenancy-in-abp/cover.png new file mode 100644 index 0000000000..0987177bf4 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-02-how-to-implement-multitenancy-in-abp/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/Post.md b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/Post.md new file mode 100644 index 0000000000..8dcde9d11a --- /dev/null +++ b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/Post.md @@ -0,0 +1,581 @@ +Multi-tenancy sounds simple at first: one application, many customers. In practice, it changes how you design data access, authentication, configuration, monitoring, migrations, and even support workflows. + +If you get it right, you can serve many organizations efficiently from one platform. If you get it wrong, you risk the worst kind of bug in SaaS: one tenant seeing another tenant's data. + +This article explains how to implement multi-tenancy in a practical way, starting from the core patterns and ending with an ABP-based implementation approach. The goal is not just to define multi-tenancy, but to help you choose the right model and avoid the mistakes that usually show up after launch. + +## What multi-tenancy actually means + +A tenant is usually a customer organization, company, school, or business unit using your application. Multi-tenancy means multiple tenants use the same application platform while remaining isolated from each other. + +That isolation can include: + +- Data isolation +- Authentication and authorization boundaries +- Feature differences per tenant +- Performance protection +- Audit and compliance boundaries + +It is useful to separate two ideas that are often mixed up: + +- **Multi-tenant application**: one application instance serves many tenants +- **Multi-instance deployment**: each customer gets a separate deployment + +Multi-instance is simpler from an isolation perspective, but more expensive to operate. Multi-tenancy is usually more efficient, but it requires stronger architectural discipline. + +## Choose your data isolation model first + +Most multi-tenancy decisions become easier once you choose the data isolation model. There are three common patterns. + +### 1. Shared database, shared schema + +This is the most common starting point. All tenants use the same tables, and each tenant-owned row includes a `TenantId`. + +Example: + +```sql +Orders +- Id +- TenantId +- CustomerId +- TotalAmount +- CreationTime +``` + +Every query must be tenant-aware. + +**Pros** + +- Lowest infrastructure cost +- Simplest provisioning +- One migration path +- Fastest way to launch + +**Cons** + +- Highest risk of accidental data leakage if tenant filtering is missed +- Noisy neighbor problems are more likely +- Harder to isolate performance-heavy tenants +- Compliance requirements may rule it out + +**Best for** + +- MVPs +- Early-stage SaaS products +- Products with many small tenants +- Teams that want operational simplicity first + +### 2. Shared database, separate schema per tenant + +In this model, tenants share the same database server, but each tenant has its own schema. + +For example: + +- `tenant_a.Orders` +- `tenant_b.Orders` + +**Pros** + +- Better separation than shared schema +- Easier tenant-specific backup and restore +- Lower leakage risk than row-level isolation alone + +**Cons** + +- Migrations become harder +- Schema drift becomes a real risk +- Managing hundreds of schemas gets messy +- Connection and routing logic becomes more complex + +**Best for** + +- Moderate compliance needs +- Tens to a few hundreds of tenants +- Teams that need more isolation without going full database-per-tenant + +### 3. Database per tenant + +Each tenant gets its own database, and sometimes even its own server. + +**Pros** + +- Strongest isolation +- Easier compliance story +- Easier tenant-specific backup, restore, and residency rules +- Better performance isolation +- Easier to support premium tenants with custom SLAs + +**Cons** + +- Higher cost +- More provisioning automation required +- More complex migrations and monitoring +- Operational overhead grows quickly + +**Best for** + +- Enterprise SaaS +- Regulated industries +- High-value tenants +- Cases where data residency or strict isolation matters + +## How to choose the right pattern + +There is no universally correct model. The right choice depends on trade-offs. + +Use these questions to decide: + +- How many tenants do you expect in 6 to 24 months? +- Do you have compliance requirements like HIPAA, GDPR residency constraints, or strict audit demands? +- How much tenant customization do you need? +- Can your team operate many databases reliably? +- What happens if one tenant runs expensive reports all day? +- Do you need tenant-specific backup and restore? + +A practical rule: + +- Start with **shared schema** if speed and cost matter most +- Use **database per tenant** if isolation and compliance matter most +- Use a **hybrid model** if your tenant base is mixed + +A hybrid model is common in real SaaS systems: + +- Small tenants live in shared infrastructure +- Large enterprise tenants get dedicated databases + +That gives you a better cost curve without forcing every customer into the most expensive setup. + +## The core building blocks of a multi-tenant system + +Regardless of the storage model, most implementations need the same building blocks. + +### Tenant resolution + +Before your application can isolate anything, it must know which tenant the request belongs to. + +Common tenant resolution strategies: + +- Subdomain: `acme.yourapp.com` +- Custom domain: `portal.acme.com` +- Header: `X-Tenant-Id` +- JWT claim or token metadata +- URL segment: `/t/acme/orders` + +Subdomain-based resolution is usually the cleanest for SaaS products. Header-based resolution is common for internal APIs but easier to misuse if not protected. + +The important part is consistency. Resolve the tenant early in the request pipeline and make it available everywhere else. + +### Tenant context + +Once resolved, store the active tenant in a tenant context that application services, repositories, caches, and logs can access. + +Typical tenant context data includes: + +- Tenant ID +- Tenant name or slug +- Connection string or database mapping +- Enabled features +- Region or residency info + +### Data filtering + +If you use shared tables, tenant filtering must be automatic. Do not rely on developers remembering to add `where TenantId == currentTenantId` in every query. + +This is where frameworks matter. ABP helps a lot here because it has built-in multi-tenancy support and data filters for tenant-aware entities. + +### Tenant-aware configuration + +Sooner or later, tenants will ask for differences: + +- Feature A enabled only for premium plans +- Different email templates +- Different password policies +- Different integrations +- Different branding + +Do not solve this with `if (tenant == ...)` scattered across the codebase. + +Use: + +- Feature flags +- Tenant settings +- Edition or plan-based configuration +- Modular integration points + +### Tenant-aware logging and auditing + +Every log entry and audit event should include tenant context where possible. + +That makes it much easier to answer questions like: + +- Which tenant triggered this error? +- Which tenant is causing high database load? +- Who accessed this record? +- Which tenant had failed background jobs? + +## Implementing multi-tenancy in ASP.NET Core + +If you build multi-tenancy manually in ASP.NET Core, the implementation usually follows this flow: + +1. Resolve tenant from the request +2. Load tenant metadata from a tenant store +3. Set current tenant context +4. Route database access based on tenant +5. Apply tenant filters automatically +6. Include tenant info in logs, cache keys, and background jobs + +A minimal tenant resolver middleware might look like this: + +```csharp +public class TenantResolutionMiddleware +{ + private readonly RequestDelegate _next; + + public TenantResolutionMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context, ICurrentTenantAccessor tenantAccessor) + { + var host = context.Request.Host.Host; + var subdomain = host.Split('.').FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(subdomain) && subdomain != "www") + { + tenantAccessor.CurrentTenantId = await ResolveTenantIdAsync(subdomain); + } + + await _next(context); + } + + private Task ResolveTenantIdAsync(string subdomain) + { + return Task.FromResult(null); + } +} +``` + +That is only the beginning. The hard part is making the rest of the application consistently tenant-aware. + +For example, your repository layer must avoid this kind of bug: + +```csharp +var orders = await _dbContext.Orders + .Where(x => x.Status == OrderStatus.Pending) + .ToListAsync(); +``` + +In a shared-schema model, that query is dangerous because it ignores tenant isolation. + +Safer approaches include: + +- Global query filters +- Tenant-aware repositories +- Row-level security at the database level +- Framework-level multi-tenancy abstractions + +## Implementing multi-tenancy with ABP + +ABP is a strong fit for multi-tenant business applications because multi-tenancy is built into the framework rather than bolted on later. + +ABP gives you several useful pieces out of the box: + +- Tenant resolution pipeline +- `ICurrentTenant` abstraction +- Multi-tenant entity support via `IMultiTenant` +- Data filters for tenant isolation +- Tenant management module +- Per-tenant connection string support +- Feature and setting systems + +### Tenant-aware entities + +In ABP, tenant-owned entities typically implement `IMultiTenant`. + +```csharp +using System; +using Volo.Abp.MultiTenancy; + +public class Product : IMultiTenant +{ + public Guid Id { get; set; } + public Guid? TenantId { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } +} +``` + +This matters because ABP can automatically apply tenant filters for entities that belong to a tenant. + +### Accessing the current tenant + +ABP exposes the current tenant through `ICurrentTenant`. + +```csharp +public class ProductAppService : ApplicationService +{ + private readonly IRepository _productRepository; + private readonly ICurrentTenant _currentTenant; + + public ProductAppService( + IRepository productRepository, + ICurrentTenant currentTenant) + { + _productRepository = productRepository; + _currentTenant = currentTenant; + } + + public async Task> GetListAsync() + { + var products = await _productRepository.GetListAsync(); + return ObjectMapper.Map, List>(products); + } +} +``` + +In a tenant context, ABP automatically scopes the repository query to the current tenant for multi-tenant entities. + +That is exactly the kind of default you want in a multi-tenant system: safe behavior unless you intentionally opt out. + +### Switching tenant context + +Background jobs, admin tools, and migration workflows sometimes need to operate in a specific tenant context. + +ABP supports this with `ICurrentTenant.Change(...)`. + +```csharp +using (_currentTenant.Change(tenantId)) +{ + var count = await _productRepository.GetCountAsync(); +} +``` + +This is useful, but it should be used carefully. Tenant context switching is powerful and easy to abuse if you do not keep boundaries clear. + +### Per-tenant connection strings + +If you choose database-per-tenant, ABP supports tenant-specific connection strings. That lets you keep the same application code while routing different tenants to different databases. + +This is one of the biggest practical advantages of using ABP for multi-tenancy: you can start simple and evolve toward stronger isolation without rewriting your entire application model. + +## Shared schema vs database per tenant in ABP + +ABP supports both host and tenant concepts, which makes it flexible enough for different SaaS stages. + +### Shared schema with ABP + +This is usually the easiest starting point. + +Use it when: + +- You want fast delivery +- Your tenants are relatively small +- Compliance requirements are moderate +- Your team wants simpler operations + +What to watch: + +- Ensure all tenant-owned entities implement `IMultiTenant` +- Be careful with raw SQL and custom queries +- Include `TenantId` in indexes where needed +- Test cross-tenant isolation aggressively + +### Database per tenant with ABP + +Use it when: + +- You need stronger isolation +- You have enterprise customers +- You need tenant-specific restore or residency +- You expect some tenants to be much larger than others + +What to watch: + +- Automate provisioning +- Automate migrations across tenant databases +- Monitor connection usage and migration failures +- Keep tenant metadata accurate and centralized + +## Migrations and schema management + +This is where many multi-tenant systems become painful. + +With shared schema, migrations are straightforward: apply once. + +With schema-per-tenant or database-per-tenant, you need orchestration. + +A practical migration workflow includes: + +- Track tenant inventory centrally +- Apply migrations in batches +- Record migration status per tenant +- Retry safely on failure +- Alert on drift +- Avoid manual one-off fixes unless documented and automated later + +If you have 5 tenants, manual migration might feel acceptable. If you have 500, it becomes an operational risk. + +For ABP-based systems, treat tenant database migration as a first-class operational workflow, not an afterthought. + +## Security pitfalls to avoid + +Multi-tenancy failures are usually not caused by the concept itself. They are caused by inconsistent enforcement. + +The most common mistakes are: + +### 1. Missing tenant filters + +This is the classic bug. One query forgets tenant scoping, and data leaks. + +Reduce the risk with: + +- Framework-level filters +- Repository abstractions +- Database row-level security where appropriate +- Integration tests that verify isolation + +### 2. Unsafe admin features + +Support tools, exports, reporting endpoints, and background jobs often bypass normal application flows. That makes them common leakage points. + +Treat admin code as high-risk code. + +### 3. Tenant context lost in async or background processing + +If a background job processes tenant data, it must explicitly carry tenant context. + +Do not assume the request context still exists. + +### 4. Shared cache keys + +If your cache key is just `product-list`, you already have a bug. + +Use tenant-aware cache keys such as: + +```text +tenant:{tenantId}:product-list +``` + +### 5. Weak audit trails + +When something goes wrong, you need to know: + +- Which tenant was affected +- Which user triggered the action +- Which service handled it +- Which data store was involved + +Without tenant-aware auditing, incident response becomes much harder. + +## Performance and noisy neighbor control + +Shared infrastructure saves money, but it also creates contention. + +A single tenant can hurt others through: + +- Expensive reports +- Large imports +- Chatty integrations +- Poorly indexed queries +- Background jobs running at the wrong time + +Mitigations include: + +- Indexing by `TenantId` +- Query timeouts +- Rate limiting per tenant +- Queue isolation for heavy jobs +- Read replicas for reporting +- Partitioning large tables +- Moving heavy tenants to dedicated databases + +This is why hybrid multi-tenancy is so common. It gives you an escape hatch when one tenant outgrows the shared model. + +## A practical implementation plan + +If you are building a new SaaS product, this is a sensible rollout path. + +### Phase 1: Start with shared schema + +- Resolve tenant from subdomain or domain +- Store tenant metadata centrally +- Use framework-level tenant filters +- Make all tenant-owned entities explicit +- Add tenant-aware logging, caching, and auditing + +For ABP, this is usually the fastest path because the framework already supports the core abstractions. + +### Phase 2: Add tenant configuration and feature management + +- Per-tenant settings +- Feature flags +- Plan-based capabilities +- Branding and integration settings + +This keeps customization manageable without branching the codebase. + +### Phase 3: Prepare for tenant mobility + +Even if you start with shared schema, design for future migration. + +That means: + +- Stable tenant IDs +- Export/import or replication strategy +- Clear ownership boundaries in data +- No hidden cross-tenant joins + +### Phase 4: Move selected tenants to dedicated databases + +Use an expand-backfill-contract approach: + +- **Expand**: create the target database +- **Backfill**: copy tenant data +- **Dual-write**: temporarily write to both stores if needed +- **Contract**: switch traffic and retire the old location + +This is much safer than a big-bang migration. + +## When to use multi-tenancy and when not to + +### When to use multi-tenancy + +- You are building a SaaS platform for many organizations +- Tenants share most application behavior +- Operational efficiency matters +- You want centralized upgrades and deployment +- You need a scalable commercial model + +### When NOT to use multi-tenancy + +- Every customer needs deep infrastructure-level customization +- Compliance requires strict physical isolation from day one +- Your team cannot support the operational complexity safely +- You only have a few large customers and each behaves like a separate product + +In those cases, multi-instance deployment may be the better choice. + +## Final recommendations + +If you are unsure where to start, start simpler than you think, but not sloppier than you can afford. + +That usually means: + +- Shared schema first +- Strong tenant resolution +- Automatic tenant filtering +- Tenant-aware logs, cache, and jobs +- A migration path to dedicated databases later + +ABP is especially useful here because it gives you the right primitives early: current tenant context, tenant-aware entities, filters, settings, and connection string support. That reduces the amount of custom plumbing you need to build and maintain. + +The biggest mistake is not choosing the wrong pattern. It is choosing a pattern without planning how tenant isolation will be enforced everywhere. + +## TL;DR + +- Multi-tenancy is mostly about safe isolation of data, behavior, and operations across customers. +- Shared schema is the easiest starting point; database-per-tenant gives the strongest isolation but adds operational cost. +- In ASP.NET Core, tenant resolution, tenant context, automatic filtering, and tenant-aware caching/logging are essential. +- ABP makes multi-tenancy easier with `ICurrentTenant`, `IMultiTenant`, data filters, tenant management, and per-tenant connection strings. +- Design for evolution early so large tenants can move to dedicated databases without a painful rewrite. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/cover.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/cover.png new file mode 100644 index 0000000000..2fde29d221 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-1.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-1.png new file mode 100644 index 0000000000..47af7d55fd Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-1.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-2.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-2.png new file mode 100644 index 0000000000..a3aebbdbf7 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-2.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-3.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-3.png new file mode 100644 index 0000000000..0f96c0835a Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-3.png differ diff --git a/latest-versions.json b/latest-versions.json index ccd634c25c..5b6987a34f 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,13 @@ [ + { + "version": "10.4.1", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "5.4.1" + } + }, { "version": "10.4.0", "releaseDate": "",