From 56af8031fdeea52f7f765e1a29c5871de28e98f3 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Tue, 22 Sep 2020 17:16:45 +0300 Subject: [PATCH 1/8] Fix: Suite Update --preview doesn't work as intended --- .../Volo/Abp/Cli/NuGet/NuGetService.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs index 79f54eb8e2..1ecfc9e34d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs @@ -75,16 +75,35 @@ namespace Volo.Abp.Cli.NuGet var responseContent = await responseMessage.Content.ReadAsStringAsync(); - var versions = JsonSerializer + List versions; + + if (!includeNightly && !includeReleaseCandidates) + { + versions = JsonSerializer .Deserialize(responseContent) .Versions .Select(SemanticVersion.Parse) .OrderByDescending(v=> v, new VersionComparer()).ToList(); - if (!includeNightly && !includeReleaseCandidates) - { versions = versions.Where(x => !x.IsPrerelease).ToList(); } + else if (!includeNightly && includeReleaseCandidates) + { + versions = JsonSerializer + .Deserialize(responseContent) + .Versions + .Where(v=> !v.Contains("-preview")) + .Select(SemanticVersion.Parse) + .OrderByDescending(v=> v, new VersionComparer()).ToList(); + } + else + { + versions = JsonSerializer + .Deserialize(responseContent) + .Versions + .Select(SemanticVersion.Parse) + .OrderByDescending(v=> v, new VersionComparer()).ToList(); + } return versions.Any() ? versions.Max() : null; } From 8c679d4d6b7d7cb96365720824559bc100b34b3b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 22 Sep 2020 19:36:18 +0300 Subject: [PATCH 2/8] docs: update deployment part in Angular quick start --- docs/en/UI/Angular/Quick-Start.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/en/UI/Angular/Quick-Start.md b/docs/en/UI/Angular/Quick-Start.md index c3cbc90b25..e054b522ea 100644 --- a/docs/en/UI/Angular/Quick-Start.md +++ b/docs/en/UI/Angular/Quick-Start.md @@ -189,9 +189,9 @@ This command will download and start a simple static server, a browser window at Of course, you need your application to run on an optimized web server and become available to everyone. This is quite straight-forward: -1. Create a new static web server instance. You can use a service like [Azure App Service](https://azure.microsoft.com/tr-tr/services/app-service/web/), [Firebase](https://firebase.google.com/docs/hosting), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), or even [GitHub Pages](https://angular.io/guide/deployment#deploy-to-github-pages). You can also build a custom static web server. Using [NGINX](https://www.nginx.com/) in this case might be a good idea. -2. Copy the files to the server (by CLI of the service provider, over SSH or FTP if not available). -3. [Configure the server](https://angular.io/guide/deployment#server-configuration) to redirect all requests to the _index.html_ file. Some services do that automatically. +1. Create a new static web server instance. You can use a service like [Azure App Service](https://azure.microsoft.com/tr-tr/services/app-service/web/), [Firebase](https://firebase.google.com/docs/hosting), [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), or even [GitHub Pages](https://angular.io/guide/deployment#deploy-to-github-pages). Another option is maintaining own web server with [NGINX](https://www.nginx.com/), [IIS](https://www.iis.net/), [Apache HTTP Server](https://httpd.apache.org/), or equivalent. +2. Copy the files from `dist/MyProjectName` [1](#f-dist-folder-name) to a publicly served destination on the server via CLI of the service provider, SSH, or FTP (whichever is available). This step would be defined as a job if you have a CI/CD flow. +3. [Configure the server](https://angular.io/guide/deployment#server-configuration) to redirect all requests to the _index.html_ file. Some services do that automatically. Others require you [to add a file to the bundle via assets](https://angular.io/guide/workspace-config#assets-configuration) which describes the server how to do the redirections. Occasionally, you may need to do manual configuration. In addition, you can [deploy your application to certain targets using the Angular CLI](https://angular.io/guide/deployment#automatic-deployment-with-the-cli). Here are some deploy targets: @@ -201,6 +201,12 @@ In addition, you can [deploy your application to certain targets using the Angul - [Vercel](https://github.com/vercel/ng-deploy-vercel#readme) - [GitHub Pages](https://github.com/angular-schule/angular-cli-ghpages/#readme) +--- + +1 _The compiled output will be placed under `/dist` in a folder by the project name._ [↩](#a-dist-folder-name) + +--- + ## What's Next? - [Environment](./Environment.md) From 7fdd4f08ebfe290304ff6be59391fa49e7999ac6 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 23 Sep 2020 11:14:33 +0300 Subject: [PATCH 3/8] docs: add Angular quick start to navigaton --- docs/en/docs-nav.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 0273030308..b395bf67f0 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -431,6 +431,10 @@ "text": "Migration Guide v2.x to v3", "path": "UI/Angular/Migration-Guide-v3.md" }, + { + "text": "Quick Start", + "path": "UI/Angular/Quick-Start.md" + }, { "text": "Environment", "path": "UI/Angular/Environment.md" From feb0e16ba925c65e4e556a43bff40b2435042819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 23 Sep 2020 11:34:17 +0300 Subject: [PATCH 4/8] Revised the multi-tenancy doc. --- docs/en/Modules/Identity.md | 2 +- docs/en/Multi-Tenancy.md | 313 +++++++++++++++++++++++------------- 2 files changed, 199 insertions(+), 116 deletions(-) diff --git a/docs/en/Modules/Identity.md b/docs/en/Modules/Identity.md index ac86b13564..b38f59c03b 100644 --- a/docs/en/Modules/Identity.md +++ b/docs/en/Modules/Identity.md @@ -2,7 +2,7 @@ Identity module is used to manage [organization units](Organization-Units.md), roles, users and their permissions, based on the Microsoft Identity library. -See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon... +**See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...** ## Identity Security Log diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index c087c6b8f2..077db1e272 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -1,148 +1,271 @@ -## Multi-Tenancy +# Multi-Tenancy -ABP Multi-tenancy module provides base functionality to create multi tenant applications. +Multi-Tenancy is a widely used architecture to create **SaaS applications** where the hardware and software **resources are shared by the customers** (tenants). ABP Framework provides all the base functionalities to create **multi tenant applications**. -Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) multi-tenancy as like that: +Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenancy as like that: > Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants. -### Volo.Abp.MultiTenancy Package +## Terminology: Host vs Tenant -Volo.Abp.MultiTenancy package defines fundamental interfaces to make your code "multi-tenancy ready". So, install it to your project using the package manager console (PMC): +There are two main side of a typical SaaS / Multi-tenant application: -```` -Install-Package Volo.Abp.MultiTenancy -```` +* A **Tenant** is a customer of the SaaS application that pays money to use the service. +* **Host** is the company that owns the SaaS application and manages the system. -> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. +We will use the Host and Tenant terms for that purpose in the rest of the document. -Then you can add **AbpMultiTenancyModule** dependency to your module: +## IMultiTenant -````C# -using Volo.Abp.Modularity; +You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**. + +**Example: A multi-tenant *Product* entity** + +````csharp +using System; +using Volo.Abp.Domain.Entities; using Volo.Abp.MultiTenancy; -namespace MyCompany.MyProject +namespace MultiTenancyDemo.Products { - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule + public class Product : AggregateRoot, IMultiTenant { - //... + public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface + + public string Name { get; set; } + + public float Price { get; set; } } } ```` -> With the "Multi-tenancy ready" concept, we intent to develop our code to be compatible with multi-tenancy approach. Then it can be used in a multi-tenant application or not, depending on the requirements of the final application. +* `IMultiTenant` interface just defines a `TenantId` property. -#### Define Entities +When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries; a tenant can not access to data of another tenant. -You can implement **IMultiTenant** interface for your entities to make them multi-tenancy ready. Example: +### Why the TenantId Property is Nullable? -````C# +`IMultiTenant.TenantId` is **nullable**. When it is null that means the entity is owned by the **Host** side and not owned by a tenant. It is useful when you create a functionality in your system that is both used by the tenant and the host sides. + +For example, `IdentityUser` is an entity defined by the [Identity Module](Modules/Identity.md). The host and all the tenants have their own users. So, for the host side, users will have a `null` `TenantId` while tenant users will have their related `TenantId`. + +> **Tip**: If your entity is tenant-specific and has no meaning in the host side, you can force to not set `null` for the `TenantId` in the constructor of your entity. + +### When to set the TenantId? + +ABP Framework doesn't set the `TenantId` for you (because of the cross tenant operations, ABP can not know the proper `TenantId` in some cases). So, you need to set it yourself **when you create a new multi-tenant entity**. + +#### Best Practice + +We suggest to set the `TenantId` in the constructor and never allow to change it again. So, the `Product` class can be re-written as below: + +````csharp using System; using Volo.Abp.Domain.Entities; using Volo.Abp.MultiTenancy; -namespace MyCompany.MyProject +namespace MultiTenancyDemo.Products { - public class Product : AggregateRoot, IMultiTenant + public class Product : AggregateRoot, IMultiTenant { - public Guid? TenantId { get; set; } //IMultiTenant defines TenantId property + //Private setter prevents changing it later + public Guid? TenantId { get; private set; } public string Name { get; set; } public float Price { get; set; } + + protected Product() + { + //This parameterless constructor is needed for ORMs + } + + public Product(string name, float price, Guid? tenantId) + { + Name = name; + Price = price; + TenantId = tenantId; //Set in the constructor + } } } ```` -IMultiTenant requires to define a **TenantId** property in the implementing entity (See [entity documentation](Entities.md) for more about entities). +> You can see the [entities document](Entities.md) for a more about entities and aggregate roots. -#### Obtain Current Tenant's Id +You typically use the `ICurrentTenant` to set the `TenantId` while creating a new `Product`. -Your code may require to get current tenant's id (regardless of how it's retrieved actually). You can [inject](Dependency-Injection.md) and use **ICurrentTenant** interface for such cases. Example: +**Example: Creating a new product in a [Domain Service](Domain-Services.md)** -````C# -using Volo.Abp.DependencyInjection; -using Volo.Abp.MultiTenancy; +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Domain.Services; -namespace MyCompany.MyProject +namespace MultiTenancyDemo.Products { - public class MyService : ITransientDependency + public class ProductManager : DomainService { - private readonly ICurrentTenant _currentTenant; + private readonly IRepository _productRepository; - public MyService(ICurrentTenant currentTenant) + public ProductManager(IRepository productRepository) { - _currentTenant = currentTenant; + _productRepository = productRepository; } - public void DoIt() + public async Task CreateAsync(string name, float price) { - var tenantId = _currentTenant.Id; - //use tenantId in your code... + var product = new Product(name, price, CurrentTenant.Id); + return await _productRepository.InsertAsync(product); } } } ```` -#### Change Current Tenant +* `DomainService` base class (and some common base classes in the ABP Framework) provides the `CurrentTenant`, so you directly use it. Otherwise, you need to [inject](Dependency-Injection.md) the `ICurrentTenant` service. -TODO: ... +## ICurrentTenant -#### Determining Current Tenant +`ICurrentTenant` is the main service to interact with the multi-tenancy infrastructure. `ApplicationService`, `DomainService`, `AbpController` and some other base classes already has pre-injected `CurrentTenant` properties. For other type of classes, you can inject the `ICurrentTenant` into your service. -The first thing for a multi-tenant application is to determine the current tenant on the runtime. Volo.Abp.MultiTenancy package only provides abstractions (named as tenant resolver) for determining the current tenant, however it does not have any implementation out of the box. +### Tenant Properties -**Volo.Abp.AspNetCore.MultiTenancy** package has implementation to determine the current tenant from current web request (from subdomain, header, cookie, route... etc.). See Volo.Abp.AspNetCore.MultiTenancy Package section later in this document. +`ICurrentTenant` defines the following properties; -##### Custom Tenant Resolvers +* `Id` (`Guid`): Id of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request. +* `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request. +* `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`. -You can add your custom tenant resolver to **AbpTenantResolveOptions** in your module's ConfigureServices method as like below: +### Change the Current Tenant -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; +ABP Framework automatically filters the resources (database, cache...) based on the `ICurrentTenant.Id`. However, in some cases you may want to perform an operation on behalf of a specific tenant, generally when you are in the host context. -namespace MyCompany.MyProject +`ICurrentTenant.Change` method changes the current tenant for a limited scope, so you can safely perform operations for the tenant. + +**Example: Get product count of a specific tenant** + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Domain.Services; + +namespace MultiTenancyDemo.Products { - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule + public class ProductManager : DomainService { - public override void ConfigureServices(ServiceConfigurationContext context) + private readonly IRepository _productRepository; + + public ProductManager(IRepository productRepository) { - Configure(options => - { - options.TenantResolvers.Add(new MyCustomTenantResolveContributor()); - }); + _productRepository = productRepository; + } - //... + public async Task GetProductCountAsync(Guid? tenantId) + { + using (CurrentTenant.Change(tenantId)) + { + return await _productRepository.GetCountAsync(); + } } } } ```` -`MyCustomTenantResolveContributor` must implement **ITenantResolveContributor** as shown below: +* `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement. +* When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly. -````C# +> Always use the `Change` method with a `using` statement like done in this example. + +## Determining Current Tenant + +The first thing for a multi-tenant application is to determine the current tenant on the runtime. ABP Framework provides an extensible *Tenant Resolving* system for that purpose. + +### Default Tenant Resolvers + +The following resolvers are provided by default; + +* `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**. +* `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default. +* `FormTenantResolveContributor`:Tries to find current tenant id from form parameters. The parameter name is `__tenant` by default. +* `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route. +* `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default. +* `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default. + +#### Problems with the NGINX + +You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please: +http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers +http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers + +#### AbpAspNetCoreMultiTenancyOptions + +`__tenant` parameter name can be changed using `AbpAspNetCoreMultiTenancyOptions`. + +**Example:** + +````csharp +services.Configure(options => +{ + options.TenantKey = "MyTenantKey"; +}); +```` + +> However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then. + +### Domain/Subdomain Tenant Resolver + +In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver. + +**Example: Add a subdomain resolver** + +````csharp +Configure(options => +{ + options.AddDomainTenantResolver("{0}.mydomain.com"); +}); +```` + +* `{0}` is the placeholder to determine current tenant's unique name. +* Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md). +* This should be done in the *Web/API Layer* since the URL is a web related stuff. + +### Custom Tenant Resolvers + +You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below: + +````csharp +Configure(options => +{ + options.TenantResolvers.Add(new MyCustomTenantResolveContributor()); +}); +```` + +`MyCustomTenantResolveContributor` must inherit from the `TenantResolveContributorBase` (or implement the `ITenantResolveContributor`) as shown below: + +````csharp using Volo.Abp.MultiTenancy; -namespace MyCompany.MyProject +namespace MultiTenancyDemo.Web { - public class MyCustomTenantResolveContributor : ITenantResolveContributor + public class MyCustomTenantResolveContributor : TenantResolveContributorBase { - public void Resolve(ITenantResolveContext context) + public override string Name => "Custom"; + + public override void Resolve(ITenantResolveContext context) { - context.TenantIdOrName = ... //find tenant id or tenant name from somewhere... + //TODO... } } } ```` -A tenant resolver can set **TenantIdOrName** if it can determine it. If not, just leave it as is to allow next resolver to determine it. +* A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it. +* `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system. -#### Tenant Store +## Avdanced Topics + +### Tenant Store Volo.Abp.MultiTenancy package defines **ITenantStore** to abstract data source from the framework. You can implement ITenantStore to work with any data source (like a relational database) that stores information of your tenants. @@ -300,75 +423,35 @@ TODO:... #### Determining Current Tenant From Web Request -Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box: - -* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**. -* **QueryStringTenantResolveContributor**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default. -* **FormTenantResolveContributor**: Tries to find current tenant id from form parameter. Parameter name is "__tenant" by default. -* **RouteTenantResolveContributor**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route. -* **HeaderTenantResolveContributor**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default. -* **CookieTenantResolveContributor**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default. +### Related Packages -> If you use nginx as a reverse proxy server, please note that if `TenantKey` contains an underscore or other special characters, there may be a problem, please refer to: -http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers -http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers +TODO +Volo.Abp.MultiTenancy package defines fundamental interfaces to make your code "multi-tenancy ready". So, install it to your project using the package manager console (PMC): -"__tenant" parameter name can be changed using AbpAspNetCoreMultiTenancyOptions. Example: - -````C# -services.Configure(options => -{ - options.TenantKey = "MyTenantKey"; -}); +```` +Install-Package Volo.Abp.MultiTenancy ```` -##### Domain Tenant Resolver - -In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure AbpTenantResolveOptions to add a domain tenant resolver. +> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. -###### Example: Add a subdomain resolver +Then you can add **AbpMultiTenancyModule** dependency to your module: ````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; namespace MyCompany.MyProject { - [DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))] + [DependsOn(typeof(AbpMultiTenancyModule))] public class MyModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - //Subdomain format: {0}.mydomain.com - //Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to - //ensure the user cannot impersonate a different tenant. - options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com")); - }); - - //... - } + //... } } ```` -{0} is the placeholder to determine current tenant's unique name. - -Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut: - -````C# -options.AddDomainTenantResolver("{0}.mydomain.com"); -```` - -###### Example: Add a domain resolver - -````C# -options.AddDomainTenantResolver("{0}.com"); -```` +> With the "Multi-tenancy ready" concept, we intent to develop our code to be compatible with multi-tenancy approach. Then it can be used in a multi-tenant application or not, depending on the requirements of the final application. ## See Also From f050231085b61696111e2bf900d190a2f6d8d997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 23 Sep 2020 12:14:18 +0300 Subject: [PATCH 5/8] Complete the initial multi-tenancy document. --- docs/en/Multi-Tenancy.md | 198 ++++++--------------------------------- 1 file changed, 28 insertions(+), 170 deletions(-) diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 077db1e272..8606e6e1e3 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -41,7 +41,7 @@ namespace MultiTenancyDemo.Products * `IMultiTenant` interface just defines a `TenantId` property. -When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries; a tenant can not access to data of another tenant. +When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries. A tenant can not access to data of another tenant by default. ### Why the TenantId Property is Nullable? @@ -177,13 +177,17 @@ namespace MultiTenancyDemo.Products > Always use the `Change` method with a `using` statement like done in this example. -## Determining Current Tenant +## Determining the Current Tenant -The first thing for a multi-tenant application is to determine the current tenant on the runtime. ABP Framework provides an extensible *Tenant Resolving* system for that purpose. +The first thing for a multi-tenant application is to determine the current tenant on the runtime. -### Default Tenant Resolvers +ABP Framework provides an extensible **Tenant Resolving** system for that purpose. Tenant Resolving system then used in the **Multi-Tenancy Middleware** to determine the current tenant for the current HTTP request. -The following resolvers are provided by default; +### Tenant Resolvers + +#### Default Tenant Resolvers + +The following resolvers are provided and configured by default; * `CurrentUserTenantResolveContributor`: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for the security**. * `QueryStringTenantResolveContributor`: Tries to find current tenant id from query string parameters. The parameter name is `__tenant` by default. @@ -192,13 +196,13 @@ The following resolvers are provided by default; * `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default. * `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default. -#### Problems with the NGINX +##### Problems with the NGINX You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please: http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers -#### AbpAspNetCoreMultiTenancyOptions +##### AbpAspNetCoreMultiTenancyOptions `__tenant` parameter name can be changed using `AbpAspNetCoreMultiTenancyOptions`. @@ -213,7 +217,7 @@ services.Configure(options => > However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then. -### Domain/Subdomain Tenant Resolver +#### Domain/Subdomain Tenant Resolver In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver. @@ -230,7 +234,7 @@ Configure(options => * Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md). * This should be done in the *Web/API Layer* since the URL is a web related stuff. -### Custom Tenant Resolvers +#### Custom Tenant Resolvers You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below: @@ -263,95 +267,31 @@ namespace MultiTenancyDemo.Web * A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it. * `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system. -## Avdanced Topics - -### Tenant Store - -Volo.Abp.MultiTenancy package defines **ITenantStore** to abstract data source from the framework. You can implement ITenantStore to work with any data source (like a relational database) that stores information of your tenants. - -... +### Multi-Tenancy Middleware -##### Configuration Data Store +Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties. -There is a built in (and default) tenant store, named ConfigurationTenantStore, that can be used to store tenants using standard [configuration system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) (with [Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration) package). Thus, you can define tenants as hard coded or get from your appsettings.json file. - -###### Example: Define tenants as hard-coded +Multi-Tenancy middleware is typically placed just under the [authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) middleware (`app.UseAuthentication()`): ````C# -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Data; -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.Tenants = new[] - { - new TenantConfiguration( - Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id - "tenant1" //Name - ), - new TenantConfiguration( - Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id - "tenant2" //Name - ) - { - //tenant2 has a seperated database - ConnectionStrings = - { - {ConnectionStrings.DefaultConnectionStringName, "..."} - } - } - }; - }); - } - } -} +app.UseMultiTenancy(); ```` -###### Example: Define tenants in appsettings.json +> This middleware is already configured in the startup templates, so you normally don't need to manually add it. -First create your configuration from your appsettings.json file as you always do. +## Tenant Store -````C# -using System.IO; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; +`ITenantStore` is used to get the tenant configuration from a data source. -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - var configuration = BuildConfiguration(); +### Tenant Management Module - Configure(configuration); - } +The [tenant management module](Modules/Tenant-Management) is **included in the startup templates** and implements the `ITenantStore` interface to get the tenants and their configuration from a database. It also provides the necessary functionality and UI to manage the tenants and their connection strings. - private static IConfigurationRoot BuildConfiguration() - { - return new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .Build(); - } - } -} -```` +### Configuration Data Store + +**If you don't want to use the tenant management module**, the `DefaultTenantStore` is used as the `ITenantStore` implementation. It gets the tenant configurations from the [configuration system](Configuration.md) (`IConfiguration`). You can either configure the `AbpDefaultTenantStoreOptions` [options](Options.md) or set it in your `appsettings.json` file: -Then add a "**Tenants**" section to your appsettings.json: +**Example: Define tenants in appsettings.json** ````json "Tenants": [ @@ -363,95 +303,13 @@ Then add a "**Tenants**" section to your appsettings.json: "Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d", "Name": "tenant2", "ConnectionStrings": { - "Default": "...write tenant2's db connection string here..." + "Default": "...tenant2's db connection string here..." } } ] ```` -##### Volo.Abp... Package (TODO) - -TODO: This package implements ITenantStore using a real database... - -#### Tenant Information - -ITenantStore works with **TenantConfiguration** class that has several properties for a tenant: - -* **Id**: Unique Id of the tenant. -* **Name**: Unique name of the tenant. -* **ConnectionStrings**: If this tenant has dedicated database(s) to store it's data, then connection strings can provide database connection strings (it may have a default connection string and connection strings per modules - TODO: Add link to Abp.Data package document). - -A multi-tenant application may require additional tenant properties, but these are the minimal requirements for the framework to work with multiple tenants. - -#### Change Tenant By Code - -TODO... - -### Volo.Abp.AspNetCore.MultiTenancy Package - -Volo.Abp.AspNetCore.MultiTenancy package integrate multi-tenancy to ASP.NET Core applications. To install it to your project, run the following command on PMC: - -```` -Install-Package Volo.Abp.AspNetCore.MultiTenancy -```` - -Then you can add **AbpAspNetCoreMultiTenancyModule** dependency to your module: - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.AspNetCore.MultiTenancy; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))] - public class MyModule : AbpModule - { - //... - } -} -```` - -#### Multi-Tenancy Middleware - -Volo.Abp.AspNetCore.MultiTenancy package includes the multi-tenancy middleware... - -````C# -app.UseMultiTenancy(); -```` - -TODO:... - -#### Determining Current Tenant From Web Request - -### Related Packages - -TODO - -Volo.Abp.MultiTenancy package defines fundamental interfaces to make your code "multi-tenancy ready". So, install it to your project using the package manager console (PMC): - -```` -Install-Package Volo.Abp.MultiTenancy -```` - -> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. - -Then you can add **AbpMultiTenancyModule** dependency to your module: - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule - { - //... - } -} -```` - -> With the "Multi-tenancy ready" concept, we intent to develop our code to be compatible with multi-tenancy approach. Then it can be used in a multi-tenant application or not, depending on the requirements of the final application. +> It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates. ## See Also From 810a2528c10acc09ae77b46e98f4cf3737d72ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 23 Sep 2020 12:39:34 +0300 Subject: [PATCH 6/8] Added Disable the Multi-Tenancy Filter section. --- docs/en/Data-Filtering.md | 53 +++++++++++++++++++++++++++++++++++++-- docs/en/Multi-Tenancy.md | 53 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/docs/en/Data-Filtering.md b/docs/en/Data-Filtering.md index 62021452bd..626719b2fc 100644 --- a/docs/en/Data-Filtering.md +++ b/docs/en/Data-Filtering.md @@ -102,9 +102,58 @@ namespace Acme.BookStore ```` * [Inject](Dependency-Injection.md) the `IDataFilter` service to your class. -* Use the `Disable` method within a `using` statement to create a code block where the `ISoftDelete` filter is disabled inside it (Always use it inside a `using` block to guarantee that the filter is reset to its previous state). +* Use the `Disable` method within a `using` statement to create a code block where the `ISoftDelete` filter is disabled inside it. -`IDataFilter.Enable` method can be used to enable a filter. `Enable` and `Disable` methods can be used in a nested way to define inner scopes. +In addition to the `Disable()` method; + +* `IDataFilter.Enable()` method can be used to enable a filter. `Enable` and `Disable` methods can be used in a **nested** way to define inner scopes. + +* `IDataFilter.IsEnabled()` can be used to check whether a filter is currently enabled or not. + +> Always use the `Disable` and `Enable` methods it inside a `using` block to guarantee that the filter is reset to its previous state. + +### The Generic IDataFilter Service + +`IDataFilter` service has a generic version, `IDataFilter` that injects a more restricted and explicit data filter based on the filter type. + +````csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class MyBookService : ITransientDependency + { + private readonly IDataFilter _softDeleteFilter; + private readonly IRepository _bookRepository; + + public MyBookService( + IDataFilter softDeleteFilter, + IRepository bookRepository) + { + _softDeleteFilter = softDeleteFilter; + _bookRepository = bookRepository; + } + + public async Task> GetAllBooksIncludingDeletedAsync() + { + //Temporary disable the ISoftDelete filter + using (_softDeleteFilter.Disable()) + { + return await _bookRepository.GetListAsync(); + } + } + } +} +```` + +* This usage determines the filter type while injecting the `IDataFilter` service. +* In this case you can use the `Disable()` and `Enable()` methods without specifying the filter type. ## AbpDataFilterOptions diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 8606e6e1e3..ee85177651 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -177,6 +177,51 @@ namespace MultiTenancyDemo.Products > Always use the `Change` method with a `using` statement like done in this example. +## Data Filtering: Disable the Multi-Tenancy Filter + +As mentioned before, ABP Framework handles data isolation between tenants using the [Data Filtering](Data-Filtering.md) system. In some cases, you may want to disable it and perform a query on all the data, without filtering for the current tenant. + +**Example: Get count of products in the database, including all the products of all the tenants.** + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Domain.Services; +using Volo.Abp.MultiTenancy; + +namespace MultiTenancyDemo.Products +{ + public class ProductManager : DomainService + { + private readonly IRepository _productRepository; + private readonly IDataFilter _dataFilter; + + public ProductManager( + IRepository productRepository, + IDataFilter dataFilter) + { + _productRepository = productRepository; + _dataFilter = dataFilter; + } + + public async Task GetProductCountAsync() + { + using (_dataFilter.Disable()) + { + return await _productRepository.GetCountAsync(); + } + } + } +} + +```` + +See the [Data Filtering document](Data-Filtering.md) for more. + +> Note that this approach won't work if your tenants have **separate databases** since there is no built-in way to query from multiple database in a single database query. You should handle it yourself if you need it. + ## Determining the Current Tenant The first thing for a multi-tenant application is to determine the current tenant on the runtime. @@ -273,7 +318,7 @@ Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https: Multi-Tenancy middleware is typically placed just under the [authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) middleware (`app.UseAuthentication()`): -````C# +````csharp app.UseMultiTenancy(); ```` @@ -311,6 +356,12 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s > It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates. +## Other Multi-Tenancy Infrastructure + +ABP Framework was designed to respect to the multi-tenancy in every aspect and most of the times everything will work as expected. + +BLOB Storing, Caching, Data Filtering, Data Seeding, Authorization and all the other services are designed to properly work in a multi-tenant system. + ## See Also * [Features](Features.md) \ No newline at end of file From 86d52fe3078df7858960158cba6b485057e3b114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 23 Sep 2020 12:43:16 +0300 Subject: [PATCH 7/8] Add section: The Tenant Management Module --- docs/en/Multi-Tenancy.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index ee85177651..54a8717a56 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -362,6 +362,12 @@ ABP Framework was designed to respect to the multi-tenancy in every aspect and m BLOB Storing, Caching, Data Filtering, Data Seeding, Authorization and all the other services are designed to properly work in a multi-tenant system. +## The Tenant Management Module + +ABP Framework provides all the the infrastructure to create a multi-tenant application, but doesn't make any assumption about how you manage (create, delete...) your tenants. + +The [Tenant Management module](Modules/Tenant-Management.md) provides a basic UI to manage your tenants and set their connection strings. It is pre-configured for the [application startup template](Startup-Templates/Application.md). + ## See Also * [Features](Features.md) \ No newline at end of file From 73778f526edf675ab00aa629aa150c1a39848e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 23 Sep 2020 14:35:29 +0300 Subject: [PATCH 8/8] Completed the multi-tenancy document. Resolved #5564. --- docs/en/Multi-Tenancy.md | 80 +++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 54a8717a56..03b3420ada 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -13,9 +13,40 @@ There are two main side of a typical SaaS / Multi-tenant application: * A **Tenant** is a customer of the SaaS application that pays money to use the service. * **Host** is the company that owns the SaaS application and manages the system. -We will use the Host and Tenant terms for that purpose in the rest of the document. +The Host and the Tenant terms will be used for that purpose in the rest of the document. -## IMultiTenant +## Configuration + +### AbpMultiTenancyOptions: Enable/Disable Multi-Tenancy + +`AbpMultiTenancyOptions` is the main options class to **enable/disable the multi-tenancy** for your application. + +**Example: Enable multi-tenancy** + +```csharp +Configure(options => +{ + options.IsEnabled = true; +}); +``` + +> Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place. + +### Database Architecture + +ABP Framework supports all the following approaches to store the tenant data in the database; + +* **Single Database**: All tenants are stored in a single database. +* **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant. +* **Hybrid**: Some tenants share a single databases while some tenants may have their own databases. + +[Tenant management module](Modules/Tenant-Management.md) (which comes pre-installed with the startup projects) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches. + +## Usage + +Multi-tenancy system is designed to **work seamlessly** and make your application code **multi-tenancy unaware** as much as possible. + +### IMultiTenant You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**. @@ -43,7 +74,7 @@ namespace MultiTenancyDemo.Products When you implement this interface, ABP Framework **automatically** [filters](Data-Filtering.md) entities for the current tenant when you query from database. So, you don't need to manually add `TenantId` condition while performing queries. A tenant can not access to data of another tenant by default. -### Why the TenantId Property is Nullable? +#### Why the TenantId Property is Nullable? `IMultiTenant.TenantId` is **nullable**. When it is null that means the entity is owned by the **Host** side and not owned by a tenant. It is useful when you create a functionality in your system that is both used by the tenant and the host sides. @@ -51,11 +82,11 @@ For example, `IdentityUser` is an entity defined by the [Identity Module](Module > **Tip**: If your entity is tenant-specific and has no meaning in the host side, you can force to not set `null` for the `TenantId` in the constructor of your entity. -### When to set the TenantId? +#### When to set the TenantId? ABP Framework doesn't set the `TenantId` for you (because of the cross tenant operations, ABP can not know the proper `TenantId` in some cases). So, you need to set it yourself **when you create a new multi-tenant entity**. -#### Best Practice +##### Best Practice We suggest to set the `TenantId` in the constructor and never allow to change it again. So, the `Product` class can be re-written as below: @@ -124,11 +155,13 @@ namespace MultiTenancyDemo.Products * `DomainService` base class (and some common base classes in the ABP Framework) provides the `CurrentTenant`, so you directly use it. Otherwise, you need to [inject](Dependency-Injection.md) the `ICurrentTenant` service. -## ICurrentTenant +### ICurrentTenant -`ICurrentTenant` is the main service to interact with the multi-tenancy infrastructure. `ApplicationService`, `DomainService`, `AbpController` and some other base classes already has pre-injected `CurrentTenant` properties. For other type of classes, you can inject the `ICurrentTenant` into your service. +`ICurrentTenant` is the main service to interact with the multi-tenancy infrastructure. -### Tenant Properties +`ApplicationService`, `DomainService`, `AbpController` and some other base classes already has pre-injected `CurrentTenant` properties. For other type of classes, you can inject the `ICurrentTenant` into your service. + +#### Tenant Properties `ICurrentTenant` defines the following properties; @@ -136,7 +169,7 @@ namespace MultiTenancyDemo.Products * `Name` (`string`): Name of the current tenant. Can be `null` if the current user is a host user or the tenant could not be determined from the request. * `IsAvailable` (`bool`): Returns `true` if the `Id` is not `null`. -### Change the Current Tenant +#### Change the Current Tenant ABP Framework automatically filters the resources (database, cache...) based on the `ICurrentTenant.Id`. However, in some cases you may want to perform an operation on behalf of a specific tenant, generally when you are in the host context. @@ -174,10 +207,11 @@ namespace MultiTenancyDemo.Products * `Change` method can be used in a **nested way**. It restores the `CurrentTenant.Id` to the previous value after the `using` statement. * When you use `CurrentTenant.Id` inside the `Change` scope, you get the `tenantId` provided to the `Change` method. So, the repository also get this `tenantId` and can filter the database query accordingly. +* Use `CurrentTenant.Change(null)` to change scope to the host context. > Always use the `Change` method with a `using` statement like done in this example. -## Data Filtering: Disable the Multi-Tenancy Filter +### Data Filtering: Disable the Multi-Tenancy Filter As mentioned before, ABP Framework handles data isolation between tenants using the [Data Filtering](Data-Filtering.md) system. In some cases, you may want to disable it and perform a query on all the data, without filtering for the current tenant. @@ -222,15 +256,17 @@ See the [Data Filtering document](Data-Filtering.md) for more. > Note that this approach won't work if your tenants have **separate databases** since there is no built-in way to query from multiple database in a single database query. You should handle it yourself if you need it. -## Determining the Current Tenant +## Infrastructure + +### Determining the Current Tenant The first thing for a multi-tenant application is to determine the current tenant on the runtime. ABP Framework provides an extensible **Tenant Resolving** system for that purpose. Tenant Resolving system then used in the **Multi-Tenancy Middleware** to determine the current tenant for the current HTTP request. -### Tenant Resolvers +#### Tenant Resolvers -#### Default Tenant Resolvers +##### Default Tenant Resolvers The following resolvers are provided and configured by default; @@ -241,13 +277,13 @@ The following resolvers are provided and configured by default; * `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default. * `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default. -##### Problems with the NGINX +###### Problems with the NGINX You may have problems with the `__tenant` in the HTTP Headers if you're using the [nginx](https://www.nginx.com/) as the reverse proxy server. Because it doesn't allow to use underscore and some other special characters in the HTTP headers and you may need to manually configure it. See the following documents please: http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers -##### AbpAspNetCoreMultiTenancyOptions +###### AbpAspNetCoreMultiTenancyOptions `__tenant` parameter name can be changed using `AbpAspNetCoreMultiTenancyOptions`. @@ -262,7 +298,7 @@ services.Configure(options => > However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then. -#### Domain/Subdomain Tenant Resolver +##### Domain/Subdomain Tenant Resolver In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver. @@ -279,7 +315,7 @@ Configure(options => * Add this code to the `ConfigureServices` method of your [module](Module-Development-Basics.md). * This should be done in the *Web/API Layer* since the URL is a web related stuff. -#### Custom Tenant Resolvers +##### Custom Tenant Resolvers You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below: @@ -312,7 +348,7 @@ namespace MultiTenancyDemo.Web * A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it. * `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](Dependency-Injection.md) system. -### Multi-Tenancy Middleware +#### Multi-Tenancy Middleware Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties. @@ -324,15 +360,15 @@ app.UseMultiTenancy(); > This middleware is already configured in the startup templates, so you normally don't need to manually add it. -## Tenant Store +### Tenant Store `ITenantStore` is used to get the tenant configuration from a data source. -### Tenant Management Module +#### Tenant Management Module The [tenant management module](Modules/Tenant-Management) is **included in the startup templates** and implements the `ITenantStore` interface to get the tenants and their configuration from a database. It also provides the necessary functionality and UI to manage the tenants and their connection strings. -### Configuration Data Store +#### Configuration Data Store **If you don't want to use the tenant management module**, the `DefaultTenantStore` is used as the `ITenantStore` implementation. It gets the tenant configurations from the [configuration system](Configuration.md) (`IConfiguration`). You can either configure the `AbpDefaultTenantStoreOptions` [options](Options.md) or set it in your `appsettings.json` file: @@ -356,7 +392,7 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s > It is recommended to **use the Tenant Management module**, which is already pre-configured when you create a new application with the ABP startup templates. -## Other Multi-Tenancy Infrastructure +### Other Multi-Tenancy Infrastructure ABP Framework was designed to respect to the multi-tenancy in every aspect and most of the times everything will work as expected.