diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 922976767d..123eb45165 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -1,6 +1,6 @@ # Multi-Tenancy -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**. +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) the multi-tenancy as like that: @@ -10,8 +10,8 @@ Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) the multi-tenanc 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. +- 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. The Host and the Tenant terms will be used for that purpose in the rest of the document. @@ -36,9 +36,9 @@ Configure(options => 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. +- **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 database 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. @@ -48,11 +48,11 @@ Multi-tenancy system is designed to **work seamlessly** and make your applicatio ### IMultiTenant -You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**. +You should implement the `IMultiTenant` interface for your [entities](Entities.md) to make them **multi-tenancy ready**. -**Example: A multi-tenant *Product* entity** +**Example: A multi-tenant _Product_ entity** -````csharp +```csharp using System; using Volo.Abp.Domain.Entities; using Volo.Abp.MultiTenancy; @@ -68,7 +68,7 @@ namespace MultiTenancyDemo.Products public float Price { get; set; } } } -```` +``` `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 by default. @@ -96,9 +96,9 @@ If you set the `TenantId` value for a specific entity object, it will override t `ICurrentTenant` defines the following properties; -* `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`. +- `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`. #### Change the Current Tenant @@ -108,7 +108,7 @@ ABP Framework automatically filters the resources (database, cache...) based on **Example: Get product count of a specific tenant** -````csharp +```csharp using System; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -134,11 +134,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. +- `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. @@ -148,7 +148,7 @@ As mentioned before, ABP Framework handles data isolation between tenants using **Example: Get count of products in the database, including all the products of all the tenants.** -````csharp +```csharp using System; using System.Threading.Tasks; using Volo.Abp.Data; @@ -181,7 +181,7 @@ namespace MultiTenancyDemo.Products } } -```` +``` See the [Data Filtering document](Data-Filtering.md) for more. @@ -201,15 +201,15 @@ ABP Framework provides an extensible **Tenant Resolving** system for that purpos 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. -* `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. +- `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. +- `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: +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 @@ -219,22 +219,24 @@ http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers **Example:** -````csharp +```csharp services.Configure(options => { options.TenantKey = "MyTenantKey"; }); -```` +``` -If you change the `TenantKey`, make sure to pass it to `CoreModule` in the Angular client as follows: +If you change the `TenantKey`, make sure to pass it to `provideAbpCore` via `withOptions` method in the Angular client as follows: ```js @NgModule({ - imports: [ - CoreModule.forRoot({ - // ... - tenantKey: 'MyTenantKey' - }), + providers: [ + provideAbpCore( + withOptions({ + // ... + tenantKey: "MyTenantKey", + }) + ), ], // ... }) @@ -249,7 +251,7 @@ import { TENANT_KEY } from '@abp/ng.core'; class SomeComponent { constructor(@Inject(TENANT_KEY) private tenantKey: string) {} -} +} ``` > 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. @@ -277,30 +279,30 @@ In a real application, most of times you will want to determine the current tena **Example: Add a subdomain resolver** -````csharp +```csharp Configure(options => { options.AddDomainTenantResolver("{0}.mydomain.com"); }); -```` +``` -* `{0}` is the placeholder to determine the 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. +- `{0}` is the placeholder to determine the 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. Openiddict is the default Auth Server in ABP (since v6.0). When you use OpenIddict, you must add this code to the `PreConfigure` method as well. ```csharp // using Volo.Abp.OpenIddict.WildcardDomains -PreConfigure(options => +PreConfigure(options => { options.EnableWildcardDomainSupport = true; options.WildcardDomainsFormat.Add("https://{0}.mydomain.com"); }); ``` -You must add this code to the `Configure` method as well. +You must add this code to the `Configure` method as well. ```csharp // using Volo.Abp.MultiTenancy; @@ -312,7 +314,7 @@ Configure(options => ``` -> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant. +> There is an [example](https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver) that uses the subdomain to determine the current tenant. If you use a sepereted Auth server, you must install `[Owl.TokenWildcardIssuerValidator](https://www.nuget.org/packages/Owl.TokenWildcardIssuerValidator)` on the `HTTPApi.Host` project @@ -332,7 +334,7 @@ context.Services options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.Audience = "ExampleProjectName"; - + // start of added block options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; options.TokenValidationParameters.ValidIssuers = new[] @@ -348,16 +350,16 @@ context.Services You can add implement your custom tenant resolver and configure the `AbpTenantResolveOptions` in your module's `ConfigureServices` method as like below: -````csharp +```csharp Configure(options => { options.TenantResolvers.Add(new MyCustomTenantResolveContributor()); }); -```` +``` `MyCustomTenantResolveContributor` must inherit from the `TenantResolveContributorBase` (or implement the `ITenantResolveContributor`) as shown below: -````csharp +```csharp using System.Threading.Tasks; using Volo.Abp.MultiTenancy; @@ -373,10 +375,10 @@ 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. +- 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 @@ -384,9 +386,9 @@ 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()`): -````csharp +```csharp app.UseMultiTenancy(); -```` +``` > This middleware is already configured in the startup templates, so you normally don't need to manually add it. @@ -406,7 +408,7 @@ The [tenant management module](Modules/Tenant-Management) is **included in the s **Example: Define tenants in appsettings.json** -````json +```json "Tenants": [ { "Id": "446a5211-3d72-4339-9adc-845151f8ada0", @@ -422,7 +424,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. @@ -440,4 +442,4 @@ The [Tenant Management module](Modules/Tenant-Management.md) provides a basic UI ## See Also -* [Features](Features.md) +- [Features](Features.md) diff --git a/docs/en/Themes/LeptonXLite/Angular.md b/docs/en/Themes/LeptonXLite/Angular.md index e7c8142bda..8e46468bd2 100644 --- a/docs/en/Themes/LeptonXLite/Angular.md +++ b/docs/en/Themes/LeptonXLite/Angular.md @@ -31,20 +31,21 @@ yarn add bootstrap-icons Note: You should remove the old theme styles from "angular.json" if you are switching from "ThemeBasic" or "Lepton." Look at the [Theme Configurations](../../UI/Angular/Theme-Configurations) list of styles. Depending on your theme, you can alter your styles in angular.json. -- Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts` +- Finally, remove `ThemeBasicModule`, `provideThemeBasicConfig` from `app.module.ts`, and import the related modules in `app.module.ts` ```js import { ThemeLeptonXModule } from "@abp/ng.theme.lepton-x"; -import { SideMenuLayoutModule } from "@abp/ng.theme.lepton-x/layouts"; @NgModule({ imports: [ // ... - // do not forget to remove ThemeBasicModule or other old theme module - // ThemeBasicModule.forRoot(), - ThemeLeptonXModule.forRoot(), - SideMenuLayoutModule.forRoot(), + // ThemeBasicModule + ThemeLeptonXModule.forRoot() + ], + providers: [ + // do not forget to remove provideThemeBasicConfig or other old theme providers + // provideThemeBasicConfig ], // ... }) diff --git a/docs/en/UI/Angular/Account-Module.md b/docs/en/UI/Angular/Account-Module.md index 28852f6b4d..b5fedb8afe 100644 --- a/docs/en/UI/Angular/Account-Module.md +++ b/docs/en/UI/Angular/Account-Module.md @@ -7,7 +7,6 @@ If you add the account module to your project; - "My account" link in the current user dropdown on the top bar will redirect the user to a page in the account module. - You can switch the authentication flow to the resource owner password flow. - ### Account Module Implementation Install the `@abp/ng.account` NPM package by running the below command: @@ -18,18 +17,18 @@ npm install @abp/ng.account > Make sure v4.3 or higher version is installed. -Open the `app.module.ts` and add `AccountConfigModule.forRoot()` to the imports array as shown below: +Open the `app.module.ts` and add `provideAccountConfig()` to the providers array as shown below: ```js // app.module.ts -import { AccountConfigModule } from '@abp/ng.account/config'; +import { provideAccountConfig } from "@abp/ng.account/config"; //... @NgModule({ - imports: [ + providers: [ //... - AccountConfigModule.forRoot() + provideAccountConfig(), ], //... }) @@ -57,19 +56,19 @@ The pro startup template comes with `@volo/abp.ng.account` package. You should u ```bash npm install @volo/abp.ng.account ``` + > Make sure v4.3 or higher version is installed. Open the `app.module.ts` and add `AccountPublicConfigModule.forRoot()` to the imports array as shown below: > Ensure that the `Account Layout Module` has been added if you are using the Lepton X theme. If you miss the step, you will get an error message that says `Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.` when you try to access the account pages. Otherwise, you can skip adding the `AccountLayoutModule` step. - ```js // app.module.ts -import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config'; +import { AccountPublicConfigModule } from "@volo/abp.ng.account/public/config"; // if you are using or want to use Lepton X, you should add AccountLayoutModule -// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account' +// import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account' //... @@ -104,9 +103,9 @@ Before v4.3, the "My account" link in the current user dropdown on the top bar r ### Personal Info Page Confirm Message -When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert. +When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert. -If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false +If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false ```js // app-routing.module.ts diff --git a/docs/en/UI/Angular/Basic-Theme.md b/docs/en/UI/Angular/Basic-Theme.md index 651be9b637..3b11e81f14 100644 --- a/docs/en/UI/Angular/Basic-Theme.md +++ b/docs/en/UI/Angular/Basic-Theme.md @@ -11,7 +11,7 @@ The Basic Theme is a theme implementation for the Angular UI. It is a minimalist If you need to manually this theme, follow the steps below: * Install the [@abp/ng.theme.basic](https://www.npmjs.com/package/@abp/ng.theme.basic) NPM package to your Angular project. -* Open the `src/app/app.module.ts` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule.forRoot()` to the `imports` array. +* Open the `src/app/app.module.ts` file, import `ThemeBasicModule`,`provideThemeBasicConfig` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` array and provide `provideThemeBasicConfig()` to the providers array. * Open the `src/app/shared/shared.module` file, import `ThemeBasicModule` (it can be imported from `@abp/ng.theme.basic` package), and add `ThemeBasicModule` to the `imports` and `exports` array. The `ThemeBasicModule` is registered own layouts (`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`) to a service which is exposed by `@abp/ng.core` package on application initialization. diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index d21489d232..73c7a2ed15 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -30,7 +30,6 @@ export class AppComponent { ![Example Usage](./images/component-replacement.gif) - ## How to Replace a Layout Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way. @@ -88,11 +87,13 @@ This component should have a 'router-outlet' for dynamic content loading. You ca ```bash ng generate component new-layout ``` + This command will create a new component named `new-layout`. Now, open the new-layout.component.html file and add a `router-outlet` to it: ```html - + ``` + This 'router-outlet' will act as a placeholder that Angular dynamically fills based on the current router state. note: (don't forget: you should add the app in the app.module.ts file) @@ -103,10 +104,11 @@ Although this step is optional, it can be useful if you're going to use the layo ```javascript export const eCustomLayout = { - key: 'CustomLayout', - component: 'CustomLayoutComponent', + key: "CustomLayout", + component: "CustomLayoutComponent", }; ``` + In this variable, `key` is a unique identifier for the layout component, and `component` is the name of the layout component. You can use this variable when you need to refer to the layout component. @@ -120,19 +122,24 @@ Here's how you can do it: ```javascript export const CUSTOM_LAYOUT_PROVIDERS = [ - { provide: APP_INITIALIZER, useFactory: configureLayoutFn, deps: [ReplaceableComponentsService], multi: true }, - + { + provide: APP_INITIALIZER, + useFactory: configureLayoutFn, + deps: [ReplaceableComponentsService], + multi: true, + }, ]; function configureLayoutFn() { - const service= inject( ReplaceableComponentsService) - return () =>{ - service.add({ - key: eCustomLayout.component, - component: CustomLayoutComponent, - }) - } + const service = inject(ReplaceableComponentsService); + return () => { + service.add({ + key: eCustomLayout.component, + component: CustomLayoutComponent, + }); + }; } ``` + In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `APP_INITIALIZER` provider runs this function when the application starts. note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.module.ts file) @@ -149,34 +156,31 @@ export const myDynamicLayouts = new Map([...DEFAULT_DYNAMIC_LAYO #### Step 5: Pass the Dynamic Layouts to the CoreModule -The final step is to pass the dynamic layouts to the `CoreModule` using the `forRoot` method. This method allows you to configure the module with a static method. +The final step is to pass the dynamic layouts to the `provideAbpCore` using the `withOptions` method. This method allows you to configure the module with a static method. Here's how you can do it: -```javascript +```ts @NgModule({ - declarations: [AppComponent], - imports: [ - // other imports... - CoreModule.forRoot({ - dynamicLayouts: myDynamicLayouts, - environment, - registerLocaleFn: registerLocale(), - }), - // other imports... - NewLayoutComponent + providers: [ + // ... + provideAbpCore( + withOptions({ + dynamicLayouts: myDynamicLayouts, + environment, + registerLocaleFn: registerLocale(), + }), + ), ], - providers: [APP_ROUTE_PROVIDER, CUSTOM_LAYOUT_PROVIDERS], - bootstrap: [AppComponent], }) export class AppModule {} ``` -In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `CoreModule` using the `forRoot` method. +In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `provideAbpCore` using the `withOptions` method. Now that you have defined the new layout, you can use it in the router definition. You do this by adding a new route that uses the new layout. -Here's how you can do it: +Here's how you can do it: ```javascript // route.provider.ts @@ -221,14 +225,13 @@ Run the following command in `angular` folder to create a new component called ` yarn ng generate component logo --inlineTemplate --inlineStyle ``` - Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following: ```js -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; @Component({ - selector: 'app-logo', + selector: "app-logo", template: ` @@ -284,14 +287,14 @@ yarn ng generate component routes Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following: ```js -import { Component, HostBinding } from '@angular/core'; +import { Component, HostBinding } from "@angular/core"; @Component({ - selector: 'app-routes', - templateUrl: 'routes.component.html', + selector: "app-routes", + templateUrl: "routes.component.html", }) export class RoutesComponent { - @HostBinding('class.mx-auto') + @HostBinding("class.mx-auto") marginAuto = true; get smallScreen() { @@ -321,11 +324,14 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac