> You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module.
This module is used to manage your tenants and editions in multi-tenant applications;
This module is used to manage tenants and editions in multi-tenant applications:
* Manage **tenants** and **editions** in the system. A tenant is allowed to have one **edition**.
* Set **features** of tenants.
* Set **connection string** of tenants.
* Set **features** of editions and tenants.
- Manage **tenants** and **editions**. A tenant can have one edition.
- Assign application **features** to editions and tenants.
- Configure default and module-specific tenant **connection strings**.
- Control tenant activation and edition expiration.
See [the module description page](https://abp.io/modules/Volo.Saas) for an overview of the module features.
## How to install
## How to Install
Saas is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it.
The SaaS module is pre-installed in the [startup templates](../solution-templates), so you don't need to install it manually.
## Packages
This module follows the [module development best practices guide](../framework/architecture/best-practices) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them.
You can visit [SaaS module package list page](https://abp.io/packages?moduleName=Volo.Saas) to see list of packages related with this module.
See the [SaaS module package list](https://abp.io/packages?moduleName=Volo.Saas) for the related packages.
## Tenant-Edition Subscription
SaaS module implements subscribing to Editions for Tenants using Payment module. To enable it, project must contain`Volo.Saas` and `Volo.Payment` modules and these modules must be configured as shown below.
The SaaS module integrates with the Payment module to subscribe tenants to editions. The solution must contain both the`Volo.Saas` and `Volo.Payment` modules.
### Configuration
Firstly, Payment module must be configured properly:
Configure the Payment module first:
- Install the `Volo.Payment` module:
- Install `Volo.Payment` module.
```bash
abp add-module Volo.Payment
```
_Or you can install via using ABP Studio._
- Configure Saas module to use Payment.
You can also install it with ABP Studio.
- Enable the Payment integration for the SaaS module:
```csharp
Configure<AbpSaasPaymentOptions>(options =>
{
@ -51,61 +53,67 @@ Firstly, Payment module must be configured properly:
});
```
- Follow the [subscriptions](payment#subscriptions) section of [Payment Module Documentation](payment#subscriptions). Complete [enabling webhooks](payment#enabling-webhooks) and [configuring plans](payment#configuring-plans) sections.
- Complete the Payment module's [subscription](payment.md#subscriptions), [webhook](payment.md#enabling-webhooks) and [plan](payment.md#configuring-plans) configuration.
- Run the application and go to `Saas > Editions` page at your Web Application menu.
- Run the application and open the `SaaS > Editions` page.
- Create or Edit an existing Edition. **Plan** dropdown must be visible if you've done earlier steps correctly. Pick a Plan for Edition.
- Create an edition or edit an existing one, then select a Payment plan in the **Plan** field. An edition must have a plan before it can be used to create a subscription.
### Usage
SaaS module doesn't contain a public facing list page for listing editions for new customers/tenants to subscribe. First, you need to create such a page in your application. Then, when a new customer/tenant selects one of those Editions, you can create a subscription and redirect user to payment module as shown below.
The module doesn't provide a public edition catalog. Create that page in your application and call `ISubscriptionAppService` after an authenticated tenant selects an edition. The following same-process Razor Pages example derives the tenant ID from `ICurrentTenant` instead of accepting it from the request:
- Inject `ISubscriptionAppService` to create a subscription for a edition:
When the payment is completed successfully, the tenant and edition relation will be updated according to subscription status. Make sure Payment Gateway Web Hooks are configured properly.
After all, payment module will redirect user to the callbackUrl if configured in [payment configuration](payment#paymentweboptions) with a paymentRequestId parameter. In this page, you can check the status of the payment request and show a success message to the user when the payment status is confirmed. Since the payment confirmation is asynchronous, you need to check the payment status repeatedly until it is confirmed.
## User interface
### Menu items
SaaS module adds the following items to the "Main" menu, under the "Administration" menu item:
public IndexModel(
ISubscriptionAppService subscriptionAppService,
ICurrentTenant currentTenant)
{
SubscriptionAppService = subscriptionAppService;
CurrentTenant = currentTenant;
}
public async Task<IActionResult> OnPostAsync(Guid editionId)
{
var paymentRequest = await SubscriptionAppService.CreateSubscriptionAsync(
Keep this operation behind an authenticated application endpoint and never bind an arbitrary tenant ID from public input. In a tiered solution, implement this orchestration in a trusted server-side application layer; the built-in SaaS subscription HTTP endpoint requires the host-side `Saas.Editions` permission.
A subscription-created event assigns the edition and period end. A subscription-updated event refreshes the period end and applies a new edition assignment when the Payment event supplies one. A cancellation keeps the edition assignment and sets its end date to the subscription's period end date. After that date, `Tenant.GetActiveEditionId()` no longer returns the edition.
Payment confirmation is asynchronous. Configure the gateway webhooks and use the Payment module's [callback URL](payment.md#paymentweboptions) and payment-request status flow to show the final result.
## User Interface
### Menu Items
The SaaS module adds a top-level **SaaS** group to the "Main" menu with the following items:
* **Tenants**: Tenant management page.
* **Editions**: Edition management page.
`SaasHostMenuNames` and `SaasTenantMenuNames` classes have the constants for the menu item names.
The `SaasHostMenuNames` class contains the host-side menu item name constants. The tenant-side `SaasTenantMenuNames` class currently contains only its group name.
### Pages
#### Tenant management
#### Tenant Management
Tenant page is used to manage tenants in the system.
The Tenants page is used to manage tenants in the system.
A tenant has one of the following activation states:
- `Active`: The tenant is active without an activation deadline.
- `ActiveWithLimitedTime`: The tenant is active through `ActivationEndDate` and becomes inactive after that time.
- `Passive`: The tenant is inactive.
An edition assignment can also have an `EditionEndDateUtc`. The stored `EditionId` is retained after this date, but `Tenant.GetActiveEditionId()` returns `null`. This lets subscription renewals retain the previous assignment while distinguishing an expired edition.
The module caches the dynamic edition claim used by feature resolution. `EditionDynamicClaimsPrincipalContributorCacheOptions.CacheAbsoluteExpiration` controls this distributed cache entry and defaults to one hour. Updating or deleting a tenant invalidates the entry, but the passage of `EditionEndDateUtc` alone doesn't. This cache setting doesn't control the lifetime of an already issued token or principal.
##### Connection String
You can manage connection string of a tenant in case you want to use a separate database for a specific tenant. If you want to use Host database for a tenant, select "Use the Shared Database" option.
You can manage a tenant's connection string when it should use a separate database. Select **Use the Shared Database** to remove the tenant-specific default and module-specific connection strings. Each connection then falls back to the corresponding host-side configuration.
You can also use the module-specific database connection string feature.
To use this feature, you should configure the module-specific database in the `ConfigureServices` method of your module class. For example, the following code configures the `Saas` module to use a separate database for each tenant.
To use this feature, configure the module-specific database in the `ConfigureServices` method of your module class. Only databases registered with `IsUsedByTenants = true` are available through the SaaS connection-string management API and UI. The following example makes the `Saas` database available:
```csharp
Configure<AbpDbConnectionOptions>(options =>
Configure<AbpDbConnectionOptions>(options =>
{
options.Databases.Configure("Saas", database =>
options.Databases.Configure("Saas", database =>
{
database.IsUsedByTenants = true;
});
});
```
You should select the "Use module specific database connection string" option, then you can determine your modules and their connection strings. Before adding you can check your connection by clicking "Check".
Select **Use module specific database connection string** to configure these databases. Use the **Check** action to validate the supplied values before saving them.
The `Volo.Saas.EnableTenantBasedConnectionStringManagement` setting controls this feature and defaults to `true`. When it is disabled, the built-in UIs hide connection-string management, tenant creation ignores supplied connection strings, and update requests are rejected. The setting is available on the SaaS tab of the Settings page to users with the `Saas.SettingManagement` permission.
> Tenant connection strings are sensitive. The module persists and returns the values as supplied; it doesn't encrypt them before persistence. Restrict `Saas.Tenants.ManageConnectionStrings`, protect the database and event transport, and avoid logging connection-string payloads.
You can set features for a tenant. A tenant-level value overrides the value assigned to its edition. If neither level has a value, feature resolution continues with the application's configuration and the feature's default value.
The application service validates edition display names for uniqueness. Before deleting an edition, you can move all of its tenants to another edition. Deleting an edition without choosing a replacement clears the edition assignment for its tenants.
Commercial startup templates include an application-level [data seed contributor](../framework/infrastructure/data-seeding.md) that calls `IEditionDataSeeder.CreateStandardEditionsAsync()` during the template's database migration and data-seeding flow. Layered applications run this flow from the `.DbMigrator` application, while no-layer applications run it from the host's database migration service. It creates the following host-side data:
This module adds some initial data (see [the data seed system](../framework/infrastructure/data-seeding.md)) to the database when you run the `.DbMigrator` application:
- A `Standard` edition, if an edition with that name doesn't already exist.
* Creates an `Standard` edition.
When integrating the SaaS module into an existing solution, call this method from your own `IDataSeedContributor` if you want the same initial edition. Referencing the SaaS module alone doesn't execute this seeder.
## Internals
### Domain layer
### Domain Layer
#### Aggregates
@ -177,53 +203,75 @@ This module follows the [Entity Best Practices & Conventions](../framework/archi
##### Tenant
A tenant is generally represents a group of users who share a common access with specific privileges to the software instance.
A tenant generally represents a group of users that share access to the software with tenant-specific data and privileges.
* `Tenant` (aggregate root): Represents a tenant in the system.
* `TenantConnectionString` (collection): Connection strings of a tenant.
##### Edition
An edition is typically a category of features of the application.
An edition is a reusable set of application feature values that can be assigned to tenants.
* `Edition` (aggregate root): Represents an edition in the system.
#### Extending the Entities
The `Tenant` and `Edition` entities support the [Module Entity Extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) system. Configure them in the `Domain.Shared` project before the database model is created. The following example adds an extra property to each entity:
The module maps the configured extra properties through its extensible application contracts. The built-in MVC, Blazor, MudBlazor and Angular UIs consume the module entity-extension metadata and display the properties in their supported tables and forms. Use the property `UI` options or an Angular UI contributor when you need to customize visibility, order or rendering.
#### Repositories
This module follows the [Repository Best Practices & Conventions](../framework/architecture/best-practices/repositories.md) guide.
Following custom repositories are defined for this module:
The following custom repositories are defined for this module:
* `ITenantRepository`
* `IEditionRepository`
#### Domain services
#### Domain Services
This module follows the [Domain Services Best Practices & Conventions](../framework/architecture/best-practices/domain-services.md) guide.
##### Tenant manager
##### Tenant and Edition Managers
`TenantManager` is used to create tenants, change and validate name of tenants.
`TenantManager`creates tenants, changes and validates tenant names, and evaluates tenant activation. `EditionManager` validates edition display names, enforces a Payment plan for subscription editions, and moves tenants between editions.
### Application layer
### Application Layer
#### Application services
#### Application Services
* `TenantAppService` (implements `ITenantAppService`): Implements the use cases of the tenant management UI.
*`EditionAppService` (implement `IEditionAppService`): Implements the use cases of the edition management UI.
* `SubscriptionAppService` (implement`ISubscriptionAppService`): Implements the use cases of Tenant-Edition subscription.
-`TenantAppService` (implements `ITenantAppService`): Implements the tenant management use cases.
-`EditionAppService` (implements`IEditionAppService`): Implements the edition management use cases.
All tables/collections use the `Saas` prefix by default. Set static properties on the `SaasDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
##### Connection String
This module uses `Saas` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module uses `Saas` for the connection string name. If you don't define a connection string with this name, it falls back to the `Default` connection string.
See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details.
@ -231,30 +279,53 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do
##### Tables
* **SaasTenants**
* SaasTenantConnectionStrings
* **SaasEditions**
- **SaasTenants**
- SaasTenantConnectionStrings
- **SaasEditions**
SaaS metadata is host-side data. The EF Core model isn't added to a tenant-only database schema.
#### MongoDB
##### Collections
* **SaasTenants**
* **SaasEditions**
- **SaasTenants** (connection strings are embedded in the tenant document)
- **SaasEditions**
### Permissions
See the `SaasHostPermissions` class members for all permissions defined for this module.
All SaaS permissions are host-side permissions:
- `Saas.SettingManagement`: Manages the SaaS settings.
The two change-history permissions are disabled when [entity history](../framework/infrastructure/audit-logging.md#entity-history-selectors) isn't enabled for the corresponding entity.
Tenant impersonation is disabled in the MVC, Blazor and MudBlazor SaaS UI options by default. See the [impersonation documentation](account/impersonation.md) for the UI and Account module configuration required to enable it.
### Angular UI
#### Installation
In order to configure the application to use the saas module, you first need to import `provideSaasConfig` from `@volo/abp.ng.saas/config` to root module. Then, you will need to append it to the `appConfig` array.
Add`provideSaasConfig` from `@volo/abp.ng.saas/config` to the root application providers. It registers the menu routes, authentication filter and SaaS settings tab.
```js
```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideSaasConfig } from '@volo/abp.ng.saas/config';
The saas module should be imported and lazy-loaded in your routing configuration. It has a static `createRoutes` method for configuration. Available options are listed below. It is available for import from `@volo/abp.ng.saas`.
Lazy-load the UI with the `createRoutes` function from `@volo/abp.ng.saas`:
```js
```ts
// app.routes.ts
const APP_ROUTES: Routes = [
import { Routes } from '@angular/router';
export const APP_ROUTES: Routes = [
// ...
{
path: 'saas',
@ -283,18 +356,19 @@ const APP_ROUTES: Routes = [
<h4id="h-saas-module-options">Options</h4>
You can modify the look and behavior of the module pages by passing the following options to `createRoutes` static method:
You can modify the look and behavior of the module pages by passing the following options to `createRoutes`:
- **entityActionContributors:** Changes grid actions. Please check [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md) for details.
- **toolbarActionContributors:** Changes page toolbar. Please check [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md) for details.
- **entityPropContributors:** Changes table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details.
- **createFormPropContributors:** Changes create form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details.
- **editFormPropContributors:** Changes create form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details.
- **editFormPropContributors:** Changes edit form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details.
Each contributor map accepts the `eSaasComponents.Editions` and `eSaasComponents.Tenants` keys.
#### Services / Models
Saas module services and models are generated via`generate-proxy` command of the [ABP CLI](../cli). If you need the module's proxies, you can run the following command in the Angular project directory:
SaaS module services and models are generated via the`generate-proxy` command of the [ABP CLI](../cli). If you need the module's proxies, you can run the following command in the Angular project directory:
See the [Component Replacement](../framework/ui/angular/component-replacement.md) documentation for details.
#### Remote Endpoint URL
The Saas module remote endpoint URLs can be configured in the environment files.
Configure the SaaS host endpoint in the environment when it is served from a different URL:
```js
```ts
export const environment = {
// other configurations
// Other configurations...
apis: {
default: {
url: 'default url here',
url: 'https://localhost:44300',
},
SaasHost: {
url: 'SaasHost remote url here'
url: 'https://localhost:44301',
},
SaasTenant: {
url: 'SaasTenant remote url here'
},
// other api configurations
// Other API configurations...
},
};
```
The Saas module remote URL configurations shown above are optional. If you don't set any URLs, the `default.url` will be used as fallback.
The `SaasHost` entry is optional. If it isn't configured, the generated SaaS Angular services use `default.url`.
## Distributed Events
### Published Events
The tenant workflows explicitly publish the following integration events:
This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module;
- `TenantCreatedEto` after a tenant is created. When `TenantAppService.CreateAsync` creates the tenant, the event's `Properties` dictionary contains `AdminEmail` and `AdminPassword` from the request. In the shared-user strategy, tenant creation also publishes `InviteUserToTenantRequestedEto`.
- `TenantConnectionStringUpdatedEto` for default and module-specific connection-string changes. Its `OldValue` and `NewValue` properties contain the connection-string values.
- `ApplyDatabaseMigrationsEto` when database migration is requested for a tenant.
- `InviteUserToTenantRequestedEto` and `UserPasswordChangeRequestedEto` for their corresponding tenant administration actions.
- `TenantEto` is published on changes done on a `Tenant` entity.
- `EditionEto` is published on changes done on an `Edition` entity.
> Some of these events carry passwords or connection strings. Protect the event transport and storage, restrict subscribers, and don't log or retain complete payloads.
**Example: Get notified when a new tenant has been created**
### Consumed Events
The module consumes `SubscriptionCreatedEto`, `SubscriptionUpdatedEto` and `SubscriptionCanceledEto` from the Payment module as described in [Tenant-Edition Subscription](#tenant-edition-subscription), and the following additional integration events:
- `CreateTenantEto` creates a tenant, then publishes `TenantCreatedEto` and an `InviteUserToTenantRequestedEto` that directly adds the invited user to the tenant.
- A host-side `AppliedDatabaseMigrationsEto` publishes an `ApplyDatabaseMigrationsEto` for each tenant that has a separate connection string for the migrated database. Events that already specify a tenant ID are ignored by this handler.
### Standard Entity Events
The module also maps `Tenant` to `TenantEto` and `Edition` to `EditionEto` for ABP's standard distributed entity events. Registering these mappings doesn't enable automatic entity events. Add the entity types to `AbpDistributedEntityEventOptions.AutoEventSelectors` in the application that owns the SaaS data when consumers need create, update or delete notifications:
See the [Distributed Event Bus](../framework/infrastructure/event-bus/distributed) documentation for delivery, handlers and pre-defined entity events.
`TenantEto` and `EditionEto` are configured to automatically publish the events. You should configure yourself for the others. See the [Distributed Event Bus document](https://github.com/abpframework/abp/blob/rel-7.3/docs/en/Distributed-Event-Bus.md) to learn details of the pre-defined events.
> Subscribing to the distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application, or listening events in the same process that runs the Tenant Management Module, then subscribing to the [local events](https://github.com/abpframework/abp/blob/rel-7.3/docs/en/Local-Event-Bus.md) can be more efficient and easier.
> Distributed events are especially useful in a distributed system. For same-process notifications in a monolithic application, the [Local Event Bus](../framework/infrastructure/event-bus/local) can be simpler.