From eca1e7e8e7a69ae606ca20e4b212923b6a6e7701 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 17 Jul 2026 15:17:39 +0800 Subject: [PATCH 01/41] Improve framework documentation coverage --- docs/en/docs-nav.json | 32 ++++ .../api-development/auto-controllers.md | 12 +- .../api-development/dynamic-csharp-clients.md | 40 +++++ .../api-development/identitymodel-clients.md | 101 +++++++++++ docs/en/framework/api-development/swagger.md | 15 ++ .../domain-driven-design/repositories.md | 5 +- .../domain-driven-design/unit-of-work.md | 10 +- .../architecture/modularity/basics.md | 30 ++++ .../extending/module-entity-extensions.md | 27 +++ .../data/entity-framework-core/index.md | 2 +- docs/en/framework/data/memorydb/index.md | 77 +++++++++ docs/en/framework/data/mongodb/index.md | 25 +++ .../fundamentals/application-startup.md | 5 + docs/en/framework/fundamentals/caching.md | 54 +++++- .../fundamentals/dependency-injection.md | 2 +- .../fundamentals/exception-handling.md | 14 ++ .../en/framework/fundamentals/localization.md | 159 +++++++++++++++++- docs/en/framework/fundamentals/logging.md | 17 ++ docs/en/framework/fundamentals/options.md | 53 ++++++ docs/en/framework/fundamentals/validation.md | 27 ++- .../microsoft-agent-framework.md | 4 +- .../framework/infrastructure/audit-logging.md | 4 +- .../infrastructure/background-jobs/index.md | 4 +- .../background-workers/hangfire.md | 10 ++ .../infrastructure/csrf-anti-forgery.md | 1 + .../framework/infrastructure/current-user.md | 27 +++ .../event-bus/distributed/azure.md | 5 +- .../event-bus/distributed/index.md | 3 + .../event-bus/distributed/rabbitmq.md | 5 +- docs/en/framework/infrastructure/features.md | 2 +- .../framework/infrastructure/interceptors.md | 17 ++ docs/en/framework/infrastructure/json.md | 29 ++++ docs/en/framework/infrastructure/mail-kit.md | 4 +- .../object-to-object-mapping.md | 13 +- docs/en/framework/infrastructure/settings.md | 9 + .../framework/infrastructure/sms-sending.md | 90 +++++++++- .../infrastructure/string-encryption.md | 8 +- .../infrastructure/text-templating/index.md | 17 +- .../infrastructure/virtual-file-system.md | 34 +++- docs/en/framework/real-time/signalr.md | 11 ++ docs/en/framework/ui/angular/commercial-ui.md | 88 ++++++++++ .../ui/angular/component-replacement.md | 4 +- .../ui/angular/datetime-format-pipe.md | 100 +++++++++-- .../ui/angular/http-error-handling.md | 42 +++-- docs/en/framework/ui/angular/http-requests.md | 4 +- docs/en/framework/ui/angular/list-service.md | 6 +- docs/en/framework/ui/angular/localization.md | 76 +++++++-- .../ui/angular/lookup-search-component.md | 94 +++++++++++ .../ui/angular/modifying-the-menu.md | 7 +- docs/en/framework/ui/angular/oauth-module.md | 59 +++++-- .../ui/angular/permission-management.md | 13 +- .../framework/ui/angular/ssr-configuration.md | 31 +++- docs/en/framework/ui/angular/testing.md | 29 ++++ .../en/framework/ui/angular/title-strategy.md | 96 +++++++++++ .../en/framework/ui/angular/tree-component.md | 138 +++++++++++++++ docs/en/framework/ui/blazor/authentication.md | 16 +- .../ui/blazor/global-scripts-styles.md | 34 +++- docs/en/framework/ui/maui/index.md | 4 +- .../mvc-razor-pages/javascript-api/clock.md | 57 +++++++ .../mvc-razor-pages/javascript-api/index.md | 3 +- .../framework/ui/mvc-razor-pages/overall.md | 14 +- docs/en/framework/ui/react/authorization.md | 22 +++ .../ui/react/permission-management.md | 36 ++++ docs/en/modules/identity/ldap.md | 14 +- docs/en/modules/virtual-file-explorer.md | 8 +- docs/en/multi-lingual-entities.md | 101 ++++++++++- 66 files changed, 1965 insertions(+), 135 deletions(-) create mode 100644 docs/en/framework/api-development/identitymodel-clients.md create mode 100644 docs/en/framework/data/memorydb/index.md create mode 100644 docs/en/framework/ui/angular/commercial-ui.md create mode 100644 docs/en/framework/ui/angular/lookup-search-component.md create mode 100644 docs/en/framework/ui/angular/title-strategy.md create mode 100644 docs/en/framework/ui/angular/tree-component.md create mode 100644 docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 32c47a832e..b205037c74 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -550,6 +550,10 @@ "text": "Localization", "path": "framework/fundamentals/localization.md" }, + { + "text": "Multi-Lingual Objects", + "path": "multi-lingual-entities.md" + }, { "text": "Logging", "path": "framework/fundamentals/logging.md" @@ -1111,6 +1115,10 @@ "text": "Dynamic C# API Clients", "path": "framework/api-development/dynamic-csharp-clients.md" }, + { + "text": "IdentityModel Clients", + "path": "framework/api-development/identitymodel-clients.md" + }, { "text": "Integration Services", "path": "framework/api-development/integration-services.md" @@ -1354,6 +1362,10 @@ "text": "Auth", "path": "framework/ui/mvc-razor-pages/javascript-api/auth.md" }, + { + "text": "Clock", + "path": "framework/ui/mvc-razor-pages/javascript-api/clock.md" + }, { "text": "Current User", "path": "framework/ui/mvc-razor-pages/javascript-api/current-user.md" @@ -1657,6 +1669,10 @@ "text": "Localization", "path": "framework/ui/angular/localization.md" }, + { + "text": "Document Title Strategy", + "path": "framework/ui/angular/title-strategy.md" + }, { "text": "Hybrid Localization", "path": "framework/ui/angular/hybrid-localization.md" @@ -1876,6 +1892,14 @@ "text": "Card", "path": "framework/ui/angular/card-component.md" }, + { + "text": "Tree", + "path": "framework/ui/angular/tree-component.md" + }, + { + "text": "Lookup Search", + "path": "framework/ui/angular/lookup-search-component.md" + }, { "text": "Dynamic Forms", "path": "framework/ui/angular/dynamic-form-module.md" @@ -1884,6 +1908,10 @@ "text": "Password Complexity Indicator", "path": "framework/ui/angular/password-complexity-indicator-component.md" }, + { + "text": "Commercial UI Components (Pro)", + "path": "framework/ui/angular/commercial-ui.md" + }, { "text": "Lookup Components(Pro)", "path": "framework/ui/angular/lookup-components.md" @@ -2080,6 +2108,10 @@ "text": "MongoDB", "path": "framework/data/mongodb" }, + { + "text": "In-Memory Database", + "path": "framework/data/memorydb" + }, { "text": "Dapper", "path": "framework/data/dapper" diff --git a/docs/en/framework/api-development/auto-controllers.md b/docs/en/framework/api-development/auto-controllers.md index 2c6f0ee39a..3202592056 100644 --- a/docs/en/framework/api-development/auto-controllers.md +++ b/docs/en/framework/api-development/auto-controllers.md @@ -93,6 +93,16 @@ Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}* * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. * If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). +The final controller name also removes suffixes configured in `AbpConventionalControllerOptions.IgnoredUrlSuffixesInControllerNames`. The default list contains `Integration`, so `PaymentIntegrationService` uses `payment` as its controller route name. You can replace the list when another suffix convention is required: + +```csharp +Configure(options => +{ + options.IgnoredUrlSuffixesInControllerNames = + ["Integration", "Endpoint"]; +}); +``` + #### Customizing the Route Calculation `IConventionalRouteBuilder` is used to build the route. It is implemented by the `ConventionalRouteBuilder` by default and works as explained above. You can replace/override this service to customize the route calculation strategy. @@ -223,4 +233,4 @@ services.Configure(options => ```` ## See Also -* [Video tutorial](https://abp.io/video-courses/essentials/auto-api-controllers) \ No newline at end of file +* [Video tutorial](https://abp.io/video-courses/essentials/auto-api-controllers) diff --git a/docs/en/framework/api-development/dynamic-csharp-clients.md b/docs/en/framework/api-development/dynamic-csharp-clients.md index d560b536eb..bfacc26886 100644 --- a/docs/en/framework/api-development/dynamic-csharp-clients.md +++ b/docs/en/framework/api-development/dynamic-csharp-clients.md @@ -209,6 +209,46 @@ Using `asDefaultServices: false` may only be needed if your application has alre > If you disable `asDefaultServices`, you can only use `IHttpClientProxy` interface to use the client proxies. See the *IHttpClientProxy Interface* section above. +### Before Sending a Proxy Request + +`AbpHttpClientOptions.AddPreSendAction` registers an action for a named remote service. It receives the proxy configuration, the current request context and the `HttpClient`, and runs immediately before each proxy request is sent. + +````csharp +Configure(options => +{ + options.AddPreSendAction( + "BookStore", + (_, requestContext, httpClient) => + { + if (requestContext.Action.Name == "GetReportAsync") + { + httpClient.Timeout = TimeSpan.FromMinutes(2); + } + } + ); +}); +```` + +### Custom Parameter Converters + +Dynamic proxies normally use the built-in conversion rules for query-string, form-data and path values. Implement `IObjectToQueryString`, `IObjectToFormData` or `IObjectToPath` when a type requires custom serialization, register the implementation in dependency injection, and map the value type to the converter: + +````csharp +context.Services.AddTransient(); +context.Services.AddTransient(); +context.Services.AddTransient(); + +Configure(options => +{ + options.QueryStringConverts[typeof(MyFilter)] = + typeof(MyFilterToQueryString); + options.FormDataConverts[typeof(MyUploadMetadata)] = + typeof(MyUploadMetadataToFormData); + options.PathConverts[typeof(MyStrongId)] = + typeof(MyStrongIdToPath); +}); +```` + ### Retry/Failure Logic & Polly Integration If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module class. diff --git a/docs/en/framework/api-development/identitymodel-clients.md b/docs/en/framework/api-development/identitymodel-clients.md new file mode 100644 index 0000000000..754c4816bc --- /dev/null +++ b/docs/en/framework/api-development/identitymodel-clients.md @@ -0,0 +1,101 @@ +```json +//[doc-seo] +{ + "Description": "Configure ABP IdentityModel clients for server-to-server access tokens, tenant-aware client selection, and request customization." +} +``` + +# IdentityModel Clients + +The `Volo.Abp.IdentityModel` package obtains access tokens for server-to-server HTTP calls. `AbpIdentityModelModule` binds the `IdentityClients` configuration section to `AbpIdentityClientOptions`. + +## Installation + +Install the `Volo.Abp.IdentityModel` NuGet package in the project that obtains the tokens: + +````shell +abp add-package Volo.Abp.IdentityModel +```` + +The command adds the package and the `AbpIdentityModelModule` dependency to the module class. + +## Configure Identity Clients + +Define a `Default` client and any named clients in the application configuration: + +````json +{ + "IdentityClients": { + "Default": { + "GrantType": "client_credentials", + "ClientId": "MyProject_Backend", + "ClientSecret": "your-client-secret", + "Authority": "https://localhost:44301/", + "Scope": "MyProject" + }, + "Reporting": { + "GrantType": "client_credentials", + "ClientId": "MyProject_Reporting", + "ClientSecret": "your-reporting-client-secret", + "Authority": "https://localhost:44301/", + "Scope": "Reporting" + } + } +} +```` + +When a client name is requested for the current tenant, ABP selects the first available configuration in this order: + +1. `.` +2. `.` +3. `` +4. `Default` + +If no client name is supplied, ABP uses `Default` as the client name. Tenant-specific entries let a tenant use different credentials without changing the consuming service. For example, `Reporting.8e6fcd0a-75ab-4d94-90f4-9a2503d0e70c` overrides the `Reporting` client for that tenant ID. + +Pass the client name to `TryAuthenticateAsync` when you authenticate an `HttpClient` directly: + +````csharp +var client = _httpClientFactory.CreateClient(); + +if (!await _authenticationService.TryAuthenticateAsync(client, "Reporting")) +{ + throw new InvalidOperationException( + "The Reporting identity client is not configured." + ); +} + +var response = await client.GetAsync("https://reporting.example.com/api/reports"); +```` + +Install `Volo.Abp.Http.Client.IdentityModel` when dynamic HTTP client proxies should obtain tokens automatically. Its `AbpHttpClientIdentityModelModule` integration uses the remote service's `IdentityClient` value when configured, then the remote service name, and finally the `Default` identity client fallback described above: + +````json +{ + "RemoteServices": { + "Reporting": { + "BaseUrl": "https://reporting.example.com/", + "IdentityClient": "Reporting" + } + } +} +```` + +## Customize Discovery and Token Requests + +Use `IdentityModelHttpRequestMessageOptions.ConfigureHttpRequestMessage` to add headers or otherwise customize the request messages: + +````csharp +Configure(options => +{ + options.ConfigureHttpRequestMessage = request => + { + request.Headers.TryAddWithoutValidation( + "X-Internal-Client", + "MyProject" + ); + }; +}); +```` + +The callback runs for discovery, client-credentials, password and device-authorization request messages created by the default authentication service. diff --git a/docs/en/framework/api-development/swagger.md b/docs/en/framework/api-development/swagger.md index 03808d7869..f07f049555 100644 --- a/docs/en/framework/api-development/swagger.md +++ b/docs/en/framework/api-development/swagger.md @@ -110,6 +110,21 @@ services.AddAbpSwaggerGen( ) ``` +### Enum and Schema ID Helpers + +ABP provides two additional `SwaggerGenOptions` helpers: + +* `UserFriendlyEnums()` changes enum schemas from numeric values to string enum names, making generated contracts easier for clients to consume. +* `CustomAbpSchemaIds()` uses full type names and includes generic argument names to avoid schema ID collisions. + +```csharp +services.AddAbpSwaggerGen(options => +{ + options.UserFriendlyEnums(); + options.CustomAbpSchemaIds(); +}); +``` + ## Using Swagger with OAUTH For non MVC/Tiered applications, we need to configure Swagger with OAUTH to handle authorization. diff --git a/docs/en/framework/architecture/domain-driven-design/repositories.md b/docs/en/framework/architecture/domain-driven-design/repositories.md index eed0529112..f9b1c7d4dd 100644 --- a/docs/en/framework/architecture/domain-driven-design/repositories.md +++ b/docs/en/framework/architecture/domain-driven-design/repositories.md @@ -249,7 +249,7 @@ public virtual async Task> GetListAsync() ABP uses dynamic proxying to make these attributes work. There are some rules here: -* If you are **not injecting** the service over an interface (like `IPersonAppService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](../../../dynamic-proxying-interceptors.md) system can not work. +* If you are **not injecting** the service over an interface (like `IPersonAppService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](../../infrastructure/interceptors.md) system can not work. * Only `async` methods (methods returning a `Task` or `Task`) are intercepted. > Change tracking behavior doesn't affect tracking entity objects returned from `InsertAsync` and `UpdateAsync` methods. The objects returned from these methods are always tracked (if the underlying provider has the change tracking feature) and any change you make to these objects are saved into the database. @@ -309,9 +309,10 @@ Methods: - `GetListAsync()` - `GetQueryableAsync()` -- `WithDetails()` 1 overload - `WithDetailsAsync()` 1 overload +The synchronous `WithDetails()` overload is obsolete. Use `WithDetailsAsync()` for new code. + Whereas the `IReadOnlyBasicRepository` provides the following methods: - `GetCountAsync()` diff --git a/docs/en/framework/architecture/domain-driven-design/unit-of-work.md b/docs/en/framework/architecture/domain-driven-design/unit-of-work.md index 697d7dba80..63ee51149a 100644 --- a/docs/en/framework/architecture/domain-driven-design/unit-of-work.md +++ b/docs/en/framework/architecture/domain-driven-design/unit-of-work.md @@ -59,7 +59,7 @@ Configure(options => ### Option Properties * `TransactionBehavior` (`enum`: `UnitOfWorkTransactionBehavior`). A global point to configure the transaction behavior. Default value is `Auto` and work as explained in the "*Database Transaction Behavior*" section above. You can enable (even for HTTP GET requests) or disable transactions with this option. -* `TimeOut` (`int?`): Used to set the timeout value for UOWs. **Default value is `null`** and uses to the default of the underlying database provider. +* `Timeout` (`int?`): Used to set the timeout value for UOWs. **Default value is `null`** and uses to the default of the underlying database provider. * `IsolationLevel` (`IsolationLevel?`): Used to set the [isolation level](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) of the database transaction, if the UOW is transactional. ## Controlling the Unit Of Work @@ -93,7 +93,7 @@ Then `MyService` (and any class derived from it) methods will be UOW. However, there are **some rules should be followed** in order to make it working; -* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](../../../dynamic-proxying-interceptors.md) system can not work). +* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](../../infrastructure/interceptors.md) system can not work). * Only `async` methods (methods returning a `Task` or `Task`) are intercepted. So, sync methods can not start a UOW. > Notice that if `FooAsync` is called inside a UOW scope, then it already participates to the UOW without needing to the `IUnitOfWorkEnabled` or any other configuration. @@ -156,13 +156,13 @@ namespace AbpDemo Again, the **same rules** are valid here: -* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](../../../dynamic-proxying-interceptors.md) system can not work). +* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](../../infrastructure/interceptors.md) system can not work). * Only `async` methods (methods returning a `Task` or `Task`) are intercepted. So, sync methods can not start a UOW. #### UnitOfWorkAttribute Properties * `IsTransactional` (`bool?`): Used to set whether the UOW should be transactional or not. **Default value is `null`**. if you leave it `null`, it is determined automatically based on the conventions and the configuration. -* `TimeOut` (`int?`): Used to set the timeout value for this UOW. **Default value is `null`** and fallbacks to the default configured value. +* `Timeout` (`int?`): Used to set the timeout value for this UOW. **Default value is `null`** and fallbacks to the default configured value. * `IsolationLevel` (`IsolationLevel?`): Used to set the [isolation level](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) of the database transaction, if the UOW is transactional. If not set, uses the default configured value. * `IsDisabled` (`bool`): Used to disable the UOW for the current method/class. @@ -234,7 +234,7 @@ namespace AbpDemo * `requiresNew` (`bool`): Set `true` to ignore the surrounding unit of work and start a new UOW with the provided options. **Default value is `false`. If it is `false` and there is a surrounding UOW, `Begin` method doesn't actually begin a new UOW, but silently participates to the existing UOW.** * `isTransactional` (`bool`). Default value is `false`. * `isolationLevel` (`IsolationLevel?`): Used to set the [isolation level](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) of the database transaction, if the UOW is transactional. If not set, uses the default configured value. -* `TimeOut` (`int?`): Used to set the timeout value for this UOW. **Default value is `null`** and fallbacks to the default configured value. +* `timeout` (`int?`): Used to set the timeout value for this UOW. **Default value is `null`** and fallbacks to the default configured value. ### The Current Unit Of Work diff --git a/docs/en/framework/architecture/modularity/basics.md b/docs/en/framework/architecture/modularity/basics.md index 727c40f517..560d794f96 100644 --- a/docs/en/framework/architecture/modularity/basics.md +++ b/docs/en/framework/architecture/modularity/basics.md @@ -145,6 +145,36 @@ You can also perform startup logic if your module requires it > These methods have asynchronous versions too, and if you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. +#### Custom Module Lifecycle Contributors + +`IModuleLifecycleContributor` is an advanced extension point for adding an application-wide initialization or shutdown phase. A contributor is invoked for every loaded module. Initialization follows module dependency order, while shutdown processes modules in reverse order. + +Derive from `ModuleLifecycleContributorBase` and override only the phases you need: + +````csharp +public class MyModuleLifecycleContributor : ModuleLifecycleContributorBase +{ + public override Task InitializeAsync( + ApplicationInitializationContext context, + IAbpModule module) + { + // Run initialization logic for the current module. + return Task.CompletedTask; + } +} +```` + +Add the contributor type to `AbpModuleLifecycleOptions.Contributors`: + +````csharp +Configure(options => +{ + options.Contributors.Add(); +}); +```` + +Contributor order is the order of the `Contributors` list. The four built-in contributors run the pre-initialization, initialization, post-initialization and shutdown callbacks. + ### Application Shutdown Lastly, you can override ``OnApplicationShutdown`` method if you want to execute some code while application is being shutdown. diff --git a/docs/en/framework/architecture/modularity/extending/module-entity-extensions.md b/docs/en/framework/architecture/modularity/extending/module-entity-extensions.md index 076238b444..d99ea4de8e 100644 --- a/docs/en/framework/architecture/modularity/extending/module-entity-extensions.md +++ b/docs/en/framework/architecture/modularity/extending/module-entity-extensions.md @@ -273,6 +273,33 @@ property => Use `property.UI.OnCreateForm` and `property.UI.OnEditForm` to control forms too. If a property is required, but not added to the create form, you definitely get a validation exception, so use this option carefully. But a required property may not be in the edit form if that's your requirement. +### Conditional Availability + +An extension property can carry global-feature, tenant-feature and permission policies. Policy-aware object-extension consumers use this metadata to decide whether the property is available for the current application and user. + +The following example requires either of two permissions: + +````csharp +property => +{ + property.Policy.Permissions.PermissionNames = + [ + "MyProject.Users.Manage", + "MyProject.Users.ManageExtendedProfile" + ]; +} +```` + +The available policy groups are: + +* `Policy.GlobalFeatures.Features` for application-wide global features. +* `Policy.Features.Features` for the current tenant's features. +* `Policy.Permissions.PermissionNames` for the current principal's permissions. + +`RequiresAll` is `false` by default for each group, so any configured name in that group is sufficient. Set the corresponding `RequiresAll` property to `true` to require every name. When more than one group is configured, every configured group must pass. An empty group imposes no restriction. + +These policies do not replace the `UI` and `Api` availability options. They add current feature and permission checks to consumers that evaluate extension-property policies. + ### UI Order When you define a property, it appears on the data table, create and edit forms on the related UI page. However, you can control its order. Example: diff --git a/docs/en/framework/data/entity-framework-core/index.md b/docs/en/framework/data/entity-framework-core/index.md index 418c040565..25b3e00b6b 100644 --- a/docs/en/framework/data/entity-framework-core/index.md +++ b/docs/en/framework/data/entity-framework-core/index.md @@ -651,7 +651,7 @@ In addition to the read-only repositories, ABP allows to manually control the ch ## Access to the EF Core API -In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example: +In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContextAsync()` or `GetDbSetAsync()` extension methods. Example: ````csharp public async Task TestAsync() diff --git a/docs/en/framework/data/memorydb/index.md b/docs/en/framework/data/memorydb/index.md new file mode 100644 index 0000000000..247d2b1e8f --- /dev/null +++ b/docs/en/framework/data/memorydb/index.md @@ -0,0 +1,77 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to use ABP's in-memory database provider, register a MemoryDb context and repositories, and customize entity serialization." +} +``` + +# In-Memory Database Provider + +The `Volo.Abp.MemoryDb` package implements ABP repositories with an in-process database. It is useful for tests and other non-durable scenarios. Data is kept in the application process and is lost when the process stops. + +## Installation + +Install the `Volo.Abp.MemoryDb` NuGet package in the data-access project: + +````shell +abp add-package Volo.Abp.MemoryDb +```` + +The command adds the package and the `AbpMemoryDbModule` dependency to the module class. You can also configure the dependency manually as shown in the next section. + +## Configure the Module + +Add `AbpMemoryDbModule` as a dependency of your module: + +````csharp +[DependsOn(typeof(AbpMemoryDbModule))] +public class MyDataModule : AbpModule +{ +} +```` + +## Create a MemoryDb Context + +Derive a class from `MemoryDbContext` and return the entity types managed by the context: + +````csharp +public class MyMemoryDbContext : MemoryDbContext +{ + private static readonly Type[] EntityTypes = + [ + typeof(Book), + typeof(Author) + ]; + + public override IReadOnlyList GetEntityTypes() + { + return EntityTypes; + } +} +```` + +Register the context in the `ConfigureServices` method of your module: + +````csharp +context.Services.AddMemoryDbContext(options => +{ + options.AddDefaultRepositories(); +}); +```` + +`AddDefaultRepositories()` registers default repositories for the aggregate roots returned by the context. Pass `includeAllEntities: true` when default repositories are also needed for other entity types. + +MemoryDb repositories use ABP's unit-of-work-aware database provider. Repository operations require an active [unit of work](../../architecture/domain-driven-design/unit-of-work.md). + +## JSON Serialization + +MemoryDb stores serialized entity values. Configure `Utf8JsonMemoryDbSerializerOptions` to customize the underlying `System.Text.Json` options: + +````csharp +Configure(options => +{ + options.JsonSerializerOptions.Converters.Add( + new MyEntityJsonConverter() + ); +}); +```` diff --git a/docs/en/framework/data/mongodb/index.md b/docs/en/framework/data/mongodb/index.md index b609e22963..5349decde4 100644 --- a/docs/en/framework/data/mongodb/index.md +++ b/docs/en/framework/data/mongodb/index.md @@ -352,6 +352,31 @@ services: ### Advanced Topics +#### MongoDB DateTime Serialization + +ABP applies a clock-aware MongoDB serializer to writable `DateTime` and nullable `DateTime` properties in ABP entity mappings by default. It uses the configured [clock](../../infrastructure/timing.md) kind when serializing these properties. Disable this handling when the application configures its own serialization for the mapped properties: + +```csharp +Configure(options => +{ + options.UseAbpClockHandleDateTime = false; +}); +``` + +#### Configuring MongoClientSettings + +`AbpMongoDbContextOptions.MongoClientSettingsConfigurer` runs before ABP creates a `MongoClient`. Use it for driver settings that are not part of the connection string, such as timeouts or TLS configuration: + +```csharp +Configure(options => +{ + options.MongoClientSettingsConfigurer = settings => + { + settings.ConnectTimeout = TimeSpan.FromSeconds(10); + }; +}); +``` + ### Controlling the Multi-Tenancy If your solution is [multi-tenant](../../architecture/multi-tenancy), tenants may have **separate databases**, you have **multiple** `DbContext` classes in your solution and some of your `DbContext` classes should be usable **only from the host side**, it is suggested to add `[IgnoreMultiTenancy]` attribute on your `DbContext` class. In this case, ABP guarantees that the related `DbContext` always uses the host [connection string](../../fundamentals/connection-strings.md), even if you are in a tenant context. diff --git a/docs/en/framework/fundamentals/application-startup.md b/docs/en/framework/fundamentals/application-startup.md index 4c323c1c52..acfe4bf2aa 100644 --- a/docs/en/framework/fundamentals/application-startup.md +++ b/docs/en/framework/fundamentals/application-startup.md @@ -213,6 +213,11 @@ We've passed a lambda method to configure the `ApplicationName` option. Here's a * `ApplicationName`: A human-readable name for the application. It is a unique value for an application. * `Configuration`: Can be used to setup the [application configuration](./configuration.md) when it is not provided by the hosting system. It is not needed for ASP.NET Core and other .NET hosted applications. However, if you've used `AbpApplicationFactory` with an internal service provider, you can use this option to configure how the application configuration is built. + * `FileName` (default: `appsettings`), `Optional` (default: `true`) and `ReloadOnChange` (default: `true`) configure the JSON files. + * The builder loads `.json` first and then the optional `.secrets.json` file. When `EnvironmentName` is set, it loads `..json` after both files. + * `EnvironmentName` adds the corresponding environment-specific JSON file. In the `Development` environment, `UserSecretsId` is used before `UserSecretsAssembly` when both are set. + * `BasePath` changes the configuration file base path. The current directory is used by default. + * `EnvironmentVariablesPrefix` filters environment variables, and `CommandLineArgs` adds command-line configuration after environment variables. * `Environment`: Environment name for the application. * `PlugInSources`: A list of plugin sources. See the [Plug-In Modules documentation](../architecture/modularity/plugin-modules.md) to learn how to work with plugins. * `Services`: The `IServiceCollection` object that can be used to register service dependencies. You generally don't need that, because you configure your services in your [module class](../architecture/modularity/basics.md). However, it can be used while writing extension methods for the `AbpApplicationCreationOptions` class. diff --git a/docs/en/framework/fundamentals/caching.md b/docs/en/framework/fundamentals/caching.md index f52cd9e07d..8f7b85e37e 100644 --- a/docs/en/framework/fundamentals/caching.md +++ b/docs/en/framework/fundamentals/caching.md @@ -214,6 +214,58 @@ public class BookService : ITransientDependency } ```` +## Hybrid Cache + +ABP registers Microsoft's `HybridCache` together with typed ABP wrappers when the `Volo.Abp.Caching` module is used. Hybrid caching keeps a local in-process cache and can use the configured `IDistributedCache` as a secondary cache. + +Use `IHybridCache` for string keys or `IHybridCache` for another key type: + +````csharp +using Volo.Abp.Caching.Hybrid; +using Volo.Abp.DependencyInjection; + +public class BookCacheItem +{ + public string Name { get; set; } = string.Empty; +} + +public class BookService : ITransientDependency +{ + private readonly IHybridCache _cache; + + public BookService(IHybridCache cache) + { + _cache = cache; + } + + public Task GetAsync(Guid bookId) + { + return _cache.GetOrCreateAsync( + bookId, + () => LoadBookAsync(bookId) + ); + } + + private Task LoadBookAsync(Guid bookId) + { + // Load the item from its source. + throw new NotImplementedException(); + } +} +```` + +The typed wrapper uses the same cache-name and tenant-aware key normalization conventions as ABP's distributed cache. Use `CacheName` on the cache item type to set its cache name and `IgnoreMultiTenancy` to share entries between tenants. A custom key type is converted with its `ToString()` method. + +The main operations are `GetOrCreateAsync`, `SetAsync`, `RemoveAsync` and `RemoveManyAsync`. Each operation has a nullable `hideErrors` argument. When it is `null`, `AbpHybridCacheOptions.HideErrors` is used; its default is `true`. Hidden errors are logged and sent to the exception notification system. `GetOrCreateAsync` can return `null` when a cache error is hidden. + +### Hybrid Cache and Unit of Work + +The hybrid-cache methods have a `considerUow` argument that defaults to `false`. When it is `true` and a unit of work is active, cache changes are visible inside that unit of work and are applied to the real cache only after the unit of work completes successfully. A rolled-back unit of work does not apply those changes. + +### Hybrid Cache Entry Options + +Pass `HybridCacheEntryOptions` to an individual `SetAsync` call when it needs a custom expiration. `AbpHybridCacheOptions.GlobalHybridCacheEntryOptions` is used by `SetAsync` when no per-call options are supplied, and `ConfigureCache()` can set the corresponding default for a cache item type. + ## Configuration ### AbpDistributedCacheOptions @@ -233,7 +285,7 @@ Configure(options => * `HideErrors` (`bool`, default: `true`): Enables or disables hiding errors when reading from or writing to the cache server. In the **development** environment, this option is **disabled** to help developers detect and fix any cache server issues. -* `KeyPrefix` (`string`, default: `null`): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items. +* `KeyPrefix` (`string`, default: an empty string): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items. * `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. The default value uses the `SlidingExpiration` as 20 minutes. ## Error Handling diff --git a/docs/en/framework/fundamentals/dependency-injection.md b/docs/en/framework/fundamentals/dependency-injection.md index 2e2e3e377a..9ceedf0ebf 100644 --- a/docs/en/framework/fundamentals/dependency-injection.md +++ b/docs/en/framework/fundamentals/dependency-injection.md @@ -547,7 +547,7 @@ public class AppModule : AbpModule This example simply checks if the service class has `MyLogAttribute` attribute and adds `MyLogInterceptor` to the interceptor list if so. -> Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](../../dynamic-proxying-interceptors.md) of dynamic proxying / interceptors. +> Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](../infrastructure/interceptors.md) of dynamic proxying / interceptors. ### IServiceCollection.OnActivated Event diff --git a/docs/en/framework/fundamentals/exception-handling.md b/docs/en/framework/fundamentals/exception-handling.md index 5ccfcf124a..f9e659f29b 100644 --- a/docs/en/framework/fundamentals/exception-handling.md +++ b/docs/en/framework/fundamentals/exception-handling.md @@ -344,6 +344,20 @@ Here, a list of the options you can configure: * `SendExceptionsDetailsToClients` (default: `false`): You can enable or disable sending exception details to the client. * `SendStackTraceToClients` (default: `true`): You can enable or disable sending the stack trace of exception to the client. If you want to send the stack trace to the client, you must set both `SendStackTraceToClients` and `SendExceptionsDetailsToClients` options to `true` otherwise, the stack trace will not be sent to the client. +* `SendExceptionDataToClientTypes`: Exception types whose `Data` dictionary is copied to the remote error response. The default list contains `IBusinessException`, so business exception data is sent to clients. Derived and implementing types are matched. +* `ExcludeExceptionFromLoggerSelectors`: Predicates that suppress matching exceptions from the ABP exception log. Add a selector when an expected exception should still produce an error response but should not be logged by the exception pipeline. + +Example: + +````csharp +Configure(options => +{ + options.SendExceptionDataToClientTypes.Add(typeof(MyClientVisibleException)); + options.ExcludeExceptionFromLoggerSelectors.Add( + exception => exception is MyExpectedException + ); +}); +```` ## See Also diff --git a/docs/en/framework/fundamentals/localization.md b/docs/en/framework/fundamentals/localization.md index 5fd243eeef..2e0121c172 100644 --- a/docs/en/framework/fundamentals/localization.md +++ b/docs/en/framework/fundamentals/localization.md @@ -108,9 +108,9 @@ You can also use nesting or array in localization files, like this: "Hello": { "World": "Hello World!" }, - "Hi":[ - "Bye": "Bye World!" - "Hello": "Hello World!" + "Hi": [ + "Bye World!", + "Hello World!" ] } } @@ -189,6 +189,21 @@ public class TestResource See the Getting Localized Test / Client Side section below. +### Non-Typed Resources + +Most localization resources are represented by a class, which allows you to inject `IStringLocalizer`. You can also register a resource by name without creating a resource class. This is useful when a resource is identified only by its name. An external localization store can also return a non-typed resource for a resource name discovered at runtime. + +````csharp +Configure(options => +{ + options.Resources + .Add("CountryNames", "en") + .AddVirtualJson("/Localization/Resources/CountryNames"); +}); +```` + +Use `IStringLocalizerFactory` to access a non-typed resource, as described in the *Creating A Localizer By Resource Name* section below. + ### Inherit From Other Resources A resource can inherit from other resources which makes possible to re-use existing localization strings without referring the existing resource. Example: @@ -215,6 +230,18 @@ services.Configure(options => * A resource may inherit from multiple resources. * If the new resource defines the same localized string, it overrides the string. +A resource can also inherit from a typed or non-typed resource by its resource name: + +````csharp +Configure(options => +{ + options.Resources + .Add("en") + .AddVirtualJson("/Localization/Resources/Test") + .AddBaseResources("CountryNames"); +}); +```` + ### Extending Existing Resource Inheriting from a resource creates a new resource without modifying the existing one. In some cases, you may want to not create a new resource but directly extend an existing resource. Example: @@ -230,6 +257,51 @@ services.Configure(options => * If an extension file defines the same localized string, it overrides the string. +### Culture Fallback + +ABP searches for a localized string in the following order: + +1. The requested culture of the current resource, such as `en-US`. +2. The base culture, such as `en`, when `TryToGetFromBaseCulture` is enabled. +3. The default culture configured for the resource when `TryToGetFromDefaultCulture` is enabled. +4. The inherited resources, in their configured order. Each inherited resource applies the same culture fallback rules. +5. The localization key itself, returned with `ResourceNotFound` set to `true`. + +Both fallback options are enabled by default. You can disable them independently: + +````csharp +Configure(options => +{ + options.TryToGetFromBaseCulture = false; + options.TryToGetFromDefaultCulture = false; +}); +```` + +### Global Resource Contributors + +`AbpLocalizationOptions.GlobalContributors` adds an `ILocalizationResourceContributor` implementation to every localization resource: + +````csharp +Configure(options => +{ + options.GlobalContributors.Add(); +}); +```` + +The contributor type must have a parameterless constructor. Its `Initialize` method receives a `LocalizationResourceInitializationContext`, which provides the resource and the application service provider. + +Contributors are order-sensitive. A lookup starts with the last registered contributor, so a later contributor overrides an earlier contributor when both provide the same key. Global contributors are appended after contributors configured directly on a resource. + +### External Localization Stores + +Replace `IExternalLocalizationStore` when localization resources need to be discovered at runtime or loaded from an external system. The default `NullExternalLocalizationStore` does not provide any resources. + +The string localizer factory first searches the resources registered in `AbpLocalizationOptions.Resources`. If it cannot find the requested resource name, it queries `IExternalLocalizationStore`. The store exposes synchronous and asynchronous methods for retrieving a resource by name, enumerating resource names and enumerating resources. + +The factory caches the localizer after it resolves a resource name. Changing the resource object returned by the store does not make the factory resolve that name again. Use dynamic contributors when the localization values themselves need to change while the application is running. + +Use the standard [dependency injection service replacement](dependency-injection.md#replace-a-service) mechanism to replace the default implementation. + ## Getting the Localized Texts Getting the localized text is pretty standard. @@ -255,12 +327,70 @@ public class MyService : ITransientDependency } ```` +### Creating A Localizer By Resource Name + +Use `IStringLocalizerFactory` when the resource type is not available or the resource is registered by name: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IStringLocalizerFactory _localizerFactory; + + public MyService(IStringLocalizerFactory localizerFactory) + { + _localizerFactory = localizerFactory; + } + + public string GetCountryName() + { + var localizer = _localizerFactory.CreateByResourceName("CountryNames"); + return localizer["USA"]; + } +} +```` + +`CreateByResourceName` throws an `AbpException` when the resource cannot be found. Use `CreateByResourceNameOrNull` when a missing resource is expected. `CreateByResourceNameAsync` and `CreateByResourceNameOrNullAsync` are available for external stores that load resources asynchronously. + +### Serializing Localizable Strings + +Use `ILocalizableStringSerializer` when an `ILocalizableString` needs to be stored as a string and reconstructed later: + +````csharp +var serialized = localizableStringSerializer.Serialize( + LocalizableString.Create("HelloWorld") +); + +var localizableString = localizableStringSerializer.Deserialize(serialized!); +```` + +The default serializer uses `L:,` for `LocalizableString` and `F:` for `FixedLocalizableString`. A value without a recognized prefix is deserialized as a `FixedLocalizableString`. An invalid `L:` value throws an `AbpException`. Serializing `null` returns `null`; serializing another `ILocalizableString` implementation throws an `AbpException`. + ### Format Arguments Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`. > Refer to the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) for details about using the localization. +### Getting All Localization Strings + +The standard `GetAllStrings(includeParentCultures)` method can include values from the resource's default and base cultures. ABP also provides an overload to control inherited resources and dynamic contributors independently: + +````csharp +var strings = localizer.GetAllStrings( + includeParentCultures: true, + includeBaseLocalizers: true, + includeDynamicContributors: false +); +```` + +* `includeParentCultures` includes values from the resource's default culture and the base culture of the current UI culture. Values from the current UI culture override them. +* `includeBaseLocalizers` includes strings from inherited resources. Values from the current resource override inherited values. +* `includeDynamicContributors` includes contributors whose `IsDynamic` property is `true`. + +`includeParentCultures` controls this bulk enumeration independently of `TryToGetFromBaseCulture` and `TryToGetFromDefaultCulture`, which control single-string lookups. + +Use `GetAllStringsAsync` with the same flags when a contributor retrieves strings asynchronously. + ### Using In A Razor View/Page Use `IHtmlLocalizer` in razor views/pages; @@ -331,6 +461,29 @@ Configure(options => }); ``` +### Mapping Culture Names For Client Packages + +Client libraries sometimes use a culture name or localization file name that differs from the application's culture name. Use `AddLanguagesMapOrUpdate` to map the culture passed to a package, and `AddLanguageFilesMapOrUpdate` to map the package's localization file name: + +```csharp +Configure(options => +{ + options.AddLanguagesMapOrUpdate( + "MyClientPackage", + new NameValue("zh-Hans", "zh-CN") + ); + + options.AddLanguageFilesMapOrUpdate( + "MyClientPackage", + new NameValue("zh-Hans", "zh-CN") + ); +}); +``` + +Mappings are scoped by package name. When a mapping is not defined, ABP uses the original culture name. + +The `NameValue` name is the application culture and its value is the culture or file name expected by the client package. Use the package's own package-name constant when it provides one. + ## URL-Based Localization ABP supports embedding the culture code directly in the URL path (e.g. `/en/products`, `/zh-Hans/about`), which is useful for SEO-friendly and shareable localized URLs. See the [URL-Based Localization](./url-based-localization.md) document for details. diff --git a/docs/en/framework/fundamentals/logging.md b/docs/en/framework/fundamentals/logging.md index 94056400c7..7a1928b4c6 100644 --- a/docs/en/framework/fundamentals/logging.md +++ b/docs/en/framework/fundamentals/logging.md @@ -11,3 +11,20 @@ ABP doesn't implement any logging infrastructure. It uses the [ASP.NET Core's lo > .NET Core's logging system is actually independent from the ASP.NET Core. It is usable in any type of application. +## Serilog Request Enrichers + +When the ABP ASP.NET Core Serilog integration is installed, its middleware enriches request log events with the current `TenantId`, `UserId`, `ClientId` and `CorrelationId` values when they are available. + +`AbpAspNetCoreSerilogOptions.EnricherPropertyNames` can align these property names with an existing observability schema: + +````csharp +Configure(options => +{ + options.EnricherPropertyNames.TenantId = "tenant_id"; + options.EnricherPropertyNames.UserId = "user_id"; + options.EnricherPropertyNames.ClientId = "client_id"; + options.EnricherPropertyNames.CorrelationId = "correlation_id"; +}); +```` + +The default names are `TenantId`, `UserId`, `ClientId` and `CorrelationId`. diff --git a/docs/en/framework/fundamentals/options.md b/docs/en/framework/fundamentals/options.md index d18c89c675..2bde471f90 100644 --- a/docs/en/framework/fundamentals/options.md +++ b/docs/en/framework/fundamentals/options.md @@ -123,3 +123,56 @@ public override void ConfigureServices(ServiceConfigurationContext context) } ```` +## Dynamic Options + +Standard options are created synchronously. `AbpDynamicOptionsManager` can override named option values asynchronously from a runtime source, such as the setting system. + +Derive a manager and implement `OverrideOptionsAsync`: + +````csharp +public class MyDynamicOptionsManager : AbpDynamicOptionsManager +{ + private readonly ISettingProvider _settingProvider; + + public MyDynamicOptionsManager( + IOptionsFactory factory, + ISettingProvider settingProvider) + : base(factory) + { + _settingProvider = settingProvider; + } + + protected override async Task OverrideOptionsAsync( + string name, + MyOptions options) + { + options.Value1 = await _settingProvider.GetAsync("MyOptions.Value1"); + } +} +```` + +Register the manager for the option type: + +````csharp +context.Services.AddAbpDynamicOptions(); +```` + +This replaces `IOptions` and `IOptionsSnapshot` with the scoped dynamic manager. Call the `IOptions.SetAsync` extension before reading the value when you need to apply the asynchronous override: + +````csharp +public class MyService : ITransientDependency +{ + private readonly IOptions _options; + + public MyService(IOptions options) + { + _options = options; + } + + public async Task GetValueAsync() + { + await _options.SetAsync(); + return _options.Value.Value1; + } +} +```` diff --git a/docs/en/framework/fundamentals/validation.md b/docs/en/framework/fundamentals/validation.md index 78e93e59f5..4d9d8a4d45 100644 --- a/docs/en/framework/fundamentals/validation.md +++ b/docs/en/framework/fundamentals/validation.md @@ -117,7 +117,7 @@ namespace Acme.BookStore } ```` -> ABP uses the [dynamic proxying / interception](../../dynamic-proxying-interceptors.md) system to perform the validation. In order to make it working, your method should be **virtual** or your service should be injected and used over an **interface** (like `IMyService`). +> ABP uses the [dynamic proxying / interception](../infrastructure/interceptors.md) system to perform the validation. In order to make it working, your method should be **virtual** or your service should be injected and used over an **interface** (like `IMyService`). #### Enabling/Disabling Validation @@ -142,6 +142,20 @@ public class InputClass } ```` +If a class has `[DisableValidation]`, add `[EnableValidation]` to a method to enable automatic method validation for that method: + +````csharp +[DisableValidation] +public class MyService +{ + [EnableValidation] + public virtual Task UpdateAsync(MyInput input) + { + //... + } +} +```` + ### AbpValidationException Once ABP determines a validation error, it throws an exception of type `AbpValidationException`. Your application code can throw `AbpValidationException`, but most of the times it is not needed. @@ -180,6 +194,17 @@ public class MyObjectValidationContributor * Remember to register your class to the [DI](./dependency-injection.md) (implementing `ITransientDependency` does it just like in this example) * ABP will automatically discover your class and use on any type of object validation (including automatic method call validation). +### Ignoring Types During Recursive Validation + +`AbpValidationOptions.IgnoredTypes` prevents matching values from being recursively validated by the default data annotation contributor. Derived and implementing types are also matched. + +````csharp +Configure(options => +{ + options.IgnoredTypes.Add(typeof(MyInfrastructureValue)); +}); +```` + ### IMethodInvocationValidator `IMethodInvocationValidator` is used to validate a method call. It internally uses the `IObjectValidator` to validate objects passes to the method call. You normally don't need to this service since it is automatically used by the framework, but you may want to reuse or replace it on your application in rare cases. diff --git a/docs/en/framework/infrastructure/artificial-intelligence/microsoft-agent-framework.md b/docs/en/framework/infrastructure/artificial-intelligence/microsoft-agent-framework.md index d3e7d128fc..8d83bfc714 100644 --- a/docs/en/framework/infrastructure/artificial-intelligence/microsoft-agent-framework.md +++ b/docs/en/framework/infrastructure/artificial-intelligence/microsoft-agent-framework.md @@ -86,7 +86,7 @@ public class CommentSummarization > [!NOTE] > If you don't specify the workspace name, the full name of the class will be used as the workspace name. -You can resolve generic versions of `IChatClient` and `IChatClientAccessor` services for a specific workspace as generic arguments. If Chat Client is not configured for a workspace, you will get `null` from the accessor services. You should check the accessor before using it. This applies only for specified workspaces. Another workspace may have a configured Chat Client. +You can resolve generic versions of `IChatClient` and `IChatClientAccessor` services for a specific workspace as generic arguments. If a Chat Client is not configured for the specified workspace, both services fall back to the default workspace. `IChatClientAccessor.ChatClient` is `null` only when neither the specified workspace nor the default workspace has a configured Chat Client. Resolving `IChatClient` requires one of them to be configured. `IChatClient` or `IChatClientAccessor` can be resolved to access a specific workspace's chat client. This is a typed chat client and can be configured separately from the default chat client. @@ -215,4 +215,4 @@ public class MyProjectModule : AbpModule - [Usage of Microsoft.Extensions.AI](./microsoft-extensions-ai.md) - [Usage of Semantic Kernel](./microsoft-semantic-kernel.md) - [Microsoft Agent Framework Overview](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) -- [AI Samples for .NET](https://learn.microsoft.com/en-us/samples/dotnet/ai-samples/ai-samples/) \ No newline at end of file +- [AI Samples for .NET](https://learn.microsoft.com/en-us/samples/dotnet/ai-samples/ai-samples/) diff --git a/docs/en/framework/infrastructure/audit-logging.md b/docs/en/framework/infrastructure/audit-logging.md index ee2cdf7e41..9c748c8732 100644 --- a/docs/en/framework/infrastructure/audit-logging.md +++ b/docs/en/framework/infrastructure/audit-logging.md @@ -44,10 +44,10 @@ Configure(options => Here, a list of the options you can configure: * `IsEnabled` (default: `true`): A root switch to enable or disable the auditing system. Other options is not used if this value is `false`. -* `HideErrors` (default: `true`): Audit log system hides and write regular [logs](../fundamentals/localization.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors. +* `HideErrors` (default: `true`): Audit log system hides and write regular [logs](../fundamentals/logging.md) if any error occurs while saving the audit log objects. If saving the audit logs is critical for your system, set this to `false` to throw exception in case of hiding the errors. * `IsEnabledForAnonymousUsers` (default: `true`): If you want to write audit logs only for the authenticated users, set this to `false`. If you save audit logs for anonymous users, you will see `null` for `UserId` values for these users. * `AlwaysLogOnException` (default: `true`): If you set to true, it always saves the audit log on an exception/error case without checking other options (except `IsEnabled`, which completely disables the audit logging). -* `IsEnabledForIntegrationService` (default: `false`): Audit Logging is disabled for [integration services](../api-development/integration-services.md) by default. Set this property as `true` to enable it. +* `IsEnabledForIntegrationServices` (default: `false`): Audit Logging is disabled for [integration services](../api-development/integration-services.md) by default. Set this property as `true` to enable it. * `IsEnabledForGetRequests` (default: `false`): HTTP GET requests should not make any change in the database normally and audit log system doesn't save audit log objects for GET request. Set this to `true` to enable it also for the GET requests. * `DisableLogActionInfo` (default: `false`):If you set to true, Will no longer log `AuditLogActionInfo`. * `ApplicationName`: If multiple applications are saving audit logs into a single database, set this property to your application name, so you can distinguish the logs of different applications. If you don't set, it will set from the `IApplicationInfoAccessor.ApplicationName` value, which is the entry assembly name by default. diff --git a/docs/en/framework/infrastructure/background-jobs/index.md b/docs/en/framework/infrastructure/background-jobs/index.md index 57bffefbe0..2d70a9ea3c 100644 --- a/docs/en/framework/infrastructure/background-jobs/index.md +++ b/docs/en/framework/infrastructure/background-jobs/index.md @@ -150,7 +150,7 @@ Configure(options => { options.GetBackgroundJobName = (jobType) => { - if (jobTyep == typeof(EmailSendingArgs)) + if (jobType == typeof(EmailSendingArgs)) { return "emails"; } @@ -504,4 +504,4 @@ See pre-built job manager alternatives: * [TickerQ Background Job Manager](./tickerq.md) ## See Also -* [Background Workers](../background-workers) \ No newline at end of file +* [Background Workers](../background-workers) diff --git a/docs/en/framework/infrastructure/background-workers/hangfire.md b/docs/en/framework/infrastructure/background-workers/hangfire.md index 9b65a488a9..887266e95c 100644 --- a/docs/en/framework/infrastructure/background-workers/hangfire.md +++ b/docs/en/framework/infrastructure/background-workers/hangfire.md @@ -47,6 +47,16 @@ public class YourModule : AbpModule > Hangfire background worker integration provides an adapter `HangfirePeriodicBackgroundWorkerAdapter` to automatically load any `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived classes as `IHangfireBackgroundWorker` instances. This allows you to still to easily switch over to use Hangfire as the background manager even you have existing background workers that are based on the [default background workers implementation](../background-workers). +The adapter uses UTC for recurring schedules by default and uses the default Hangfire queue when no queue is specified. You can configure both values globally for adapted periodic workers: + +````csharp +Configure(options => +{ + options.TimeZone = TimeZoneInfo.Local; + options.Queue = "periodic"; +}); +```` + ## Configuration You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package). diff --git a/docs/en/framework/infrastructure/csrf-anti-forgery.md b/docs/en/framework/infrastructure/csrf-anti-forgery.md index f1ad1feb03..675b6b6b18 100644 --- a/docs/en/framework/infrastructure/csrf-anti-forgery.md +++ b/docs/en/framework/infrastructure/csrf-anti-forgery.md @@ -47,6 +47,7 @@ That's all. The systems works smoothly. * `TokenCookie`: Can be used to configure the cookie details. This cookie is used to store the antiforgery token value in the client side, so clients can read it and sends the value as the HTTP header. Default cookie name is `XSRF-TOKEN`, expiration time is 10 years (yes, ten years! It should be a value longer than the authentication cookie max life time, for the security). * `AuthCookieSchemaName`: The name of the authentication cookie used by your application. Default value is `Identity.Application` (which becomes `AspNetCore.Identity.Application` on runtime). The default value properly works with the ABP startup templates. **If you change the authentication cookie name, you also must change this.** * `AutoValidate`: The single point to enable/disable the ABP automatic antiforgery validation system. Default value is `true`. +* `NormalizeUserIdClaimIssuer`: Normalizes the user ID claim issuer while generating and validating antiforgery tokens. This allows the same user to have the same token identifier under cookie and bearer authentication. Default value is `true`; disable it only when issuer-sensitive token identity is required for compatibility. * `AutoValidateFilter`: A predicate that gets a type and returns a boolean. ABP uses this predicate to check a controller type. If it returns false for a controller type, the controller is excluded from the automatic antiforgery token validation. * `AutoValidateIgnoredHttpMethods`: A list of HTTP Methods to ignore on automatic antiforgery validation. Default value: "GET", "HEAD", "TRACE", "OPTIONS". These HTTP Methods are safe to skip antiforgery validation since they don't change the application state. diff --git a/docs/en/framework/infrastructure/current-user.md b/docs/en/framework/infrastructure/current-user.md index cb5954a778..f7d7654f68 100644 --- a/docs/en/framework/infrastructure/current-user.md +++ b/docs/en/framework/infrastructure/current-user.md @@ -64,6 +64,8 @@ Here are the fundamental properties of the `ICurrentUser` interface: * **IsAuthenticated** (bool): Returns `true` if the current user has logged in (authenticated). If the user has not logged in then `Id` and `UserName` returns `null`. * **Id** (Guid?): Id of the current user. Returns `null`, if the current user has not logged in. * **UserName** (string): User name of the current user. Returns `null`, if the current user has not logged in. +* **Name** (string): Name of the current user. Returns `null` if the corresponding claim is not available. +* **SurName** (string): Surname of the current user. Returns `null` if the corresponding claim is not available. * **TenantId** (Guid?): Tenant Id of the current user, which can be useful for a [multi-tenant](../architecture/multi-tenancy) application. Returns `null`, if the current user is not assigned to a tenant. * **Email** (string): Email address of the current user.Returns `null`, if the current user has not logged in or not set an email address. * **EmailVerified** (bool): Returns `true`, if the email address of the current user has been verified. @@ -91,6 +93,10 @@ Beside these standard methods, there are some extension methods: `ICurrentUser` works independently of how the user is authenticated or authorized. It seamlessly works with any authentication system that works with the current principal (see the section below). +## ICurrentClient + +`ICurrentClient` provides the current client identity for machine-to-machine requests. Its `Id` property reads the `AbpClaimTypes.ClientId` claim, and `IsAuthenticated` is `true` when that claim exists. Inject this service when client credentials are used without a current user. The authorization system uses the same client ID claim for client permission checks. + ## ICurrentPrincipalAccessor `ICurrentPrincipalAccessor` is the service that should be used (by the ABP and your application code) whenever the current principal of the current user is needed. @@ -172,3 +178,24 @@ This can be a way to simulate a user login for a scope of the application code, It is suggested to use properties of this class instead of magic strings for claim names. +## IAbpClaimsPrincipalContributor + +Implement `IAbpClaimsPrincipalContributor` to add claims while `IAbpClaimsPrincipalFactory.CreateAsync` creates a principal. Conventionally registered implementations are discovered automatically: + +````csharp +public class DepartmentClaimsPrincipalContributor : + IAbpClaimsPrincipalContributor, + ITransientDependency +{ + public Task ContributeAsync( + AbpClaimsPrincipalContributorContext context) + { + var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); + identity?.AddClaim(new Claim("department", "sales")); + + return Task.CompletedTask; + } +} +```` + +This contributor runs during regular principal creation. Use `IAbpDynamicClaimsPrincipalContributor` when claims need to be refreshed by the [dynamic claims](../fundamentals/dynamic-claims.md) pipeline. diff --git a/docs/en/framework/infrastructure/event-bus/distributed/azure.md b/docs/en/framework/infrastructure/event-bus/distributed/azure.md index 92a961230d..10983218d4 100644 --- a/docs/en/framework/infrastructure/event-bus/distributed/azure.md +++ b/docs/en/framework/infrastructure/event-bus/distributed/azure.md @@ -42,7 +42,8 @@ This is the simplest way to configure the Azure Service Bus settings. It is also "EventBus": { "ConnectionName": "Default", "SubscriberName": "MySubscriberName", - "TopicName": "MyTopicName" + "TopicName": "MyTopicName", + "IsServiceBusDisabled": false } } } @@ -124,6 +125,8 @@ You can use any of the [ServiceBusAdministrationClientOptions](https://docs.micr `AbpAzureServiceBusOptions` and `AbpAzureEventBusOptions` classes can be used to configure the connection strings and event bus options for Azure Service Bus. +Set `AbpAzureEventBusOptions.IsServiceBusDisabled` to `true`, or set `Azure:EventBus:IsServiceBusDisabled` in the configuration, to skip Azure Service Bus initialization. The default value is `false`. + You can configure this options inside the `ConfigureServices` of your [module](../../../architecture/modularity/basics.md). **Example: Configure the connection** diff --git a/docs/en/framework/infrastructure/event-bus/distributed/index.md b/docs/en/framework/infrastructure/event-bus/distributed/index.md index 54baeefcac..4500932e3e 100644 --- a/docs/en/framework/infrastructure/event-bus/distributed/index.md +++ b/docs/en/framework/infrastructure/event-bus/distributed/index.md @@ -648,12 +648,15 @@ Configure(options => * `CleanOldEventTimeIntervalSpan`: The event inbox system periodically checks and deletes the old processed events from the inbox in the database. You can set this value to determine the check period. Default value is 6 hours (`TimeSpan.FromHours(6)`). * `WaitTimeToDeleteProcessedInboxEvents`: Inbox events are not deleted from the database for a while even if they are successfully processed. This is for a system to prevent multiple process of the same event (if the event broker sends it twice). This configuration value determines the time to keep the processed events. Default value is 2 hours (`TimeSpan.FromHours(2)`). * `InboxWaitingEventMaxCount`: The maximum number of events to query at once from the inbox in the database. Default value is 1000. +* `InboxProcessorFilter`: An expression used to filter incoming event records fetched by the inbox processor. The default value is `null`, which includes all records. * `OutboxWaitingEventMaxCount`: The maximum number of events to query at once from the outbox in the database. Default value is 1000. +* `OutboxProcessorFilter`: An expression used to filter outgoing event records fetched by the outbox processor. The default value is `null`, which includes all records. * `DistributedLockWaitDuration`: ABP uses [distributed locking](../../distributed-locking.md) to prevent concurrent access to the inbox and outbox messages in the database, when running multiple instance of the same application. If an instance of the application can not obtain the lock, it tries after a duration. This is the configuration of that duration. Default value is 15 seconds (`TimeSpan.FromSeconds(15)`). * `InboxProcessorFailurePolicy`: The policy to handle the failure of the inbox processor. Default value is `Retry`. Possible values are: * `Retry`: The current exception and subsequent events will continue to be processed in order in the next cycle. * `RetryLater`: Skip the event that caused the exception and continue with the following events. The failed event will be retried after a delay that doubles with each retry, starting from the configured `InboxProcessorRetryBackoffFactor` (e.g., 10, 20, 40, 80 seconds). The default maximum retry count is 10 (configurable). Discard the event if it still fails after reaching the maximum retry count. * `Discard`: The event that caused the exception will be discarded and will not be retried. +* `InboxProcessorMaxRetryCount`: The maximum retry count used by the `RetryLater` failure policy before an event is discarded. Default value is `10`. * `InboxProcessorRetryBackoffFactor`: The initial retry delay factor (double) used when `InboxProcessorFailurePolicy` is `RetryLater`. The retry delay is calculated as: `delay = InboxProcessorRetryBackoffFactor × 2^retryCount`. Default value is `10`. ### Skipping Outbox diff --git a/docs/en/framework/infrastructure/event-bus/distributed/rabbitmq.md b/docs/en/framework/infrastructure/event-bus/distributed/rabbitmq.md index 7b7d64c4c6..5c8fc740f1 100644 --- a/docs/en/framework/infrastructure/event-bus/distributed/rabbitmq.md +++ b/docs/en/framework/infrastructure/event-bus/distributed/rabbitmq.md @@ -148,17 +148,20 @@ Configure(options => }); ```` -**Example: Configure the client, exchange names and prefetchCount** +**Example: Configure the client, exchange name, exchange type and prefetch count** ````csharp Configure(options => { options.ClientName = "TestApp1"; options.ExchangeName = "TestMessages"; + options.ExchangeType = "topic"; options.PrefetchCount = 1; }); ```` +`ExchangeType` uses RabbitMQ's `direct` exchange type when it is `null` or empty. + **Example: Configure the queue and exchange optional arguments** ```csharp diff --git a/docs/en/framework/infrastructure/features.md b/docs/en/framework/infrastructure/features.md index e450dc2068..744d5cec13 100644 --- a/docs/en/framework/infrastructure/features.md +++ b/docs/en/framework/infrastructure/features.md @@ -49,7 +49,7 @@ ABP uses the interception system to make the `[RequiresFeature]` attribute worki However, there are **some rules should be followed** in order to make it working; -* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](../../dynamic-proxying-interceptors.md) system can not work. +* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](./interceptors.md) system can not work. * Only `async` methods (methods returning a `Task` or `Task`) are intercepted. > There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP uses the action/page filters to implement the feature checking in this case. diff --git a/docs/en/framework/infrastructure/interceptors.md b/docs/en/framework/infrastructure/interceptors.md index 9005c3d612..25cf783108 100644 --- a/docs/en/framework/infrastructure/interceptors.md +++ b/docs/en/framework/infrastructure/interceptors.md @@ -203,6 +203,23 @@ ABP uses interceptors for features like UOW, auditing, and authorization, which To avoid generating dynamic proxies for specific types, use the static class `DynamicProxyIgnoreTypes` and add the base classes of the types to the list. Subclasses of any listed base class are also ignored. ABP framework already adds some base classes to the list (`ComponentBase, ControllerBase, PageModel, ViewComponent`); you can add more base classes if needed. +You can also disable ABP class interceptors for all registrations or for types selected by a predicate: + +````csharp +// Disable all class interceptors. +context.Services.DisableAbpClassInterceptors(); + +// Or disable them only for selected implementation types. +context.Services.DisableAbpClassInterceptors( + new NamedTypeSelector( + "MyHotPathServices", + type => type.Namespace == "MyProject.HotPath" + ) +); +```` + +These methods control class interception. Interface-based interception is configured separately. + > Always use interface-based proxies instead of class-based proxies for better performance. ## See Also diff --git a/docs/en/framework/infrastructure/json.md b/docs/en/framework/infrastructure/json.md index a8d17528cb..28aa279439 100644 --- a/docs/en/framework/infrastructure/json.md +++ b/docs/en/framework/infrastructure/json.md @@ -45,6 +45,35 @@ public class ProductManager } ``` +## IObjectSerializer + +`IObjectSerializer` serializes objects to and from `byte[]`. The default implementation uses UTF-8 JSON bytes from `System.Text.Json`: + +```csharp +public interface IObjectSerializer +{ + byte[]? Serialize(T? obj); + T? Deserialize(byte[] bytes); +} +``` + +Inject `IObjectSerializer` when a storage or transport API works with bytes instead of strings. To customize serialization for a specific type, implement `IObjectSerializer`. ABP automatically exposes conventionally registered implementations through the corresponding closed generic interface, and the default serializer uses that implementation for `T`: + +```csharp +public class ProductSerializer : IObjectSerializer, ITransientDependency +{ + public byte[]? Serialize(Product? obj) + { + return obj is null ? null : JsonSerializer.SerializeToUtf8Bytes(obj); + } + + public Product? Deserialize(byte[]? bytes) + { + return bytes is null ? null : JsonSerializer.Deserialize(bytes); + } +} +``` + ## Configuration ### AbpJsonOptions diff --git a/docs/en/framework/infrastructure/mail-kit.md b/docs/en/framework/infrastructure/mail-kit.md index f483aaa512..307ba61557 100644 --- a/docs/en/framework/infrastructure/mail-kit.md +++ b/docs/en/framework/infrastructure/mail-kit.md @@ -37,7 +37,7 @@ MailKit integration package uses the same settings defined by the email sending In addition to the standard settings, this package defines `AbpMailKitOptions` as a simple [options](../fundamentals/options.md) class. This class defines only one options: -* **SecureSocketOption**: Used to set one of the `SecureSocketOptions`. Default: `null` (uses the defaults). +* **SecureSocketOption**: Used to set one of the `SecureSocketOptions`. The default is `null`. In that case, ABP uses `SslOnConnect` when the SMTP `EnableSsl` setting is `true`; otherwise, it uses `StartTlsWhenAvailable`. **Example: Use *SecureSocketOptions.SslOnConnect*** @@ -52,4 +52,4 @@ Refer to the [MailKit documentation](http://www.mimekit.net/) to learn more abou ## See Also -* [Email sending](./emailing.md) \ No newline at end of file +* [Email sending](./emailing.md) diff --git a/docs/en/framework/infrastructure/object-to-object-mapping.md b/docs/en/framework/infrastructure/object-to-object-mapping.md index d8484a277e..888655d446 100644 --- a/docs/en/framework/infrastructure/object-to-object-mapping.md +++ b/docs/en/framework/infrastructure/object-to-object-mapping.md @@ -224,7 +224,18 @@ public class MyProfile : Profile } ```` -> AutoMapper 14.x contains a [known vulnerability (GHSA-rvv3-g6hj-g44x)](https://github.com/advisories/GHSA-rvv3-g6hj-g44x). ABP Framework has applied a code-level mitigation (`MaxDepth = 64`) to address this. If you hold a commercial AutoMapper license, you can use [Volo.Abp.LuckyPenny.AutoMapper](luckypenny-automapper.md) to upgrade to the officially patched version. Alternatively, you can migrate to [Mapperly](../../../release-info/migration-guides/AutoMapper-To-Mapperly.md). +> AutoMapper 14.x contains a [known vulnerability (GHSA-rvv3-g6hj-g44x)](https://github.com/advisories/GHSA-rvv3-g6hj-g44x). ABP Framework has applied a code-level mitigation (`MaxDepth = 64`) to address this. If you hold a commercial AutoMapper license, you can use [Volo.Abp.LuckyPenny.AutoMapper](luckypenny-automapper.md) to upgrade to the officially patched version. Alternatively, you can migrate to [Mapperly](../../release-info/migration-guides/AutoMapper-To-Mapperly.md). + +The global maximum depth is configured by `AbpAutoMapperOptions.DefaultMaxDepth` and defaults to `64`. It is applied only when a map does not already configure `MaxDepth`. Set it to `null` to disable ABP's global default: + +````csharp +Configure(options => +{ + options.DefaultMaxDepth = null; +}); +```` + +> Disabling the global default also removes ABP's mitigation for unbounded mapping depth. Disable it only when every affected map has an explicit safe depth or the application uses an officially patched mapper. ## Mapperly Integration diff --git a/docs/en/framework/infrastructure/settings.md b/docs/en/framework/infrastructure/settings.md index 38c9528d86..14b493e1fc 100644 --- a/docs/en/framework/infrastructure/settings.md +++ b/docs/en/framework/infrastructure/settings.md @@ -257,6 +257,15 @@ While a setting value provider is free to use any source to get the setting valu You can replace this service in the dependency injection system to customize the encryption/decryption process. Default implementation uses the `StringEncryptionService` which is implemented with the AES algorithm by default (see string [encryption document](./string-encryption.md) for more). +If an encrypted setting value cannot be decrypted, the default service logs a warning and returns the original value. This behavior helps when an existing setting is changed from unencrypted to encrypted. Set `AbpSettingOptions.ReturnOriginalValueIfDecryptFailed` to `false` to return an empty string instead: + +````csharp +Configure(options => +{ + options.ReturnOriginalValueIfDecryptFailed = false; +}); +```` + ## Setting Management Module The core setting system is pretty independent and doesn't make any assumption about how you manage (change) the setting values. Even the default `ISettingStore` implementation is the `NullSettingStore` which returns null for all setting values. diff --git a/docs/en/framework/infrastructure/sms-sending.md b/docs/en/framework/infrastructure/sms-sending.md index 64439be111..d2532709ec 100644 --- a/docs/en/framework/infrastructure/sms-sending.md +++ b/docs/en/framework/infrastructure/sms-sending.md @@ -85,20 +85,19 @@ The given `SendAsync` method in the example is an extension method to send an SM - `PhoneNumber` (`string`): Target phone number - `Text` (`string`): Message text -- `Properties` (`Dictionary`): Key-value pairs to pass custom arguments +- `Properties` (`IDictionary`): Key-value pairs to pass custom arguments ## NullSmsSender -`NullSmsSender` is a the default implementation of the `ISmsSender`. It writes SMS content to the [standard logger](../fundamentals/logging.md), rather than actually sending the SMS. +`NullSmsSender` is the default implementation of `ISmsSender`. It writes SMS content to the [standard logger](../fundamentals/logging.md), rather than actually sending the SMS. -This class can be useful especially in development time where you generally don't want to send real SMS. **However, if you want to actually send SMS, you should implement the `ISmsSender` in your application code.** +This class can be useful especially in development time where you generally don't want to send real SMS. To send real SMS, install one of the pre-built providers below or implement `ISmsSender` in your application code. ## Implementing the ISmsSender You can easily create your SMS sending implementation by creating a class that implements the `ISmsSender` interface, as shown below: ```csharp -using System.IO; using System.Threading.Tasks; using Volo.Abp.Sms; using Volo.Abp.DependencyInjection; @@ -107,14 +106,95 @@ namespace AbpDemo { public class MyCustomSmsSender : ISmsSender, ITransientDependency { - public async Task SendAsync(SmsMessage smsMessage) + public Task SendAsync(SmsMessage smsMessage) { // Send sms + return Task.CompletedTask; } } } ``` +## Pre-Built Providers + +Adding a provider module registers its sender as the `ISmsSender` implementation in place of the default `NullSmsSender`. + +### Aliyun + +Install the Aliyun provider package: + +```bash +abp add-package Volo.Abp.Sms.Aliyun +``` + +For manual installation, add the `Volo.Abp.Sms.Aliyun` package and declare a dependency on `AbpSmsAliyunModule`. + +Configure the provider in the `AbpAliyunSms` section: + +```json +{ + "AbpAliyunSms": { + "AccessKeyId": "your-access-key-id", + "AccessKeySecret": "your-access-key-secret", + "EndPoint": "your-endpoint" + } +} +``` + +Aliyun sends template-based messages. Set `SmsMessage.Text` to the template parameter JSON and use the `SignName` and `TemplateCode` properties: + +```csharp +var message = new SmsMessage( + "+012345678901", + "{\"code\":\"123456\"}" +); + +message.Properties["SignName"] = "MySign"; +message.Properties["TemplateCode"] = "SMS_123456789"; + +await _smsSender.SendAsync(message); +``` + +### Tencent Cloud + +Install the Tencent Cloud provider package: + +```bash +abp add-package Volo.Abp.Sms.TencentCloud +``` + +For manual installation, add the `Volo.Abp.Sms.TencentCloud` package and declare a dependency on `AbpSmsTencentCloudModule`. + +Configure the provider in the `AbpTencentCloudSms` section: + +```json +{ + "AbpTencentCloudSms": { + "SmsSdkAppId": "your-sdk-app-id", + "SecretId": "your-secret-id", + "SecretKey": "your-secret-key", + "Endpoint": "sms.tencentcloudapi.com", + "Region": "ap-guangzhou" + } +} +``` + +`Endpoint` defaults to `sms.tencentcloudapi.com` and `Region` defaults to `ap-guangzhou`. + +Set the sign and template identifiers through `TencentCloudSmsProperties`. The provider splits `SmsMessage.Text` by commas and sends the resulting values as template parameters: + +```csharp +var message = new SmsMessage( + "+012345678901", + "123456,5" +); + +message.Properties[TencentCloudSmsProperties.SignName] = "MySign"; +message.Properties[TencentCloudSmsProperties.TemplateId] = "123456"; + +await _smsSender.SendAsync(message); +``` + ## More [ABP](https://abp.io/) provides Twilio integration package to send SMS over [Twilio service](https://abp.io/docs/latest/modules/twilio-sms). diff --git a/docs/en/framework/infrastructure/string-encryption.md b/docs/en/framework/infrastructure/string-encryption.md index de8f6c75bf..ccdc9d8eac 100644 --- a/docs/en/framework/infrastructure/string-encryption.md +++ b/docs/en/framework/infrastructure/string-encryption.md @@ -112,8 +112,8 @@ Configure(opts => { opts.DefaultPassPhrase = "MyStrongPassPhrase"; opts.DefaultSalt = Encoding.UTF8.GetBytes("MyStrongSalt"); - opts.InitVectorBytes = Encoding.UTF8.GetBytes("YetAnotherStrongSalt"); - opts.Keysize = 512; + opts.InitVectorBytes = Encoding.UTF8.GetBytes("My16ByteInitVect"); + opts.Keysize = 256; }); ``` @@ -123,10 +123,10 @@ Configure(opts => Default value: `Encoding.ASCII.GetBytes("hgt!16kl")` -- **InitVectorBytes:** This constant string is used as a "salt" value for the PasswordDeriveBytes function calls. This size of the IV (in bytes) must = (keysize / 8). Default keysize is 256, so the IV must be 32 bytes long. Using a 16 character string here gives us 32 bytes when converted to a byte array. +- **InitVectorBytes:** The initialization vector used by AES. It must be exactly 16 bytes, regardless of the configured key size. Default value: `Encoding.ASCII.GetBytes("jkE49230Tf093b42")` -- **Keysize:** This constant is used to determine the keysize of the encryption algorithm. +- **Keysize:** The AES key size in bits. Use a key size supported by AES: `128`, `192`, or `256`. Default value: `256` diff --git a/docs/en/framework/infrastructure/text-templating/index.md b/docs/en/framework/infrastructure/text-templating/index.md index 853014c1cd..e8912889b6 100644 --- a/docs/en/framework/infrastructure/text-templating/index.md +++ b/docs/en/framework/infrastructure/text-templating/index.md @@ -33,6 +33,21 @@ ABP provides two templating engines; You can use different template engines in the same application, or even create a new custom template engine. +## Default Rendering Engine + +A template can select its rendering engine explicitly with `WithScribanEngine`, `WithRazorEngine` or `WithRenderEngine`. If it does not, the renderer uses `AbpTextTemplatingOptions.DefaultRenderingEngine`. + +The Scriban module selects Scriban as the default engine. The Razor module selects Razor only if no default has already been configured. You can explicitly select the application-wide default: + +````csharp +Configure(options => +{ + options.DefaultRenderingEngine = ScribanTemplateRenderingEngine.EngineName; +}); +```` + +An engine selected on a template definition takes precedence over this global default. + ## Source Code Get [the source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. @@ -41,4 +56,4 @@ Get [the source code of the sample application](https://github.com/abpframework/ * [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document. * [Localization system](../../fundamentals/localization.md). -* [Virtual File System](../../infrastructure/virtual-file-system.md). \ No newline at end of file +* [Virtual File System](../../infrastructure/virtual-file-system.md). diff --git a/docs/en/framework/infrastructure/virtual-file-system.md b/docs/en/framework/infrastructure/virtual-file-system.md index 682fcd2f10..51fb3a44aa 100644 --- a/docs/en/framework/infrastructure/virtual-file-system.md +++ b/docs/en/framework/infrastructure/virtual-file-system.md @@ -116,6 +116,38 @@ public class MyService : ITransientDependency } ```` +### Dynamic Files + +`IDynamicFileProvider` can add, replace and delete virtual files at runtime. Inside `IVirtualFileProvider`, dynamic files take precedence over configured embedded and replacement physical file sets, so they can temporarily override a file with the same virtual path. ASP.NET Core's physical web-root provider is a separate, higher-precedence layer, as described in the *Physical Files* section below. Dynamic files also support exact file-path change notifications through the standard `Watch` method; directory and wildcard watches are not supported. + +````csharp +public class DynamicFileService : ITransientDependency +{ + private readonly IDynamicFileProvider _dynamicFileProvider; + + public DynamicFileService(IDynamicFileProvider dynamicFileProvider) + { + _dynamicFileProvider = dynamicFileProvider; + } + + public void SetFile(string content) + { + _dynamicFileProvider.AddOrUpdate( + new InMemoryFileInfo( + "/my-files/runtime.txt", + Encoding.UTF8.GetBytes(content), + "runtime.txt" + ) + ); + } + + public bool DeleteFile() + { + return _dynamicFileProvider.Delete("/my-files/runtime.txt"); + } +} +```` + ## ASP.NET Core Integration The Virtual File System is well integrated to ASP.NET Core: @@ -192,4 +224,4 @@ Physical files always override the virtual files. That means if you put a file u ## See Also -* [Video tutorial](https://abp.io/video-courses/essentials/virtual-file-system) \ No newline at end of file +* [Video tutorial](https://abp.io/video-courses/essentials/virtual-file-system) diff --git a/docs/en/framework/real-time/signalr.md b/docs/en/framework/real-time/signalr.md index be4160a819..4a3ffe69fc 100644 --- a/docs/en/framework/real-time/signalr.md +++ b/docs/en/framework/real-time/signalr.md @@ -223,6 +223,17 @@ app.UseConfiguredEndpoints(endpoints => }); ``` +### Dynamic Claims + +When [dynamic claims](../fundamentals/dynamic-claims.md) are enabled, ABP refreshes the principal when a client connects and periodically during hub method invocations. `AbpSignalROptions.CheckDynamicClaimsInterval` controls the minimum interval between invocation-time checks for a connection. The default is five seconds; set it to `null` to check on every invocation: + +```csharp +Configure(options => +{ + options.CheckDynamicClaimsInterval = TimeSpan.FromMinutes(1); +}); +``` + ### UserIdProvider ABP implements SignalR's `IUserIdProvider` interface to provide the current user id from the `ICurrentUser` service of the ABP (see [the current user service](../infrastructure/current-user.md)), so it will be integrated to the authentication system of your application. The implementing class is the `AbpSignalRUserIdProvider`, if you want to change/override it. diff --git a/docs/en/framework/ui/angular/commercial-ui.md b/docs/en/framework/ui/angular/commercial-ui.md new file mode 100644 index 0000000000..e696dec165 --- /dev/null +++ b/docs/en/framework/ui/angular/commercial-ui.md @@ -0,0 +1,88 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to use ABP Commercial Angular date range controls, standalone UI configuration and the public testing entrypoint." +} +``` + +# Commercial UI Components + +The `@volo/abp.commercial.ng.ui` package provides shared ABP Commercial Angular controls in addition to the separately documented [lookup components](./lookup-components.md) and [entity filters](./entity-filters.md). The package is included in ABP Commercial Angular application templates. + +## Date Range Controls + +`DateRangePickerComponent` and `DatetimeRangePickerComponent` are Angular form controls. `startDateProp` and `endDateProp` specify the two properties updated in the bound model: + +```ts +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { + DateRangePickerModule, + DatetimeRangePickerComponent, +} from '@volo/abp.commercial.ng.ui'; + +@Component({ + selector: 'app-report-range', + templateUrl: './report-range.component.html', + imports: [ + FormsModule, + DateRangePickerModule, + DatetimeRangePickerComponent, + ], +}) +export class ReportRangeComponent { + dateRange: { + startDate: string | Date | null; + endDate: string | Date | null; + } = { + startDate: null, + endDate: null, + }; +} +``` + +```html + +``` + +Use `abp-datetime-range-picker` with the same inputs when the model also needs start and end times. + +## Standalone Configuration + +Register commercial UI configuration in the application providers. The following example enables flag icons: + +```ts +import { ApplicationConfig } from '@angular/core'; +import { + provideCommercialUiConfig, + withEnableFlagIcon, +} from '@volo/abp.commercial.ng.ui/config'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideCommercialUiConfig( + withEnableFlagIcon(true), + ), + ], +}; +``` + +Calling `provideCommercialUiConfig()` also registers the shared profile-picture, impersonation and tenant-switching providers. Flag icons are disabled unless `withEnableFlagIcon(true)` is supplied. + +## Testing + +`CommercialUiTestingModule` imports and exports `BaseCommercialUiModule`, making its declarations available from the public testing entrypoint. Its `withConfig()` method returns the module registration without adding test doubles or providers: + +```ts +import { TestBed } from '@angular/core/testing'; +import { CommercialUiTestingModule } from '@volo/abp.commercial.ng.ui/testing'; + +await TestBed.configureTestingModule({ + imports: [CommercialUiTestingModule.withConfig()], +}).compileComponents(); +``` diff --git a/docs/en/framework/ui/angular/component-replacement.md b/docs/en/framework/ui/angular/component-replacement.md index ec1102f893..4750bbbb64 100644 --- a/docs/en/framework/ui/angular/component-replacement.md +++ b/docs/en/framework/ui/angular/component-replacement.md @@ -171,7 +171,7 @@ export const appConfig: ApplicationConfig = { withOptions({ dynamicLayouts: myDynamicLayouts, environment, - registerLocaleFn: registerLocale(), + registerLocaleFn: registerLocaleForEsBuild(), }), ), ], @@ -180,6 +180,8 @@ export const appConfig: ApplicationConfig = { In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `provideAbpCore` using the `withOptions` method. +This example uses the Angular application builder. Use `registerLocale()` instead when the application uses the Webpack builder. See [Registering a New Locale](./localization.md#registering-a-new-locale) for the builder-specific setup. + 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: diff --git a/docs/en/framework/ui/angular/datetime-format-pipe.md b/docs/en/framework/ui/angular/datetime-format-pipe.md index e0e5493851..190b17aab6 100644 --- a/docs/en/framework/ui/angular/datetime-format-pipe.md +++ b/docs/en/framework/ui/angular/datetime-format-pipe.md @@ -1,41 +1,117 @@ ```json //[doc-seo] { - "Description": "Learn how to easily format dates in Angular using DateTime format pipes for shortDate, shortTime, and shortDateTime with culture settings." + "Description": "Format dates and handle clock-aware timezone conversion in ABP Angular applications with pipes, TimeService, and TimezoneService." } ``` {%{ -# DateTime Format Pipes +# Date and Time -You can format date by Date pipe of angular. +ABP Angular provides culture-aware format pipes, clock-aware UTC conversion, timezone selection, and date-time services. These APIs use the culture, clock, and timezone values from the application configuration. + +## Culture-Aware Format Pipes + +Angular's built-in `DatePipe` can format a date directly: -Example ```html -{{today | date 'dd/mm/yy'}} +{{ today | date:'dd/MM/yy' }} ``` -ShortDate, ShortTime and ShortDateTime format data like angular's data pipe but easier. Also the pipes get format from config service by culture. +The ABP pipes below use the short date and time patterns returned in the application localization configuration. -## ShortDate Pipe +### `shortDate` ```html - {{today | shortDate }} +{{ today | shortDate }} ``` +### `shortTime` -## ShortTime Pipe +```html +{{ today | shortTime }} +``` + +### `shortDateTime` ```html - {{today | shortTime }} +{{ today | shortDateTime }} ``` +These pipes extend Angular's `DatePipe`. They select the format pattern from `ConfigStateService`; they do not apply ABP's clock-aware timezone selection. Use `abpUtcToLocal` when the value also needs to follow the application's clock and timezone. + +## Clock-Aware UTC Conversion -## ShortDateTime Pipe +The `abpUtcToLocal` pipe accepts `date`, `time`, or `datetime` as its format type: ```html - {{today | shortDateTime }} +{{ order.creationTime | abpUtcToLocal:'datetime' }} +``` + +Its behavior depends on the clock configuration returned by the backend: + +- With the UTC clock enabled, it converts the input to `TimezoneService.timezone`, including the daylight-saving-time offset for that date. +- With a non-UTC clock, it formats the value without applying the configured timezone conversion. +- Empty or invalid input produces an empty string. + +The output pattern is still taken from the current application's short date and time formats. + +## `TimezoneService` + +Inject `TimezoneService` to read or persist the timezone used by the Angular application: + +```ts +import { TimezoneService } from '@abp/ng.core'; +import { Component, inject } from '@angular/core'; + +@Component({ + selector: 'app-timezone-selector', + template: ``, +}) +export class TimezoneSelectorComponent { + private readonly timezoneService = inject(TimezoneService); + + selectTimezone(): void { + this.timezoneService.setTimezone('Europe/Istanbul'); + } +} +``` + +`timezone` returns: + +- the browser timezone when the backend clock is not UTC; +- the `Abp.Timing.TimeZone` setting when the clock is UTC and the setting has a value; +- the browser timezone as a fallback when the UTC setting is empty. + +`setTimezone` writes the selected IANA timezone to the `__timezone` cookie only when the UTC clock is enabled. + +When you configure the application with `provideAbpCore`, its built-in `timezoneInterceptor` adds the effective timezone to outgoing `HttpClient` requests as the `__timezone` header. It does not add the header when the UTC clock is disabled. + +## `TimeService` + +`TimeService` returns [Luxon](https://moment.github.io/luxon/#/) `DateTime` values and formats dates with the current Angular locale: + +```ts +import { TimeService } from '@abp/ng.core'; +import { inject, Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class ScheduleFormatter { + private readonly timeService = inject(TimeService); + + formatForIstanbul(value: string): string { + return this.timeService.format(value, 'ff', 'Europe/Istanbul'); + } +} ``` +| Method | Behavior | +| --- | --- | +| `now(zone = 'local')` | Returns the current time in the requested IANA timezone. | +| `toZone(value, zone)` | Parses an ISO string or `Date` and returns a Luxon value in the requested timezone. | +| `format(value, format = 'ff', zone = 'local')` | Converts to the timezone, applies its DST rules, and formats with the current locale. | +| `formatDateWithStandardOffset(value, format = 'ff', zone?)` | Applies the zone's January 1 offset and formats without any further timezone or DST conversion. | +| `formatWithoutTimeZone(value, format = 'ff')` | Formats the parsed ISO clock fields without shifting them to another timezone. | + }%} diff --git a/docs/en/framework/ui/angular/http-error-handling.md b/docs/en/framework/ui/angular/http-error-handling.md index 33c4b0cec2..aa436a8293 100644 --- a/docs/en/framework/ui/angular/http-error-handling.md +++ b/docs/en/framework/ui/angular/http-error-handling.md @@ -13,7 +13,11 @@ ABP offers a configurations for errors handling like below ```ts //app.config.ts -import { provideAbpThemeShared } from '@abp/ng.theme.shared'; +import { ApplicationConfig } from '@angular/core'; +import { + provideAbpThemeShared, + withHttpErrorConfig, +} from '@abp/ng.theme.shared'; import { CustomErrorComponent } from './custom-error.component'; export const appConfig: ApplicationConfig = { @@ -75,15 +79,13 @@ export function handleHttpErrors(injector: Injector, httpError: HttpErrorRespons } // app.config.ts -import { Error404Component } from './error404/error404.component'; -import { handleHttpErrors } from './http-error-handling'; -import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared'; +import { ApplicationConfig } from '@angular/core'; +import { HTTP_ERROR_HANDLER } from '@abp/ng.theme.shared'; +import { handleHttpErrors } from './http-error-handler'; export const appConfig: ApplicationConfig = { providers: [ - ... { provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }, - ... ], }; @@ -116,7 +118,9 @@ export function handleHttpErrors( - `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`. ```ts -import { of } from "rxjs"; +import { HttpErrorResponse } from '@angular/common/http'; +import { Injector } from '@angular/core'; +import { of } from 'rxjs'; export function handleHttpErrors( injector: Injector, @@ -163,11 +167,13 @@ See an example: ```ts // custom-error-handler.service.ts -import { inject, Injectable } from "@angular/core"; -import { HttpErrorResponse } from "@angular/common/http"; -import { CustomHttpErrorHandlerService } from "@abp/ng.theme.shared"; -import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from "@abp/ng.theme.shared"; -import { ToasterService } from "@abp/ng.theme.shared"; +import { HttpErrorResponse } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { + CUSTOM_HTTP_ERROR_HANDLER_PRIORITY, + CustomHttpErrorHandlerService, + ToasterService, +} from '@abp/ng.theme.shared'; @Injectable({ providedIn: "root" }) export class MyCustomErrorHandlerService @@ -190,8 +196,8 @@ export class MyCustomErrorHandlerService // If this service is picked from ErrorHandler, this execute method will be called. execute() { this.toaster.error( - this.error.error?.error?.message || "Bad request!", - "400" + this.error?.error?.error?.message || 'Bad request!', + '400', ); } } @@ -200,17 +206,17 @@ export class MyCustomErrorHandlerService ```ts // app.config.ts -import { CUSTOM_ERROR_HANDLERS, ... } from '@abp/ng.theme.shared'; +import { ApplicationConfig } from '@angular/core'; +import { CUSTOM_ERROR_HANDLERS } from '@abp/ng.theme.shared'; import { MyCustomErrorHandlerService } from './custom-error-handler.service'; export const appConfig: ApplicationConfig = { providers: [ - //... { provide: CUSTOM_ERROR_HANDLERS, useExisting: MyCustomErrorHandlerService, multi: true, - } + }, ], }; ``` @@ -227,5 +233,5 @@ In the example above: - If your service cannot handle the error. Then ABP will check the next Error Service. - If none of the service handle the error. Then basic confirmation message about the error will be shown to the user. -- You can provide more than one service, with CUSTOM_ERROR_HANDLER injection token. +- You can provide more than one service with the `CUSTOM_ERROR_HANDLERS` injection token. - If you want your custom service to be evaluated (checked) earlier, set the priority variable high. diff --git a/docs/en/framework/ui/angular/http-requests.md b/docs/en/framework/ui/angular/http-requests.md index a5ca0dec66..db7c65ab12 100644 --- a/docs/en/framework/ui/angular/http-requests.md +++ b/docs/en/framework/ui/angular/http-requests.md @@ -30,7 +30,7 @@ An `HttpInterceptor` is able to catch `HttpErrorResponse` and can be used for a ## RestService -ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default. +ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and reports them through `HttpErrorReporterService`. The error handler provided by the Theme Shared package subscribes to that service and displays the appropriate error UI. When the Theme Shared provider is configured in your application, HTTP errors from `RestService` are handled automatically by default. ### Getting Started with RestService @@ -110,7 +110,7 @@ deleteFoo(id: number) { } ``` -`skipHandleError` config option, when set to `true`, disables the error handler and the returned observable starts throwing an error that you can catch in your subscription. +The `skipHandleError` config option, when set to `true`, prevents `RestService` from reporting the error through `HttpErrorReporterService`. The returned observable still throws the error, so you can handle it in the caller. ```js removeFooFromList(id: number) { diff --git a/docs/en/framework/ui/angular/list-service.md b/docs/en/framework/ui/angular/list-service.md index dab58d0e33..bfa66cebb9 100644 --- a/docs/en/framework/ui/angular/list-service.md +++ b/docs/en/framework/ui/angular/list-service.md @@ -15,7 +15,7 @@ `ListService` is **not provided in root**. The reason is, this way, it will clear any subscriptions on component destroy. You may use the optional `LIST_QUERY_DEBOUNCE_TIME` token to adjust the debounce behavior. -```js +```ts import { ListService } from '@abp/ng.core'; import { BookDto } from '../models'; import { BookService } from '../services'; @@ -29,7 +29,7 @@ import { inject } from '@angular/core'; // [Optional] // Provide this token if you want a different debounce time. - // Default is 300. Cannot be 0. Any value below 100 is not recommended. + // Default is 300. Use 0 to disable debouncing. { provide: LIST_QUERY_DEBOUNCE_TIME, useValue: 500 }, ], template: ` @@ -55,7 +55,7 @@ class BookComponent { this.list.hookToQuery(bookStreamCreator).subscribe( response => { this.items = response.items; - this.count = response.count; + this.count = response.totalCount; // If you use OnPush change detection strategy, // call detectChanges method of ChangeDetectorRef here. } diff --git a/docs/en/framework/ui/angular/localization.md b/docs/en/framework/ui/angular/localization.md index d50484c83a..b6a77ec0f7 100644 --- a/docs/en/framework/ui/angular/localization.md +++ b/docs/en/framework/ui/angular/localization.md @@ -72,6 +72,27 @@ Then, we can use this key like this: ``` +### Using the Async Localization Pipe + +Use `abpAsyncLocalization` when the template must wait for the application localization state before resolving a key. The pipe returns an observable, so combine it with Angular's `async` pipe: + +```ts +import { AsyncPipe } from '@angular/common'; +import { Component } from '@angular/core'; +import { AsyncLocalizationPipe } from '@abp/ng.core'; + +@Component({ + selector: 'app-greeting', + imports: [AsyncPipe, AsyncLocalizationPipe], + template: ` +

{%{{{ 'MyProjectName::Greeting' | abpAsyncLocalization | async }}}%}

+ `, +}) +export class GreetingComponent {} +``` + +The observable initially emits an empty string. After the localization configuration is available, it emits the localized value. It also emits an empty string when the key cannot be resolved. Interpolation parameters can be passed in the same way as with `abpLocalization`. + ### Using the Localization Service First of all, you should import the `LocalizationService` from **@abp/ng.core** @@ -212,7 +233,7 @@ The localizations above can be used like this:
{%{{{ 'MyProjectName::HomePage' | abpLocalization }}}%}
``` -> **Note:** If you have specified the same localizations in the UI and backend, the backend localizations override the UI localizations. +> **Note:** If the same localization key is specified in the UI and backend, the UI localization overrides the backend localization. ## RTL Support @@ -279,9 +300,42 @@ export class AppComponent {} ## Registering a New Locale -Since ABP has more than one language, Angular locale files load lazily using [Webpack's import function](https://webpack.js.org/api/module-methods/#import-1) to avoid increasing the bundle size and to register the Angular core using the [`registerLocaleData`](https://angular.dev/api/common/registerLocaleData) function. The chunks to be included in the bundle are specified by the [Webpack's magic comments](https://webpack.js.org/api/module-methods/#magic-comments) as hard-coded. Therefore a `registerLocale` function that returns Webpack `import` function must be passed to `provideAbpCore(withOptions({...}))`. +ABP loads Angular locale data lazily and registers it with Angular's [`registerLocaleData`](https://angular.dev/api/common/registerLocaleData) function. The registration function depends on the Angular builder used by your application: + +| Builder | Registration function | +| --- | --- | +| Angular application builder (`@angular/build:application`) | `registerLocaleForEsBuild()` | +| Webpack builder | `registerLocale()` | + +Pass the selected function as `registerLocaleFn` to `provideAbpCore(withOptions({...}))`. + +### Application Builder (EsBuild) + +Current ABP Angular application templates use the Angular application builder. Configure them with `registerLocaleForEsBuild`: -### registerLocaleFn +```ts +import { provideAbpCore, withOptions } from '@abp/ng.core'; +import { registerLocaleForEsBuild } from '@abp/ng.core/locale'; +import { ApplicationConfig } from '@angular/core'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAbpCore( + withOptions({ + environment, + registerLocaleFn: registerLocaleForEsBuild({ + cultureNameLocaleFileMap: { 'pt-BR': 'pt' }, + }), + }), + ), + ], +}; +``` + +`registerLocaleForEsBuild` uses a fixed list of supported Angular locale imports so the application builder can include them in the bundle. + +### Webpack Builder The `registerLocale` function, exported from the `@abp/ng.core/locale` package, is a **higher-order function**. @@ -290,7 +344,7 @@ It accepts the following parameters: - **`cultureNameLocaleFileMap`** – an object that maps culture names to their corresponding locale files. - **`errorHandlerFn`** – a function that handles any errors that occur during locale loading. -It returns a **Webpack `import` function**. +It returns a **Webpack `import` function**. Use it only when the application is built with Webpack. You should use `registerLocale` within the `withOptions` function of `provideAbpCore`, as shown in the example below: @@ -326,14 +380,14 @@ Some of the culture names defined in .NET do not match Angular locales. In such ![locale-error](./images/locale-error.png) -If you see an error like this, you should pass the `cultureNameLocaleFileMap` property like below to the `registerLocale` function. +If you see an error like this, pass the `cultureNameLocaleFileMap` property to the registration function selected for your builder. The following example uses the Angular application builder: ```ts // app.config.ts -import { registerLocale } from "@abp/ng.core/locale"; -// if you have commercial license and the language management module, add the below import -// import { registerLocale } from '@volo/abp.ng.language-management/locale'; +import { registerLocaleForEsBuild } from "@abp/ng.core/locale"; +// If you use the Language Management module, replace the import above with: +// import { registerLocale as registerLocaleForEsBuild } from '@volo/abp.ng.language-management/locale'; export const appConfig: ApplicationConfig = { providers: [ @@ -341,7 +395,7 @@ export const appConfig: ApplicationConfig = { provideAbpCore( withOptions({ // ..., - registerLocaleFn: registerLocale({ + registerLocaleFn: registerLocaleForEsBuild({ cultureNameLocaleFileMap: { DotnetCultureName: "AngularLocaleFileName", "pt-BR": "pt", // example @@ -353,6 +407,8 @@ export const appConfig: ApplicationConfig = { }; ``` +For a Webpack project, pass the same option object to `registerLocale()` instead. + See [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales). ### Adding a New Culture @@ -370,7 +426,7 @@ import( ).then((m) => storeLocaleData(m.default, "your-locale")); ``` -You can also configure a custom `registerLocale` function that can be passed to the abp core provider configuration options: +In a Webpack project, you can also configure a custom `registerLocale` function and pass it to the ABP Core provider options: ```ts // register-locale.ts diff --git a/docs/en/framework/ui/angular/lookup-search-component.md b/docs/en/framework/ui/angular/lookup-search-component.md new file mode 100644 index 0000000000..a60b713510 --- /dev/null +++ b/docs/en/framework/ui/angular/lookup-search-component.md @@ -0,0 +1,94 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to use the generic ABP Angular lookup search component with remote searches, two-way values and custom templates." +} +``` + +# Lookup Search Component + +`LookupSearchComponent` is a generic standalone search control exported by `@abp/ng.components/lookup`. A search function returns observable lookup items, and the component manages debouncing, loading, selection and clearing. + +This component is part of the open-source `@abp/ng.components` package. It is different from the [commercial lookup component family](./lookup-components.md), which provides form-oriented typeahead, select and table controls. + +The package is included in the Angular application templates. Install it if the application does not already reference it: + +```bash +npm install @abp/ng.components +``` + +```ts +import { Component, inject, signal } from '@angular/core'; +import { + LookupItem, + LookupSearchComponent, + LookupSearchFn, +} from '@abp/ng.components/lookup'; +import { map } from 'rxjs'; + +interface BookLookupItem extends LookupItem { + authorName: string; +} + +@Component({ + selector: 'app-book-lookup', + templateUrl: './book-lookup.component.html', + imports: [LookupSearchComponent], +}) +export class BookLookupComponent { + private readonly bookService = inject(BookService); + + readonly selectedBookId = signal(''); + readonly selectedBookName = signal(''); + + readonly searchBooks: LookupSearchFn = filter => + this.bookService.search(filter).pipe( + map(books => + books.map(book => ({ + key: book.id, + displayName: book.name, + authorName: book.authorName, + })), + ), + ); + + onBookSelected(book: BookLookupItem) { + console.log(book.key); + } +} +``` + +{%{ +```html + + + {{ book.displayName }} + {{ book.authorName }} + + +``` +}%} + +Each lookup item uses `key` as its selected value and `displayName` as its displayed value by default. Set `valueKey` or `displayKey` to use another item property. + +Searches use a 300 millisecond debounce by default. Configure `debounceTime` and `minSearchLength` when the remote endpoint needs different behavior. `label` and `placeholder` values are passed through ABP localization. + +`selectedValue` and `displayValue` are model inputs and support two-way binding. The component also emits `searchChanged` for input changes and `itemSelected` after selection. + +Add an `#itemTemplate` template to customize each result, as in the previous example. Add a `#noResultsTemplate` template to replace the default no-results content: + +```html + +
No matching books
+
+``` diff --git a/docs/en/framework/ui/angular/modifying-the-menu.md b/docs/en/framework/ui/angular/modifying-the-menu.md index eec9e79ba0..47cce9c49b 100644 --- a/docs/en/framework/ui/angular/modifying-the-menu.md +++ b/docs/en/framework/ui/angular/modifying-the-menu.md @@ -109,7 +109,7 @@ An alternative and probably cleaner way is to use a route provider. First create ```js // route.provider.ts import { RoutesService, eLayoutType } from '@abp/ng.core'; -import { provideAppInitializer } from '@angular/core'; +import { inject, provideAppInitializer } from '@angular/core'; export const APP_ROUTE_PROVIDER = [ provideAppInitializer(() => { @@ -119,7 +119,7 @@ export const APP_ROUTE_PROVIDER = [ function configureRoutes() { const routesService = inject(RoutesService); - routes.add([ + routesService.add([ { path: '/your-path', name: 'Your navigation', @@ -145,10 +145,11 @@ We can also define a group for navigation elements. It's an optional property ```js // route.provider.ts import { RoutesService } from '@abp/ng.core'; +import { inject } from '@angular/core'; function configureRoutes() { const routesService = inject(RoutesService); - routes.add([ + routesService.add([ { //etc.. group: 'ModuleName::GroupName' diff --git a/docs/en/framework/ui/angular/oauth-module.md b/docs/en/framework/ui/angular/oauth-module.md index 9e6d4a8e1e..6209f77bb8 100644 --- a/docs/en/framework/ui/angular/oauth-module.md +++ b/docs/en/framework/ui/angular/oauth-module.md @@ -7,23 +7,56 @@ # ABP OAuth Package -The authentication functionality has been moved from @abp/ng.core to @abp/ng.oauth since v7.0. +The authentication implementation was moved from `@abp/ng.core` to `@abp/ng.oauth` in v7.0. The core package defines the authentication abstractions and tokens, while the OAuth package supplies their `angular-oauth2-oidc` implementations. -If your app is version 8.3 or higher, you should include "provideAbpOAuth()" after "provideAbpCore()" in the `appConfig` array of your `app.config.ts`. +The package is included in the Angular application templates. Install it if the application does not already reference it: -Those abstractions can be found in the @abp/ng-core packages. +```bash +npm install @abp/ng.oauth +``` + +## Standalone Setup + +Add `provideAbpOAuth()` after `provideAbpCore()` in the `providers` array of `app.config.ts`: + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideAbpCore, withOptions } from '@abp/ng.core'; +import { registerLocaleForEsBuild } from '@abp/ng.core/locale'; +import { provideAbpOAuth } from '@abp/ng.oauth'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAbpCore( + withOptions({ + environment, + registerLocaleFn: registerLocaleForEsBuild(), + }), + ), + provideAbpOAuth(), + ], +}; +``` + +`AbpOAuthModule.forRoot()` is deprecated. Use the standalone provider for new applications. + +## Registered Authentication Services + +`provideAbpOAuth()` registers or replaces these public core abstractions: -- `AuthService` (the class that implements the IAuthService interface). -- `NAVIGATE_TO_MANAGE_PROFILE` Inject token. -- `ApiInterceptor` (the class that implements the IApiInterceptor interface). +- `AuthService`, `AuthGuard`, `authGuard` and `asyncAuthGuard` with their OAuth implementations. +- `ApiInterceptor` and its `HTTP_INTERCEPTORS` registration. +- `PIPE_TO_LOGIN_FN_KEY` with the function used when authentication is required. +- `CHECK_AUTHENTICATION_STATE_FN_KEY` with the function that checks and stores the current authentication state. +- `NAVIGATE_TO_MANAGE_PROFILE` with navigation to the authority's account-management page. +- `AuthErrorFilterService` with the OAuth error filter. +- `OAuthStorage`, using `BrowserTokenStorageService` or `ServerTokenStorageService` for an SSR-started application and `MemoryTokenStorageService` otherwise. -Those base classes are overridden by the "AbpOAuthModule" for oAuth. There are also three functions provided with AbpOAuthModule. +It also registers the OAuth configuration initializer and the providers from `angular-oauth2-oidc`. -- `PIPE_TO_LOGIN_FN_KEY` a provide that calls a function when the user is not authenticated. The function should be PipeToLoginFn type. -- `SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY` a provide that calls a function when the user is authenticated. The function should be SetTokenResponseToStorageFn type. -- `CHECK_AUTHENTICATION_STATE_FN_KEY` a provide that calls a function when the user is authenticated and stores the auth state. The function should be CheckAuthenticationStateFn type. - The tokens and interfaces are in the `@abp/ng.core` package but the implementation of these interfaces is in the `@abp/ng.oauth` package. +## API Interceptor -If you want to make your own authentication system, you must also change these 'abstract' classes. +For non-external requests, the OAuth API interceptor adds an `Authorization` bearer token, `Accept-Language`, the configured tenant header and `X-Requested-With` when the corresponding values are available. Existing authorization, language and tenant headers are preserved. Requests marked with the `IS_EXTERNAL_REQUEST` HTTP context token are sent without those ABP headers. The interceptor also integrates every request with the HTTP wait service. -ApiInterceptor is provided by `@abp/ng.core` but overridden with `@abp/ng.oauth`. The ApiInterceptor adds the token, accepted-language, and tenant id to the header of the HTTP request. It also calls the http-wait service. +To implement another authentication system, provide replacements for the core services and tokens used by the application instead of depending on the OAuth implementations. diff --git a/docs/en/framework/ui/angular/permission-management.md b/docs/en/framework/ui/angular/permission-management.md index 8d30d925bb..5ec8497af4 100644 --- a/docs/en/framework/ui/angular/permission-management.md +++ b/docs/en/framework/ui/angular/permission-management.md @@ -42,7 +42,8 @@ const hasIdentityOrAccountPermission = this.permissionService.getGrantedPolicy( Please consider the following **rules** when creating your permission selectors: -- Maximum 2 keys can be combined. +- Two or more keys can be combined with the same operator. +- Do not mix `&&` and `||` in the same selector. - `&&` operator looks for both keys. - `||` operator looks for either key. - Empty string `''` as key will return `true` @@ -89,18 +90,14 @@ In some cases, a custom permission management may be needed. All you need to do - First, create a service of your own. Let's call it `CustomPermissionService` and extend `PermissionService` from `@abp/ng.core` as follows: -```js -import { ConfigStateService, PermissionService } from '@abp/ng.core'; -import { Injectable, inject } from '@angular/core'; +```ts +import { PermissionService } from '@abp/ng.core'; +import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class CustomPermissionService extends PermissionService { - constructor() { - super(inject(ConfigStateService)); - } - // This is an example to show how to override the methods getGrantedPolicy$(key: string) { return super.getGrantedPolicy$(key); diff --git a/docs/en/framework/ui/angular/ssr-configuration.md b/docs/en/framework/ui/angular/ssr-configuration.md index 4d889377fd..87f95185a3 100644 --- a/docs/en/framework/ui/angular/ssr-configuration.md +++ b/docs/en/framework/ui/angular/ssr-configuration.md @@ -504,6 +504,21 @@ export class MyService { - `key(index: number): string | null` - `length: number` +#### Session and Cookie Storage + +`AbpCookieStorageService` provides the cookie-backed `Storage` implementation used during SSR. On the server, it reads cookies from the incoming Angular `REQUEST`; write and remove operations do nothing. In the browser, it reads and writes `document.cookie`. Values written with `setItem` use `Path=/`, `SameSite=Lax`, and `Secure`. Use `setItemWithExpiry` when a cookie also needs a maximum age. + +`SessionStateService` selects its storage automatically: + +- When the application started with SSR, it persists the `abpSession` value in `AbpCookieStorageService` so the server request can read the session state. +- Otherwise, it persists `abpSession` in `AbpLocalStorageService`. + +Application code normally uses `SessionStateService` for the current language and tenant instead of reading `abpSession` directly. + +#### Cross-Tab Authentication Changes + +`provideAbpCore` initializes `LocalStorageListenerService`. In the browser, this service listens for storage changes to the `access_token` key. When another browser context adds or removes that token, the current page navigates to `/` so its authentication state is refreshed. + ### 9.3. Hydration Mismatch Errors If you see "NG0500" errors in the console: @@ -515,7 +530,7 @@ If you see "NG0500" errors in the console: ### 9.4. Avoiding Duplicate API Calls -ABP Core provides a `transferStateInterceptor` that automatically prevents duplicate HTTP GET requests during hydration. When you use `provideAbpCore()`, this interceptor is already active. +ABP Core provides a `transferStateInterceptor` that automatically prevents duplicate HTTP GET requests during hydration. When you configure `provideAbpCore(withOptions(...))`, this interceptor is already active. **How it works:** - Server: Stores HTTP GET responses in `TransferState` @@ -524,16 +539,26 @@ ABP Core provides a `transferStateInterceptor` that automatically prevents dupli ```typescript // app.config.ts -import { provideAbpCore } from '@abp/ng.core'; +import { provideAbpCore, withOptions } from '@abp/ng.core'; +import { registerLocaleForEsBuild } from '@abp/ng.core/locale'; +import { ApplicationConfig } from '@angular/core'; +import { environment } from '../environments/environment'; export const appConfig: ApplicationConfig = { providers: [ - provideAbpCore(), + provideAbpCore( + withOptions({ + environment, + registerLocaleFn: registerLocaleForEsBuild(), + }), + ), // transferStateInterceptor is automatically included ] }; ``` +The example uses the Angular application builder. If your SSR application uses the Webpack builder, use `registerLocale()` instead. See [Registering a New Locale](localization.md#registering-a-new-locale) for the builder-specific configuration. + The interceptor works with all HTTP GET requests made through `HttpClient`: ```typescript diff --git a/docs/en/framework/ui/angular/testing.md b/docs/en/framework/ui/angular/testing.md index 4ec5577f73..112a785e49 100644 --- a/docs/en/framework/ui/angular/testing.md +++ b/docs/en/framework/ui/angular/testing.md @@ -137,6 +137,35 @@ expect(deleteSpy).toHaveBeenCalledWith("some-id"); The template's `home.component.spec.ts` is a good reference for mocking ABP services and asserting DOM behavior with `TestBed`. +### Configuring Permission Results + +`CoreTestingModule.withConfig()` replaces `PermissionService` with `MockPermissionService`. The mock grants every policy by default, which keeps unrelated permission checks from hiding components in a test. + +Use `grantPolicies` when a spec needs a specific authorization state. Policies not included in the array are denied: + +```ts +import { PermissionService } from '@abp/ng.core'; +import { MockPermissionService } from '@abp/ng.core/testing'; +import { TestBed } from '@angular/core/testing'; + +let permissionService: MockPermissionService; + +beforeEach(() => { + permissionService = TestBed.inject(PermissionService) as MockPermissionService; +}); + +it('shows the create action only for the create policy', () => { + permissionService.grantPolicies(['BookStore.Books.Create']); + + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector('[data-testid="create-book"]')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('[data-testid="delete-book"]')).toBeFalsy(); +}); +``` + +Call `grantAllPolicies()` to restore the grant-all behavior in a test that previously selected individual policies. + ## Tips ### Clearing DOM After Each Spec diff --git a/docs/en/framework/ui/angular/title-strategy.md b/docs/en/framework/ui/angular/title-strategy.md new file mode 100644 index 0000000000..831f2e9bdf --- /dev/null +++ b/docs/en/framework/ui/angular/title-strategy.md @@ -0,0 +1,96 @@ +```json +//[doc-seo] +{ + "Description": "Configure localized Angular route titles, the application-name suffix, and a custom TitleStrategy in an ABP application." +} +``` + +# Document Title Strategy + +`provideAbpCore` registers `AbpTitleStrategy` as Angular's default `TitleStrategy`. It reads the deepest active route title, localizes it, and updates the browser document title. + +## Setting a Route Title + +Use Angular's `title` route property. The value can be an ABP localization key: + +```ts +import { Routes } from '@angular/router'; +import { BooksComponent } from './books.component'; + +export const routes: Routes = [ + { + path: 'books', + component: BooksComponent, + title: 'BookStore::Menu:Books', + }, +]; +``` + +With an application name of `Book Store`, the resulting title is: + +```text +Books | Book Store +``` + +The strategy resolves the application name from the `::AppName` localization key. If a route has no title, it uses only the application name. It also recalculates the active title when the language changes. + +## Removing the Application-Name Suffix + +Provide `DISABLE_PROJECT_NAME` with `true` to omit the application name from routes that have a title: + +```ts +import { DISABLE_PROJECT_NAME } from '@abp/ng.core'; +import { ApplicationConfig } from '@angular/core'; + +export const appConfig: ApplicationConfig = { + providers: [ + { + provide: DISABLE_PROJECT_NAME, + useValue: true, + }, + ], +}; +``` + +The route above then produces `Books`. A route without a title still falls back to the application name. + +## Replacing the Strategy + +Create an Angular `TitleStrategy` and pass it to the `withTitleStrategy` feature when the application needs a different title convention: + +```ts +import { Title } from '@angular/platform-browser'; +import { RouterStateSnapshot, TitleStrategy } from '@angular/router'; +import { inject, Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class CustomTitleStrategy extends TitleStrategy { + private readonly title = inject(Title); + + override updateTitle(routerState: RouterStateSnapshot): void { + const routeTitle = this.buildTitle(routerState); + this.title.setTitle(routeTitle ? `My Application - ${routeTitle}` : 'My Application'); + } +} +``` + +Register it together with the normal ABP Core options: + +```ts +import { provideAbpCore, withOptions, withTitleStrategy } from '@abp/ng.core'; +import { registerLocaleForEsBuild } from '@abp/ng.core/locale'; +import { ApplicationConfig } from '@angular/core'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAbpCore( + withOptions({ + environment, + registerLocaleFn: registerLocaleForEsBuild(), + }), + withTitleStrategy(CustomTitleStrategy), + ), + ], +}; +``` diff --git a/docs/en/framework/ui/angular/tree-component.md b/docs/en/framework/ui/angular/tree-component.md new file mode 100644 index 0000000000..bf66cfee12 --- /dev/null +++ b/docs/en/framework/ui/angular/tree-component.md @@ -0,0 +1,138 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to use the ABP Angular tree component for hierarchical data, selection, templates and drag-and-drop." +} +``` + +# Tree Component + +`TreeComponent` is a standalone tree control exported by `@abp/ng.components/tree`. It supports selection, checkboxes, expansion, context-menu templates and drag-and-drop. + +The `@abp/ng.components` package is included in the Angular application templates. Install it if the application does not already reference it: + +```bash +npm install @abp/ng.components +``` + +Import it into a standalone component and provide nodes in the format expected by the underlying NG-ZORRO tree: + +```ts +import { Component, signal } from '@angular/core'; +import { + DropEvent, + ExpandedIconTemplateDirective, + TreeComponent, + TreeNodeTemplateDirective, +} from '@abp/ng.components/tree'; +import { of } from 'rxjs'; + +interface Category { + id: string; + name: string; +} + +@Component({ + selector: 'app-category-tree', + templateUrl: './category-tree.component.html', + imports: [ + TreeComponent, + TreeNodeTemplateDirective, + ExpandedIconTemplateDirective, + ], +}) +export class CategoryTreeComponent { + readonly nodes = signal([ + { + key: 'books', + title: 'Books', + entity: { id: 'books', name: 'Books' } as Category, + children: [], + isLeaf: true, + }, + ]); + + readonly expandedKeys = signal([]); + readonly selectedCategory = signal(null); + + readonly allowDrop = () => of(true); + + handleDrop(event: DropEvent) { + console.log(event.dragNode?.key); + } + + edit(key: string) { + console.log(key); + } +} +``` + +```html + + + + + +``` + +The main inputs are: + +- `nodes`, `checkedKeys`, `expandedKeys` and `selectedNode` for tree state. +- `draggable`, `checkable`, `checkStrictly` and `noAnimation` for tree behavior. +- `changeCheckboxWithNode` to update checked keys when a node is selected. +- `isNodeSelected` to replace the default selected-node comparison. +- `beforeDrop` to approve or reject a drag-and-drop operation. + +State changes are exposed through `checkedKeysChange`, `expandedKeysChange`, `selectedNodeChange`, `dropOver` and `nzExpandChange`. + +The default `beforeDrop` handler rejects drops. Supply a handler that returns an observable accepted by the underlying tree control when drag-and-drop is enabled. + +## Templates + +Use the `#menu` template, as in the previous example, to add a context menu for each node. Use `abpTreeNodeTemplate` and `abpTreeExpandedIconTemplate` to replace the node and expanded-icon templates: + +{%{ +```html + + + {{ node.title }} + + + + {{ node.isExpanded ? '−' : '+' }} + + +``` +}%} + +The component example imports `TreeNodeTemplateDirective` and `ExpandedIconTemplateDirective` because Angular must see each directive used by a standalone component template. If you use only one of these templates, import only its corresponding directive. + +## Flat-List Adapter + +`TreeAdapter` converts a flat list of `BaseNode` values into tree nodes. Each item requires an `id` and a nullable `parentId`; its `displayName` or `name` becomes the default node title. Use `getTree()`, `handleDrop()`, `handleRemove()` and `handleUpdate()` to keep the flat list and tree representations synchronized. + +## Style Loading + +The component loads `ng-zorro-antd-tree.css` when it is initialized. Provide `DISABLE_TREE_STYLE_LOADING_TOKEN` with `true` only when the application already includes that stylesheet: + +```ts +import { DISABLE_TREE_STYLE_LOADING_TOKEN } from '@abp/ng.components/tree'; + +export const providers = [ + { + provide: DISABLE_TREE_STYLE_LOADING_TOKEN, + useValue: true, + }, +]; +``` diff --git a/docs/en/framework/ui/blazor/authentication.md b/docs/en/framework/ui/blazor/authentication.md index a793a784bd..40bc6cf40f 100644 --- a/docs/en/framework/ui/blazor/authentication.md +++ b/docs/en/framework/ui/blazor/authentication.md @@ -31,4 +31,18 @@ This is a typical and recommended approach to implement authentication in Single See the [Blazor Security document](https://docs.microsoft.com/en-us/aspnet/core/blazor/security) to understand and customize the authentication process. -{{end}} \ No newline at end of file +{{end}} + +## Authentication URLs + +`AbpAuthenticationOptions` centralizes the login and logout routes used by ABP Blazor authentication services. The shared web defaults are `Account/Login` and `Account/Logout`. A standalone Blazor WebAssembly application changes them to `authentication/login` and `authentication/logout`; this override is not applied when the client is hosted as part of a Blazor Web App. + +Configure the options when the application uses custom routes: + +````csharp +Configure(options => +{ + options.LoginUrl = "authentication/sign-in"; + options.LogoutUrl = "authentication/sign-out"; +}); +```` diff --git a/docs/en/framework/ui/blazor/global-scripts-styles.md b/docs/en/framework/ui/blazor/global-scripts-styles.md index 998a16a50a..02ae892089 100644 --- a/docs/en/framework/ui/blazor/global-scripts-styles.md +++ b/docs/en/framework/ui/blazor/global-scripts-styles.md @@ -7,7 +7,7 @@ # Blazor UI: Managing Global Scripts & Styles -You can add your JavaScript and CSS files from your modules or applications to the Blazor global assets system. All the JavaScript and CSS files will be added to the `global.js` and `global.css` files. You can access these files via the following URL in a Blazor WASM project: +You can add your JavaScript and CSS files from your modules or applications to the Blazor global assets system. By default, all the JavaScript and CSS files are added to the `global.js` and `global.css` files. You can access these files via the following URL in a Blazor WASM project: - https://localhost/global.js - https://localhost/global.css @@ -71,9 +71,37 @@ This is similar to the module. You need to define JavaScript and CSS contributor ## AbpBundlingGlobalAssetsOptions -You can configure the JavaScript and CSS file names in the `GlobalAssets` property of the `AbpBundlingOptions` class. The default values are `global.js` and `global.css`. +The `GlobalAssets` property of `AbpBundlingOptions` is shared by the MVC, Blazor WebAssembly and MAUI Blazor bundling integrations. It has the following properties: + +* `Enabled`: Enables global asset generation. The Blazor WebAssembly and MAUI Blazor theming modules enable it when they configure their global bundles. +* `GlobalStyleBundleName`: The style bundle used to generate the global CSS asset. +* `GlobalScriptBundleName`: The script bundle used to generate the global JavaScript asset. +* `CssFileName`: The generated CSS file name. The default is `global.css`. +* `JavaScriptFileName`: The generated JavaScript file name. The default is `global.js`. + +```csharp +Configure(options => +{ + options.GlobalAssets.Enabled = true; + options.GlobalAssets.GlobalStyleBundleName = + BlazorWebAssemblyStandardBundles.Styles.Global; + options.GlobalAssets.GlobalScriptBundleName = + BlazorWebAssemblyStandardBundles.Scripts.Global; + options.GlobalAssets.CssFileName = "app-global.css"; + options.GlobalAssets.JavaScriptFileName = "app-global.js"; +}); +``` + +When you change `CssFileName` or `JavaScriptFileName`, update the host page references to the same names. For a standalone Blazor WebAssembly host, replace the default references in `App.razor`: + +```html + + +``` + +For a Blazor Web App, replace `global.css` and `global.js` in the `GlobalStyles` and `GlobalScripts` lists in `Components/App.razor`. Changing only `AbpBundlingOptions` generates the files under the new names but does not rewrite these host references. ## Reference -- [ASP.NET Core MVC Bundling & Minification](../mvc-razor-pages/bundling-minification#bundle-contributorsg) +- [ASP.NET Core MVC Bundling & Minification](../mvc-razor-pages/bundling-minification.md#bundle-contributors) - [ABP Global Assets - New way to bundle JavaScript/CSS files in Blazor WebAssembly app](https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md) diff --git a/docs/en/framework/ui/maui/index.md b/docs/en/framework/ui/maui/index.md index 2622bdc7e0..1bd089b26d 100644 --- a/docs/en/framework/ui/maui/index.md +++ b/docs/en/framework/ui/maui/index.md @@ -34,9 +34,9 @@ Open the `appsettings.json` in the `MAUI` project: {{ end }} -After ensuring the backend application is running and the `appsettings.json` is properly configured in the mobile application, you can proceed to run the mobile application. You can run the application either by using the `dotnet build` command (e.g. `dotnet build -t:Run -f net9.0-android` for Android or `dotnet build -t:Run -f net9.0-ios` for iOS) or by running it through Visual Studio or any other IDE that supports MAUI. +After ensuring the backend application is running and the `appsettings.json` is properly configured in the mobile application, you can proceed to run the mobile application. You can run the application either by using the `dotnet build` command (e.g. `dotnet build -t:Run -f net10.0-android` for Android or `dotnet build -t:Run -f net10.0-ios` for iOS) or by running it through Visual Studio or any other IDE that supports MAUI. -> For more information about running the mobile application, please refer to the [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/maui/?view=net-maui-9.0). +> For more information about running the mobile application, please refer to the [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/maui/?view=net-maui-10.0). You can examine the [Users Page](#users-page) or any other pre-defined page to see how to use CSharp Client Proxy to request backend API and consume the backend API in the same way in your application. Also, if you encounter any errors on specific platforms, you can refer to the following sections for each platform to find common issues and their solutions. diff --git a/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md b/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md new file mode 100644 index 0000000000..436e6b54af --- /dev/null +++ b/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md @@ -0,0 +1,57 @@ +```json +//[doc-seo] +{ + "Description": "Use ABP's MVC JavaScript Clock API for time-zone detection, date normalization, localized display, and the browser time-zone cookie." +} +``` + +# ASP.NET Core MVC / Razor Pages UI: JavaScript Clock API + +The `abp.clock` namespace provides date, time-zone and browser-time-zone helpers for MVC / Razor Pages applications. The application configuration script sets `abp.clock.kind` from the server-side ABP clock configuration. + +## Time-Zone Support + +`abp.clock.supportsMultipleTimezone()` returns `true` when the configured clock kind is `Utc`. `abp.clock.timeZone()` returns the value of the `Abp.Timing.TimeZone` setting when it is available and otherwise returns the browser's IANA time-zone name. + +````js +if (abp.clock.supportsMultipleTimezone()) { + console.log(abp.clock.timeZone()); +} +```` + +## Normalize Date Values + +Use `normalizeToString` before sending a date value to the server and `normalizeToLocaleString` before displaying a server value: + +````js +const requestValue = abp.clock.normalizeToString(new Date()); +const displayValue = abp.clock.normalizeToLocaleString(requestValue); +```` + +`normalizeToString` returns an ISO-compatible date-time string. For a non-UTC clock, the core implementation uses `yyyy-MM-ddTHH:mm:ss`. Both normalization methods return empty or invalid values unchanged. + +The standard shared MVC theme bundle loads Luxon and replaces the core implementations of `normalizeToString` and `normalizeToLocaleString`. When multiple time zones are supported, this Luxon implementation interprets the input in the configured IANA time zone, converts it to UTC and returns an ISO value ending in `Z`. The output can include milliseconds (for example, `2026-07-17T08:30:00.000Z`), so do not require an exact string length when consuming it. + +If an application uses the core scripts without the shared theme's Luxon contributor, the fallback implementation produces a `Z`-suffixed transport value by detecting a numeric browser offset. It is not a full IANA time-zone conversion and does not account for fractional-hour offsets or an offset change between the current date and the input date. Include the Luxon contributor when those cases must be handled. + +`normalizeToLocaleString` accepts standard `Intl.DateTimeFormat` options. When no options are supplied, it uses `abp.clock.toLocaleStringOptions`. The default options include the numeric year, long month, numeric day, hour, minute and second. You can replace them to define application-wide display defaults: + +````js +abp.clock.toLocaleStringOptions = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' +}; +```` + +## Browser Time-Zone Cookie + +After application configuration is initialized, ABP writes the browser's time-zone name to the `__timezone` cookie when multiple time zones are supported. Set the following flag before configuration initialization to disable this behavior: + +````js +abp.clock.trySetBrowserTimeZoneToCookie = false; +```` + +Use `abp.clock.browserTimeZone()` to read the browser time zone or `abp.clock.setBrowserTimeZoneToCookie()` to refresh the cookie explicitly. diff --git a/docs/en/framework/ui/mvc-razor-pages/javascript-api/index.md b/docs/en/framework/ui/mvc-razor-pages/javascript-api/index.md index 193575def6..8bb09645f7 100644 --- a/docs/en/framework/ui/mvc-razor-pages/javascript-api/index.md +++ b/docs/en/framework/ui/mvc-razor-pages/javascript-api/index.md @@ -13,6 +13,7 @@ ABP provides a set of JavaScript APIs for ASP.NET Core MVC / Razor Pages applica * [AJAX](ajax.md) * [Auth](auth.md) +* [Clock](clock.md) * [CurrentUser](current-user.md) * [DOM](dom.md) * [Events](events.md) @@ -24,4 +25,4 @@ ABP provides a set of JavaScript APIs for ASP.NET Core MVC / Razor Pages applica * [Settings](settings.md) * [UI Block/Busy](block-busy.md) * [UI Message](message.md) -* [UI Notification](notify.md) \ No newline at end of file +* [UI Notification](notify.md) diff --git a/docs/en/framework/ui/mvc-razor-pages/overall.md b/docs/en/framework/ui/mvc-razor-pages/overall.md index 425c94d2d9..b029fda57c 100644 --- a/docs/en/framework/ui/mvc-razor-pages/overall.md +++ b/docs/en/framework/ui/mvc-razor-pages/overall.md @@ -164,4 +164,16 @@ ABP provides a lot of built-in solutions to common application requirements; ## Customization -There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](customization-user-interface.md). \ No newline at end of file +There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](customization-user-interface.md). + +### Status-Specific Error Views + +The shared MVC theme uses `~/Views/Error/Default.cshtml` by default. Map an HTTP status code to another view with `AbpErrorPageOptions.ErrorViewUrls`: + +````csharp +Configure(options => +{ + options.ErrorViewUrls["404"] = "~/Views/Error/NotFound.cshtml"; + options.ErrorViewUrls["500"] = "~/Views/Error/InternalError.cshtml"; +}); +```` diff --git a/docs/en/framework/ui/react/authorization.md b/docs/en/framework/ui/react/authorization.md index 4f6d673d87..9111badaa3 100644 --- a/docs/en/framework/ui/react/authorization.md +++ b/docs/en/framework/ui/react/authorization.md @@ -89,6 +89,28 @@ client = createAbpReactOidcAuth({ The template stores the OIDC user in local storage and enables silent renewal with `public/silent-renew.html`. +## Lower-Level OIDC Client + +`@volo/abp-oidc-auth` is the framework-agnostic client used by the React OIDC adapter. Use it directly in another JavaScript runtime or supply it to `createAbpReactOidcAuth`: + +```ts +import { createAbpOidcAuth } from '@volo/abp-oidc-auth' +import { createAbpReactOidcAuth } from '@volo/abp-react-oidc-auth' + +const client = createAbpOidcAuth({ + authority: 'https://localhost:44301/', + clientId: 'MyProject_App', + redirectUri: window.location.origin, + postLogoutRedirectUri: window.location.origin, + scope: 'offline_access MyProject', +}) + +const auth = createAbpReactOidcAuth({ client }) +await client.init() +``` + +Use `subscribe()` for ABP authentication lifecycle events and `getSnapshot()` for the current user, profile, token and initialization state. `clearStaleState()` removes abandoned OIDC state entries. The client also exposes the underlying `UserManager`, its events and the configured authority for integrations that need lower-level OIDC control. + ## Auth Provider and Hook `AuthProvider` wraps the app and handles the OIDC callback: diff --git a/docs/en/framework/ui/react/permission-management.md b/docs/en/framework/ui/react/permission-management.md index fc009ce5a2..fabfefcea2 100644 --- a/docs/en/framework/ui/react/permission-management.md +++ b/docs/en/framework/ui/react/permission-management.md @@ -29,6 +29,42 @@ export const appConfig = createAbpReactAppConfig({ }) ``` +## Framework-Agnostic Application Configuration Client + +`@volo/abp-app-config` provides the application-configuration client without a React dependency. Use it directly in another JavaScript runtime or pass an existing client to the React adapter: + +```ts +import { createAbpAppConfig } from '@volo/abp-app-config' +import { createAbpReactAppConfig } from '@volo/abp-react-app-config' + +const client = createAbpAppConfig({ + baseUrl: 'https://localhost:44300', +}) + +const appConfig = createAbpReactAppConfig({ client }) +``` + +The lower-level client exposes: + +- `fetchConfig()` and `fetchLocalization()` to load server data. +- `refetch()` to reload the application configuration. +- `getSnapshot()` and `getSections()` to read the current state. +- `subscribe(listener)` to observe state changes. It returns an unsubscribe function. +- `t()` and `tWithFallback()` for localization. +- `clear()` to reset the client state. + +```ts +const unsubscribe = client.subscribe(snapshot => { + console.log(snapshot.initialized, snapshot.currentCulture) +}) + +await client.fetchConfig() +await client.fetchLocalization('en') + +unsubscribe() +client.clear() +``` + ## Fetching Permissions After the user logs in, `AuthProvider` fetches application configuration with the current access token: diff --git a/docs/en/modules/identity/ldap.md b/docs/en/modules/identity/ldap.md index c01bc630ef..2c3e74f105 100644 --- a/docs/en/modules/identity/ldap.md +++ b/docs/en/modules/identity/ldap.md @@ -48,17 +48,17 @@ public class VoloOpenLdapManager : OpenLdapManager } - protected override async Task NormalizeUserNameAsync(string userName) + protected override Task NormalizeUserNameAsync(string userName) { - // or "userName@domain + // or "userName@domain" // await LdapSettingProvider.GetDomainAsync() return Task.FromResult($"Volo\\{userName}"); } protected override Task GetUserFilterAsync(string userName) { - // Default is $"cn={userName},{LdapOptions.BaseDc}" - return return Task.FromResult($"(&(objectClass=user)(sAMAccountName={userName}))"); + // Default is $"(&(uid={userName}))" + return Task.FromResult($"(&(objectClass=user)(sAMAccountName={userName}))"); } protected override Task GetUserEmailAsync(LdapEntry ldapEntry) @@ -96,10 +96,10 @@ public class VoloLdapExternalLoginProvider : LdapExternalLoginProvider } - protected override async Task NormalizeUserNameAsync(string userName) + protected override Task NormalizeUserNameAsync(string userName) { // Default is $"uid={userName}, {BaseDc}" - // or "userName@domain + // or "userName@domain" // await LdapSettingProvider.GetDomainAsync() return Task.FromResult($"Volo\\{userName}"); } @@ -128,5 +128,3 @@ The following simple Dotnet Console App is for testing your LDAP configuration. Before configuring your LDAP settings, you can test the information via this app to see if it works. [ABP Ldap Test Application](https://github.com/abpframework/abp-samples/tree/master/AbpLdapSample) - - diff --git a/docs/en/modules/virtual-file-explorer.md b/docs/en/modules/virtual-file-explorer.md index a891e57140..352bbd0504 100644 --- a/docs/en/modules/virtual-file-explorer.md +++ b/docs/en/modules/virtual-file-explorer.md @@ -55,7 +55,7 @@ Or you can also manually install nuget package to `Acme.MyProject.Web` project: ##### 2.2- Adding NPM Package - * Open `package.json` and add `@abp/virtual-file-explorer": "^2.9.0` as shown below: + * Open `package.json` and add `@abp/virtual-file-explorer` with the same version as the other `@abp` packages, as shown below: ```json { @@ -63,8 +63,8 @@ Or you can also manually install nuget package to `Acme.MyProject.Web` project: "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0", - "@abp/virtual-file-explorer": "^2.9.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.6.0", + "@abp/virtual-file-explorer": "~10.6.0" } } ``` @@ -91,4 +91,4 @@ public override void PreConfigureServices(ServiceConfigurationContext context) options.IsEnabled = false; }); } -``` \ No newline at end of file +``` diff --git a/docs/en/multi-lingual-entities.md b/docs/en/multi-lingual-entities.md index b0419fe2a1..3802c39018 100644 --- a/docs/en/multi-lingual-entities.md +++ b/docs/en/multi-lingual-entities.md @@ -1,6 +1,99 @@ -# Multi Lingual Entities +```json +//[doc-seo] +{ + "Description": "Use ABP multi-lingual objects to select translations by culture, apply fallback rules, and process translations in bulk." +} +``` -This feature is still under development. -Follow the below link to get information about the development status +# Multi-Lingual Objects -https://github.com/abpframework/abp/issues/11698 +The `Volo.Abp.MultiLingualObject` package provides a contract and a selection service for objects that store one translation per language. Persistence mapping is application-specific; the package does not create a database relationship for the translations. + +## Installation + +Install the package in the project that defines the consuming module: + +```bash +abp add-package Volo.Abp.MultiLingualObject +``` + +Add `AbpMultiLingualObjectsModule` as a dependency of that module when the package is installed manually: + +````csharp +[DependsOn(typeof(AbpMultiLingualObjectsModule))] +public class MyApplicationModule : AbpModule +{ +} +```` + +## Define a Multi-Lingual Object + +Implement `IMultiLingualObject` on the object and `IObjectTranslation` on its translation type: + +```csharp +using Volo.Abp.MultiLingualObjects; + +public class Product : IMultiLingualObject +{ + public ICollection Translations { get; set; } = + new List(); +} + +public class ProductTranslation : IObjectTranslation +{ + public string Language { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; +} +``` + +`Language` stores a culture name such as `en`, `en-US` or `tr`. + +## Select a Translation + +Inject `IMultiLingualObjectManager` and call `GetTranslationAsync`: + +```csharp +public class ProductService + : ITransientDependency +{ + private readonly IMultiLingualObjectManager _multiLingualObjectManager; + + public ProductService( + IMultiLingualObjectManager multiLingualObjectManager) + { + _multiLingualObjectManager = multiLingualObjectManager; + } + + public Task GetTranslationAsync(Product product) + { + return _multiLingualObjectManager + .GetTranslationAsync(product); + } +} +``` + +With the default arguments, the manager uses `CultureInfo.CurrentUICulture.Name`. It selects translations in this order: + +1. An exact translation for the current UI culture. +2. A translation for a parent of the current UI culture when `fallbackToParentCultures` is `true`. +3. A translation for the language configured by `LocalizationSettingNames.DefaultLanguage`. +4. The first available translation. + +The method returns `null` when the collection is null or empty. To disable only the parent-culture fallback, pass `culture` and set `fallbackToParentCultures` to `false`. The default-language and first-available fallbacks still apply. + +## Select Translations in Bulk + +Use `GetBulkTranslationsAsync` to select translations for multiple objects with the same culture and fallback settings: + +```csharp +var results = await _multiLingualObjectManager + .GetBulkTranslationsAsync(products); + +foreach (var (product, translation) in results) +{ + // Use product and its selected translation. +} +``` + +Each result keeps the source object together with its selected translation. An object with no translations gets a `null` translation. From 59c84dd22d88d52652ea7e5baca1d192e121d7c8 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 17 Jul 2026 17:30:23 +0800 Subject: [PATCH 02/41] Improve Audit Logging documentation coverage --- .../framework/infrastructure/audit-logging.md | 2 + docs/en/modules/audit-logging-pro.md | 131 +++++++++++++++++- docs/en/modules/audit-logging.md | 29 +++- 3 files changed, 155 insertions(+), 7 deletions(-) diff --git a/docs/en/framework/infrastructure/audit-logging.md b/docs/en/framework/infrastructure/audit-logging.md index 9c748c8732..c5885f0d01 100644 --- a/docs/en/framework/infrastructure/audit-logging.md +++ b/docs/en/framework/infrastructure/audit-logging.md @@ -311,6 +311,8 @@ An **audit log object** is created for each **web request** by default. An audit * **Exception**: An audit log object may contain zero or more exception. In this way, you can get a report of the failed requests. * **Comment**: An arbitrary string value to add custom messages to the audit log entry. An audit log object may contain zero or more comments. +> When the [Audit Logging Module](../../modules/audit-logging.md) persists exceptions, it uses `AbpExceptionHandlingOptions` to convert them. `SendExceptionsDetailsToClients`, `SendStackTraceToClients` and `SendExceptionDataToClientTypes` therefore also control the exception details stored in audit logs, not only the details sent to clients. Review these options when audit logs may contain sensitive information. See the [Exception Handling](../fundamentals/exception-handling.md#abpexceptionhandlingoptions) document for configuration details. + In addition to the standard properties explained above, `AuditLogInfo`, `AuditLogActionInfo` and `EntityChangeInfo` objects implement the `IHasExtraProperties` interface, so you can add custom properties to these objects. ## Audit Log Contributors diff --git a/docs/en/modules/audit-logging-pro.md b/docs/en/modules/audit-logging-pro.md index 851787d390..1e7ab562dc 100644 --- a/docs/en/modules/audit-logging-pro.md +++ b/docs/en/modules/audit-logging-pro.md @@ -25,7 +25,7 @@ See [the module description page](https://abp.io/modules/Volo.AuditLogging.Ui) f ## How to install -Identity is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it. +Audit Logging is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it. ### Packages @@ -41,7 +41,7 @@ Audit logs module adds the following items to the "Main" menu, under the "Admini * **Audit Logs**: List, view and filter audit logs and entity changes. -`IAbpAuditLoggingMainMenuNames` class has the constants for the menu item names. +`AbpAuditLoggingMainMenuNames` class has the constants for the menu item names. ### Pages @@ -67,7 +67,7 @@ You can view details of an audit log by clicking the magnifier icon on each audi ##### Export to Excel -You can export audit logs to Excel by clicking the "Export to Excel" button in the toolbar. If the result set is small (less than a configurable threshold), the file will be generated and downloaded immediately. For larger result sets, the export will be processed as a background job and you'll receive an email with a download link once the export is completed. +You can export audit logs to Excel by clicking the "Export to Excel" button in the toolbar. The file is generated and downloaded immediately when the result set contains 1,000 records or fewer. If the result set contains more than 1,000 records, the export is processed as a background job and you'll receive an email with a download link once the export is completed. #### Entity Changes @@ -97,7 +97,7 @@ You can view details of all changes of an entity by clicking the "Full Change Hi ##### Export to Excel -You can export entity changes to Excel by clicking the "Export to Excel" button in the toolbar. Similar to audit logs export, for large datasets the export will be processed as a background job and you'll receive an email notification once completed. +You can export entity changes to Excel by clicking the "Export to Excel" button in the toolbar. As with audit log exports, result sets with 1,000 records or fewer are downloaded immediately. Result sets with more than 1,000 records are processed as a background job, and you'll receive an email notification once the export is completed. #### Audit Log Settings @@ -113,6 +113,111 @@ To view the audit log settings, you need to enable the feature. For the host sid > If you don't enable the *Cleanup Service System Wide* from the host side under *Settings* -> *Audit logs* -> *Global*, it won't remove the expired audit logs, even if there are tenant specific settings. +## Reusable widgets + +The module provides **Error Rate** and **Average Execution Duration Per Day** widgets. The current user needs the `AuditLogging.AuditLogs` permission to load their data. + +### Angular + +Import the widget components from `@volo/abp.ng.audit-logging`, add them to your component imports and keep references when you need to refresh their date range: + +```ts +import { Component, ViewChild } from '@angular/core'; +import { + AverageExecutionDurationWidgetComponent, + ErrorRateWidgetComponent, +} from '@volo/abp.ng.audit-logging'; + +@Component({ + selector: 'app-audit-statistics', + templateUrl: './audit-statistics.component.html', + imports: [ + AverageExecutionDurationWidgetComponent, + ErrorRateWidgetComponent, + ], +}) +export class AuditStatisticsComponent { + @ViewChild('averageExecutionDurationWidget') + averageExecutionDurationWidget!: AverageExecutionDurationWidgetComponent; + + @ViewChild('errorRateWidget') + errorRateWidget!: ErrorRateWidgetComponent; + + refresh(startDate: string, endDate: string) { + this.averageExecutionDurationWidget.draw({ startDate, endDate }); + this.errorRateWidget.draw({ startDate, endDate }); + } +} +``` + +The `width` and `height` inputs are optional. Both default to `273` and `136`, respectively. + +```html + + + +``` + +### Blazor + +The Bootstrap and MudBlazor packages expose components with the same parameters and `RefreshAsync` method. The following example uses the Bootstrap Blazor package. For MudBlazor, use the corresponding `Volo.Abp.AuditLogging.Blazor.MudBlazor` namespaces. + +```razor +@using Volo.Abp.AuditLogging.Blazor.Pages.Shared.AverageExecutionDurationPerDayWidget +@using Volo.Abp.AuditLogging.Blazor.Pages.Shared.ErrorRateWidget + + + + + +@code { + private DateTime StartDate { get; set; } = DateTime.Today.AddMonths(-1); + private DateTime EndDate { get; set; } = DateTime.Today; + + private AuditLoggingAverageExecutionDurationPerDayWidgetComponent AverageExecutionDurationWidget { get; set; } = default!; + private AuditLoggingErrorRateWidgetComponent ErrorRateWidget { get; set; } = default!; + + private async Task RefreshAsync() + { + await AverageExecutionDurationWidget.RefreshAsync(); + await ErrorRateWidget.RefreshAsync(); + } +} +``` + +### MVC / Razor Pages + +Use `IWidgetManager` to check the widget permission before invoking its view component: + +```cshtml +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets +@using Volo.Abp.AuditLogging.Web.Pages.Shared.Components.AverageExecutionDurationPerDayWidget +@using Volo.Abp.AuditLogging.Web.Pages.Shared.Components.ErrorRateWidget +@inject IWidgetManager WidgetManager + +@if (await WidgetManager.IsGrantedAsync(typeof(AuditLoggingErrorRateWidgetViewComponent))) +{ + @await Component.InvokeAsync(typeof(AuditLoggingErrorRateWidgetViewComponent)) +} + +@if (await WidgetManager.IsGrantedAsync(typeof(AuditLoggingAverageExecutionDurationPerDayWidgetViewComponent))) +{ + @await Component.InvokeAsync(typeof(AuditLoggingAverageExecutionDurationPerDayWidgetViewComponent)) +} +``` + ## Data seed This module doesn't seed any data. @@ -145,7 +250,7 @@ Configure(options => // The Hangfire Cron expression is different from the Quartz Cron expression, Please refer to the following links: // https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontriggers.html#cron-expressions // https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html - options.ExcelFileCleanupOptions.CronExpression = "0 23 * * *"; // Quartz Cron expression is "0 0 23 * * ?" + options.CronExpression = "0 23 * * *"; // Quartz Cron expression is "0 0 23 * * ?" }); ``` @@ -237,16 +342,30 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do * AbpAuditLogActions * AbpEntityChanges * AbpEntityPropertyChanges +* **AbpAuditLogExcelFiles** #### MongoDB ##### Collections * **AbpAuditLogs** +* **AbpAuditLogExcelFiles** ### Permissions -See the `AbpAuditLoggingPermissions` class members for all permissions defined for this module. +The module defines the following feature and permission relationships: + +* `AuditLogging.Enable` is enabled by default. The `AuditLogging.AuditLogs` permission requires this feature, and the audit log application service also checks it. +* `AuditLogging.SettingManagement` is a child feature of `AuditLogging.Enable` and is disabled by default. The `AuditLogging.AuditLogs.SettingManagement` permission requires this feature. +* `AuditLogging.AuditLogs.Export` is a child permission of `AuditLogging.AuditLogs`. Audit log and entity change export operations require this permission. + +See the `AbpAuditLoggingPermissions` and `AbpAuditLoggingFeatures` class members for the complete definitions. + +#### Entity-specific change history permissions + +You can define a permission for the change history of a specific entity by using the `AuditLogging.ViewChangeHistory:{EntityTypeFullName}` naming convention. For example, the permission name for `Acme.BookStore.Books.Book` is `AuditLogging.ViewChangeHistory:Acme.BookStore.Books.Book`. + +When a matching permission is defined and granted, the user can view that entity's change history. If the entity-specific permission is not defined or is not granted, authorization falls back to `AuditLogging.AuditLogs`. Users who have the general audit log permission can therefore still view the entity history. ### Angular UI diff --git a/docs/en/modules/audit-logging.md b/docs/en/modules/audit-logging.md index 5cba52cf34..a3f99147db 100644 --- a/docs/en/modules/audit-logging.md +++ b/docs/en/modules/audit-logging.md @@ -29,12 +29,37 @@ The source code of this module can be accessed [here](https://github.com/abpfram - `EntityChange` (collection): Changed entities of audit log. - `AuditLogAction` (collection): Executed actions of audit log. +#### Extending the Entities + +The `AuditLog`, `AuditLogAction` and `EntityChange` 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 a property to `AuditLog`: + +````csharp +ObjectExtensionManager.Instance.Modules() + .ConfigureAuditLogging(auditLogging => + { + auditLogging.ConfigureAuditLog(auditLog => + { + auditLog.AddOrUpdateProperty("ExternalId"); + }); + }); +```` + +Use `ConfigureAuditLogAction` or `ConfigureEntityChange` in the same way to extend the other supported entities. + #### Repositories Following custom repositories are defined for this module: - `IAuditLogRepository` +#### Audit Log Conversion + +The module uses `IAuditLogInfoToAuditLogConverter` to convert the `AuditLogInfo` collected by the auditing system into the persisted `AuditLog` aggregate. You can inject this service when you need the same conversion in a custom persistence flow, or replace its default implementation using the [dependency injection system](../framework/fundamentals/dependency-injection.md#replace-a-service) to customize the mapping. + +#### Persistence Limits + +Before saving an audit log, the module truncates fields that have maximum lengths defined by `AuditLogConsts`, `AuditLogActionConsts`, `EntityChangeConsts` and `EntityPropertyChangeConsts`. Action parameters are handled differently: if `AuditLogAction.Parameters` exceeds `AuditLogActionConsts.MaxParametersLength` (2,000 by default), it is persisted as an empty string instead of being truncated. + ### Database providers #### Common @@ -55,13 +80,15 @@ This module uses `AbpAuditLogging` for the connection string name. If you don't - AbpAuditLogActions - AbpEntityChanges - AbpEntityPropertyChanges +- **AbpAuditLogExcelFiles** #### MongoDB ##### Collections - **AbpAuditLogs** +- **AbpAuditLogExcelFiles** ## See Also -* [Audit logging system](../framework/infrastructure/audit-logging.md) \ No newline at end of file +* [Audit logging system](../framework/infrastructure/audit-logging.md) From 3ae40b9c376783393df9d38efee2013c515a946f Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 18 Jul 2026 14:19:20 +0800 Subject: [PATCH 03/41] Preserve RabbitMQ connection settings from configuration --- .../Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs | 17 ++- .../Abp/RabbitMQ/AbpRabbitMqOptions_Tests.cs | 129 ++++++++++++++++++ 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/AbpRabbitMqOptions_Tests.cs diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs index 74b41dea23..e1da7d4036 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs @@ -1,5 +1,7 @@ using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using RabbitMQ.Client; using Volo.Abp.Json; using Volo.Abp.Modularity; using Volo.Abp.Threading; @@ -18,9 +20,20 @@ public class AbpRabbitMqModule : AbpModule Configure(configuration.GetSection("RabbitMQ")); Configure(options => { - foreach (var connectionFactory in options.Connections.Values) + var connectionsSection = configuration.GetSection("RabbitMQ:Connections"); + foreach (var connection in options.Connections) { - connectionFactory.AutomaticRecoveryEnabled = false; + var connectionSection = connectionsSection.GetSection(connection.Key); + connectionSection.GetSection(nameof(ConnectionFactory.Ssl)).Bind(connection.Value.Ssl); + + var maxInboundMessageBodySize = connectionSection.GetValue( + nameof(ConnectionFactory.MaxInboundMessageBodySize)); + if (maxInboundMessageBodySize.HasValue) + { + connection.Value.MaxInboundMessageBodySize = maxInboundMessageBodySize.Value; + } + + connection.Value.AutomaticRecoveryEnabled = false; } }); } diff --git a/framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/AbpRabbitMqOptions_Tests.cs b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/AbpRabbitMqOptions_Tests.cs new file mode 100644 index 0000000000..7ec918b5fd --- /dev/null +++ b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/AbpRabbitMqOptions_Tests.cs @@ -0,0 +1,129 @@ +using System.IO; +using System.Net.Security; +using System.Security.Authentication; +using System.Text; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using RabbitMQ.Client; +using Shouldly; +using Xunit; + +namespace Volo.Abp.RabbitMQ; + +public class AbpRabbitMqOptions_Tests +{ + [Fact] + public void Should_Bind_Connection_Settings_From_Json() + { + var connection = GetConnection( + """ + { + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "123.123.123.123", + "Port": 5672, + "MaxInboundMessageBodySize": 500000000 + } + } + } + } + """); + + connection.HostName.ShouldBe("123.123.123.123"); + connection.Port.ShouldBe(5672); + connection.MaxInboundMessageBodySize.ShouldBe(500000000u); + } + + [Fact] + public void Should_Bind_Ssl_Settings_From_Json() + { + var connection = GetConnection( + """ + { + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "rabbit.example.test", + "Ssl": { + "AcceptablePolicyErrors": "RemoteCertificateChainErrors", + "CertPassphrase": "secret", + "CheckCertificateRevocation": true, + "Enabled": true, + "ServerName": "tls.example.test", + "Version": "Tls12" + } + } + } + } + } + """); + + connection.Ssl.AcceptablePolicyErrors.ShouldBe(SslPolicyErrors.RemoteCertificateChainErrors); + connection.Ssl.CertPassphrase.ShouldBe("secret"); + connection.Ssl.CheckCertificateRevocation.ShouldBeTrue(); + connection.Ssl.Enabled.ShouldBeTrue(); + connection.Ssl.ServerName.ShouldBe("tls.example.test"); + connection.Ssl.Version.ShouldBe(SslProtocols.Tls12); + } + + [Fact] + public void Should_Combine_Uri_With_Advanced_Json_Connection_Settings() + { + var connection = GetConnection( + """ + { + "RabbitMQ": { + "Connections": { + "Default": { + "Uri": "amqps://configured-user:configured-pass@uri.example.test:5678/configured-vhost", + "MaxInboundMessageBodySize": 500000000, + "Ssl": { + "AcceptablePolicyErrors": "RemoteCertificateChainErrors", + "CertPassphrase": "secret", + "CheckCertificateRevocation": true, + "Enabled": true, + "ServerName": "tls.example.test", + "Version": "Tls12" + } + } + } + } + } + """); + + connection.HostName.ShouldBe("uri.example.test"); + connection.Port.ShouldBe(5678); + connection.UserName.ShouldBe("configured-user"); + connection.Password.ShouldBe("configured-pass"); + connection.VirtualHost.ShouldBe("configured-vhost"); + connection.MaxInboundMessageBodySize.ShouldBe(500000000u); + connection.Ssl.AcceptablePolicyErrors.ShouldBe(SslPolicyErrors.RemoteCertificateChainErrors); + connection.Ssl.CertPassphrase.ShouldBe("secret"); + connection.Ssl.CheckCertificateRevocation.ShouldBeTrue(); + connection.Ssl.Enabled.ShouldBeTrue(); + connection.Ssl.ServerName.ShouldBe("tls.example.test"); + connection.Ssl.Version.ShouldBe(SslProtocols.Tls12); + } + + private static ConnectionFactory GetConnection( + string json, + string connectionName = RabbitMqConnections.DefaultConnectionName) + { + using var application = AbpApplicationFactory.Create(options => + { + options.Services.ReplaceConfiguration( + new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) + .Build()); + }); + + application.Initialize(); + + return application.ServiceProvider + .GetRequiredService>() + .Value + .Connections[connectionName]; + } +} From 724f0865a361e0a4dfacff9d0afc060d48def0f6 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:09:51 +0800 Subject: [PATCH 04/41] Correct Background Jobs module documentation --- docs/en/modules/background-jobs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/modules/background-jobs.md b/docs/en/modules/background-jobs.md index 2d57d0c907..63ba851e2b 100644 --- a/docs/en/modules/background-jobs.md +++ b/docs/en/modules/background-jobs.md @@ -41,7 +41,7 @@ Following custom repositories are defined for this module: ##### Table / collection prefix & schema -All tables/collections use the `Abp` prefix by default. Set static properties on the `BackgroundJobsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Abp` prefix by default. Set static properties on the `AbpBackgroundJobsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string @@ -61,4 +61,4 @@ This module uses `AbpBackgroundJobs` for the connection string name. If you don' ## See Also -* [Background job system](../framework/infrastructure/background-jobs) \ No newline at end of file +* [Background job system](../framework/infrastructure/background-jobs) From 0c4985accf8e5fa1fc87ecccd5f30833581daa3d Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:22 +0800 Subject: [PATCH 05/41] Document database BLOB storage provider --- .../infrastructure/blob-storing/database.md | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/docs/en/framework/infrastructure/blob-storing/database.md b/docs/en/framework/infrastructure/blob-storing/database.md index 1b568c247c..ff226ae3c6 100644 --- a/docs/en/framework/infrastructure/blob-storing/database.md +++ b/docs/en/framework/infrastructure/blob-storing/database.md @@ -9,6 +9,8 @@ BLOB Storing Database Storage Provider can store BLOBs in a relational or non-relational database. +The database provider reads the complete input stream into memory before saving a BLOB and returns BLOB content from an in-memory buffer. Database and driver value-size limits still apply. Consider an external object-storage provider for very large BLOBs or workloads that require end-to-end streaming. + There are two database providers implemented; * [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) package implements for [EF Core](../../data/entity-framework-core), so it can store BLOBs in [any DBMS supported](https://docs.microsoft.com/en-us/ef/core/providers/) by the EF Core. @@ -32,16 +34,16 @@ This command adds all the NuGet packages to corresponding layers of your solutio ### Manual Installation -Here, all the NuGet packages defined by this provider; +The following NuGet packages are defined by this provider: * [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain.Shared) * [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain) * [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) * [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) -You can only install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference) since they depends on the other packages. +You only need to install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference), since they depend on the other packages. -After installation, add `DepenedsOn` attribute to your related [module](../../architecture/modularity/basics.md). Here, the list of module classes defined by the related NuGet packages listed above: +After installation, add the `[DependsOn]` attribute to your related [module](../../architecture/modularity/basics.md). Here is the list of module classes defined by the related NuGet packages listed above: * `BlobStoringDatabaseDomainModule` * `BlobStoringDatabaseDomainSharedModule` @@ -52,6 +54,17 @@ Whenever you add a NuGet package to a project, also add the module class depende If you are using EF Core, you also need to configure your **Migration DbContext** to add BLOB storage tables to your database schema. Call `builder.ConfigureBlobStoring()` extension method inside the `OnModelCreating` method to include mappings to your DbContext. Then you can use the standard `Add-Migration` and `Update-Database` [commands](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create necessary tables in your database. +If you are using MongoDB and combine module collections in a custom `AbpMongoDbContext`, call `modelBuilder.ConfigureBlobStoring()` inside the `CreateModel` method: + +````csharp +protected override void CreateModel(IMongoModelBuilder modelBuilder) +{ + base.CreateModel(modelBuilder); + + modelBuilder.ConfigureBlobStoring(); +} +```` + ## Configuration ### Connection String @@ -60,9 +73,17 @@ If you will use your `Default` connection string, you don't need to any addition If you want to use a separate database for BLOB storage, use the `AbpBlobStoring` as the [connection string](../../fundamentals/connection-strings.md) name in your configuration file (`appsettings.json`). In this case, also read the [EF Core Migrations](../../data/entity-framework-core/migrations.md) document to learn how to create and use a different database for a desired module. +### Common Database Properties + +The `AbpBlobStoringDatabaseDbProperties` class defines the following database settings. Set `DbTablePrefix` and `DbSchema` at application startup, before the database model is created: + +* `DbTablePrefix` (`Abp` by default) is the prefix for table and collection names. +* `DbSchema` (`null` by default) is the database schema used by EF Core. MongoDB does not use this property. +* `ConnectionStringName` (`AbpBlobStoring`) is the connection-string name used by both database providers. + ### Configuring the Containers -If you are using only the database storage provider, you don't need to manually configure it, since it is automatically done. If you are using multiple storage providers, you may want to configure it. +The database module selects `DatabaseBlobProvider` for the default container when no provider has already been selected. It does not replace an explicitly configured provider. If you use multiple storage providers, configure the database provider for the required default, typed or named containers. Configuration is done in the `ConfigureServices` method of your [module](../../architecture/modularity/basics.md) class, as explained in the [BLOB Storing document](../blob-storing). @@ -84,12 +105,18 @@ Configure(options => It is expected to use the [BLOB Storing services](../blob-storing) to use the BLOB storing system. However, if you want to work on the database tables/entities, you can use the following information. +### Database Tables and Collections + +With the default `Abp` prefix, EF Core maps the entities to the `AbpBlobContainers` and `AbpBlobs` tables. MongoDB uses collections with the same names. Changing `DbTablePrefix` changes both table and collection names, while `DbSchema` only changes the EF Core schema. + ### Entities Entities defined for this module: -* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database. -* `DatabaseBlob` (aggregate root) represents a BLOB in the database. +* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database. It stores the tenant identifier and the container name. Persisted container names have a maximum length of 128 characters. +* `DatabaseBlob` (aggregate root) represents a BLOB in the database. It stores the container identifier, tenant identifier, BLOB name and content. Persisted BLOB names have a maximum length of 256 characters. + +The provider creates a container record lazily when the first BLOB is saved to that container. Read, existence-check and delete operations do not create container records, and deleting the last BLOB does not delete its container record. See the [entities document](../../architecture/domain-driven-design/entities.md) to learn what is an entity and aggregate root. @@ -102,4 +129,4 @@ You can also use `IRepository` and `IRepository Date: Sun, 19 Jul 2026 12:10:22 +0800 Subject: [PATCH 06/41] Expand Feature Management documentation --- docs/en/modules/feature-management.md | 165 +++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 4 deletions(-) diff --git a/docs/en/modules/feature-management.md b/docs/en/modules/feature-management.md index e80098a04d..e268c15052 100644 --- a/docs/en/modules/feature-management.md +++ b/docs/en/modules/feature-management.md @@ -7,7 +7,7 @@ # Feature Management Module -The Feature Management module implements the `IFeatureManagementStore` interface defined by the [Feature System](../framework/infrastructure/features.md). +The Feature Management module persists feature values and implements the `IFeatureStore` interface defined by the [Feature System](../framework/infrastructure/features.md). It also provides management services and reusable user interfaces for reading, changing and resetting values for a feature provider. > This document covers only the feature management module which persists feature values to a database. See [the features](../framework/infrastructure/features.md) document for more about the feature system. @@ -33,9 +33,97 @@ When you click *Actions* -> *Features* for a tenant, the feature management dial In this dialog, you can enable, disable or set values for the features for a tenant. +### Host Feature Management + +The MVC, Blazor and MudBlazor packages add a **Feature Management** group to the Setting Management page. The group is available on the host side to users granted the `FeatureManagement.ManageHostFeatures` permission. It opens the same reusable dialog with the tenant provider (`T`) and an empty provider key, which represents host feature values. + +For Angular applications, register the setting-tab contributor in the application configuration: + +````ts +import { ApplicationConfig } from '@angular/core'; +import { provideFeatureManagementConfig } from '@abp/ng.feature-management'; + +export const appConfig: ApplicationConfig = { + providers: [provideFeatureManagementConfig()], +}; +```` + +`provideFeatureManagementConfig` adds the host Feature Management tab to Setting Management and protects it with the same `FeatureManagement.ManageHostFeatures` policy. The current application templates already register this provider. + +### Reusing the Feature Management Dialog + +All UI implementations accept a provider name and an optional provider key. The built-in provider names are `D` for default values, `C` for configuration values, `E` for editions and `T` for tenants. Default and configuration values are read-only, while edition, tenant and custom providers can persist values. + +#### MVC + +Create an `abp.ModalManager` for the module page and pass the provider information when opening it: + +````js +const featureManagementModal = new abp.ModalManager( + abp.appPath + 'FeatureManagement/FeatureManagementModal' +); + +featureManagementModal.open({ + providerName: 'T', + providerKey: tenantId, + providerKeyDisplayName: tenantName, +}); +```` + +#### Blazor and MudBlazor + +Render the `FeatureManagementModal` component and call its `OpenAsync` method: + +````razor +@using Volo.Abp.FeatureManagement.Blazor.Components + + + +@code { + private FeatureManagementModal FeatureManagementModal = default!; + + private Task OpenFeaturesAsync(Guid tenantId, string tenantName) + { + return FeatureManagementModal.OpenAsync("T", tenantId.ToString(), tenantName); + } +} +```` + +For a MudBlazor application, use the `Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components` namespace. The component has the same `OpenAsync(providerName, providerKey, providerKeyDisplayName)` contract. + +#### Angular + +`FeatureManagementComponent` is a standalone component exported from `@abp/ng.feature-management`: + +````ts +import { Component, signal } from '@angular/core'; +import { FeatureManagementComponent } from '@abp/ng.feature-management'; + +@Component({ + selector: 'app-tenant-features', + imports: [FeatureManagementComponent], + templateUrl: './tenant-features.component.html', +}) +export class TenantFeaturesComponent { + readonly visible = signal(false); + tenantId = ''; +} +```` + +````html + +```` + +Use `eFeatureManagementComponents.FeatureManagement` as the component key when replacing the dialog through the Angular component replacement system. + ## IFeatureManager -`IFeatureManager` is the main service provided by this module. It is used to read and change the setting values for the tenants in a multi-tenant application. `IFeatureManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a feature value. +`IFeatureManager` is the main service provided by this module. It reads and changes feature values for registered feature management providers. `IFeatureManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a feature value directly. > If you just want to read feature values, use the `IFeatureChecker` as explained in the [Features document](../framework/infrastructure/features.md). @@ -70,6 +158,8 @@ namespace Demo } ```` +`SetAsync` and the provider-specific extension methods validate the value against the feature definition's value validator. By default, setting a value equal to the fallback value clears the explicit provider value; pass `forceToSet: true` when an explicit value must be kept even if it currently matches the fallback. Use `DeleteAsync(providerName, providerKey)` to reset all values for a provider object to their fallbacks. + ## Feature Management Providers Features Management Module is extensible, just like the [features system](../framework/infrastructure/features.md). You can extend it by defining feature management providers. There are 4 pre-built feature management providers registered in the following order: @@ -86,7 +176,9 @@ If you want to create your own provider, implement the `IFeatureManagementProvid ````csharp public class CustomFeatureProvider : FeatureManagementProvider { - public override string Name => "Custom"; + public const string ProviderName = "Custom"; + + public override string Name => ProviderName; public CustomFeatureProvider(IFeatureManagementStore store) : base(store) @@ -103,12 +195,77 @@ Once you create your provider class, you should register it using the `FeatureMa Configure(options => { options.Providers.Add(); + options.ProviderPolicies[CustomFeatureProvider.ProviderName] = + "MyApp.Features.Manage"; }); ```` The order of the providers are important. Providers are executed in the reverse order. That means the `CustomFeatureProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. +The `ProviderPolicies` entry is required when the custom provider is managed through `IFeatureAppService` or one of the reusable dialogs. Map the provider name to an authorization policy that grants access to the corresponding provider object. The application service rejects get, update and reset operations when no policy is mapped. + +The management application service exposes get, update and reset operations through `IFeatureAppService`. The HTTP API maps the same operations to `GET`, `PUT` and `DELETE` requests at `/api/feature-management/features`, using `providerName` and `providerKey` to identify the managed object. + +## Custom Value Validators + +Feature definitions can use custom `IValueValidator` implementations. When those definitions are persisted or returned by the management API, the module must be able to reconstruct the validator from its serialized name. Define a parameterless validator and register a matching factory during pre-configuration: + +````csharp +[Serializable] +[ValueValidator("URL")] +public class UrlValueValidator : ValueValidatorBase +{ + public override bool IsValid(object? value) + { + return Uri.TryCreate(value?.ToString(), UriKind.Absolute, out _); + } +} +```` + +````csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + context.Services.PreConfigure(options => + { + options.ValueValidatorFactory.Add( + new ValueValidatorFactory("URL") + ); + }); +} +```` + +The factory name must match the name supplied by `ValueValidatorAttribute`. The module registers factories for the built-in `NULL`, `BOOLEAN`, `NUMERIC` and `STRING` validators. + +## Database Providers + +The Entity Framework Core and MongoDB packages persist the same three record types: feature groups, feature definitions and feature values. + +### Common + +#### Table / Collection Prefix and Schema + +All tables and collections use the `Abp` prefix by default. Set the static `AbpFeatureManagementDbProperties.DbTablePrefix` property to change the prefix. `AbpFeatureManagementDbProperties.DbSchema` changes the schema for database providers that support schemas. + +#### Connection String + +The module uses `AbpFeatureManagement` as the connection string name. If this connection string is not configured, it falls back to the `Default` connection string. See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details. + +### Entity Framework Core + +The Entity Framework Core provider maps the following tables: + +* **AbpFeatureGroups** +* **AbpFeatures** +* **AbpFeatureValues** + +### MongoDB + +The MongoDB provider maps the following collections: + +* **AbpFeatureGroups** +* **AbpFeatures** +* **AbpFeatureValues** + ## See Also * [Features](../framework/infrastructure/features.md) - From 2fd842441f48e8ea2679a607355494fe22347749 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:22 +0800 Subject: [PATCH 07/41] Expand Permission Management documentation --- docs/en/modules/permission-management.md | 276 +++++++++++++++++++---- 1 file changed, 238 insertions(+), 38 deletions(-) diff --git a/docs/en/modules/permission-management.md b/docs/en/modules/permission-management.md index 5a80648ecb..342f355617 100644 --- a/docs/en/modules/permission-management.md +++ b/docs/en/modules/permission-management.md @@ -33,6 +33,87 @@ When you click *Actions* -> *Permissions* for a role, the permission management In this dialog, you can grant permissions for the selected role. The tabs in the left side represents main permission groups and the right side contains the permissions defined in the selected group. +#### Reusing the Permission Management Dialog + +The standard permission management dialog is reusable for any registered permission management provider. The provider name and key identify the object whose permissions are being managed. + +##### MVC / Razor Pages + +Use `abp.ModalManager` to open the built-in modal page: + +````javascript +var permissionModal = new abp.ModalManager( + abp.appPath + 'AbpPermissionManagement/PermissionManagementModal' +); + +permissionModal.open({ + providerName: 'R', + providerKey: roleName, + providerKeyDisplayName: roleName +}); +```` + +##### Blazor + +Add the `PermissionManagementModal` component to the page and call its `OpenAsync` method: + +````razor +@using Volo.Abp.PermissionManagement.Blazor.Components + + + +@code { + private PermissionManagementModal PermissionModal { get; set; } + + private Task OpenPermissionsAsync(string roleName) + { + return PermissionModal.OpenAsync("R", roleName, roleName); + } +} +```` + +The MudBlazor package provides the same component API in the `Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components` namespace. + +##### Angular + +Import the standalone `PermissionManagementComponent` into your component: + +````typescript +import { Component } from '@angular/core'; +import { PermissionManagementComponent } from '@abp/ng.permission-management'; + +@Component({ + selector: 'app-role-actions', + templateUrl: './role-actions.component.html', + imports: [PermissionManagementComponent], +}) +export class RoleActionsComponent { + roleName = 'admin'; + permissionsVisible = false; + + openPermissions() { + this.permissionsVisible = true; + } +} +```` + +Then add the component to the template. It owns the modal, so you only need to control its `visible` value: + +````html + + + +```` + +The reusable dialog calls `IPermissionAppService`. The provider must be registered and mapped to an authorization policy as described in the [Permission Management Providers](#permission-management-providers) section. For non-admin users, only permissions that the current user already has are editable, and update requests are filtered by the same rule. Users with the built-in admin role are not subject to this editability filter. + +Use `entityDisplayName` to customize the modal title and `hideBadges` to hide the granted-provider badges. The component emits `visibleChange`, so the two-way `[(visible)]` binding keeps the caller's visibility state synchronized. + ### Resource Permission Management Dialog In addition to standard permissions, this module provides a reusable dialog for managing **resource-based permissions** on specific resource instances. This allows administrators to grant or revoke permissions for users, roles and clients on individual resources (e.g., a specific document, project, or any entity). @@ -94,32 +175,45 @@ Use the `ResourcePermissionManagementModal` component's `OpenAsync` method to op } ```` +The MudBlazor package provides the same component API in the `Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components` namespace. + #### Angular -Use the `ResourcePermissionManagementComponent`: +Import the standalone `ResourcePermissionManagementComponent` into your component: ````typescript -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Component } from '@angular/core'; import { ResourcePermissionManagementComponent } from '@abp/ng.permission-management'; @Component({ - // ... + selector: 'app-document-actions', + templateUrl: './document-actions.component.html', + imports: [ResourcePermissionManagementComponent], }) -export class DocumentListComponent { - constructor(private modalService: NgbModal) {} - - openPermissionsModal(document: DocumentDto) { - const modalRef = this.modalService.open( - ResourcePermissionManagementComponent, - { size: 'lg' } - ); - modalRef.componentInstance.resourceName = 'MyApp.Document'; - modalRef.componentInstance.resourceKey = document.id; - modalRef.componentInstance.resourceDisplayName = document.title; +export class DocumentActionsComponent { + documentId = '42'; + documentTitle = 'Permission Management Guide'; + resourcePermissionsVisible = false; + + openPermissions() { + this.resourcePermissionsVisible = true; } } ```` +Render the component directly. It owns the modal and requires `resourceName` and `resourceKey` inputs: + +````html + + + +```` + ## IPermissionManager `IPermissionManager` is the main service provided by this module. It is used to read and change the global permission values. `IPermissionManager` is typically used by the *Permission Management Dialog*. However, you can inject it if you need to set a permission value. @@ -146,7 +240,7 @@ public class MyService : ITransientDependency } public async Task GrantUserPermissionDemoAsync( - Guid userId, string roleName, string permission) + Guid userId, string permission) { await _permissionManager .SetForUserAsync(userId, permission, true); @@ -154,6 +248,8 @@ public class MyService : ITransientDependency } ```` +The OpenIddict integration also provides `SetForClientAsync` for client permissions. + ## IResourcePermissionManager `IResourcePermissionManager` is the service for programmatically managing resource-based permissions. It is typically used by the *Resource Permission Management Dialog*. However, you can inject it when you need to grant, revoke, or query permissions for specific resource instances. @@ -243,32 +339,85 @@ public class MyService : ITransientDependency ## Cleaning Up Resource Permissions -When a resource is deleted, you should clean up its associated permissions to avoid orphaned permission records in the database. You can do this directly in your delete logic or handle it asynchronously through event handlers: +When a resource is deleted, you should clean up its associated permissions to avoid orphaned permission records in the database. Query all grants for the resource and delete the returned entities: + +````csharp +public class DocumentService : ITransientDependency, IUnitOfWorkEnabled +{ + private readonly IDocumentRepository _documentRepository; + private readonly IResourcePermissionGrantRepository _resourcePermissionGrantRepository; + + public DocumentService( + IDocumentRepository documentRepository, + IResourcePermissionGrantRepository resourcePermissionGrantRepository) + { + _documentRepository = documentRepository; + _resourcePermissionGrantRepository = resourcePermissionGrantRepository; + } + + public virtual async Task DeleteDocumentAsync(Guid id) + { + await _documentRepository.DeleteAsync(id); + + var grants = await _resourcePermissionGrantRepository.GetPermissionsAsync( + "MyApp.Document", + id.ToString() + ); + + await _resourcePermissionGrantRepository.DeleteManyAsync( + grants, + autoSave: true + ); + } +} +```` + +`IUnitOfWorkEnabled` keeps the resource deletion and grant cleanup in the same unit of work. `ResourcePermissionGrant` is a multi-tenant entity, so repository queries are scoped by the current tenant data filter. If cleanup runs from the host or a background process, switch `ICurrentTenant` to the resource owner's tenant before calling `GetPermissionsAsync` and `DeleteManyAsync`; otherwise the query will not return that tenant's grants. + +The `providerName` and `providerKey` arguments of `IResourcePermissionManager.DeleteAsync` identify one exact provider and key; a `null` provider key is not a wildcard. The Identity integration cleans up user and role grants, and the OpenIddict integration cleans up client grants when those entities are deleted. For your custom entities, you are responsible for removing all resource grants when the resource is deleted. + +## Application Service and HTTP API + +`IPermissionAppService` exposes the management operations under the `api/permission-management/permissions` route. Standard permission `GET` and `PUT` operations require the authorization policy mapped to the requested provider in `PermissionManagementOptions.ProviderPolicies`. + +Standard and resource update operations handle omitted permissions differently: + +* The standard permission `PUT` operation changes only the permission entries included in `UpdatePermissionsDto`. For non-admin users, entries the current user does not have are ignored. +* The resource permission `PUT` operation treats `UpdateResourcePermissionsDto.Permissions` as the complete desired set for the selected resource, provider, and provider key. Manageable permissions omitted from the list are revoked. Each resource permission is filtered by its `ManagementPermissionName` before it can be returned or changed. + +## Seeding Permission Grants + +Use `IPermissionDataSeeder` in a data seed contributor to add initial permission grants for a provider and key: ````csharp -public async Task DeleteDocumentAsync(Guid id) +public class MyPermissionDataSeedContributor + : IDataSeedContributor, ITransientDependency { - // Delete the document - await _documentRepository.DeleteAsync(id); - - // Clean up all permissions for this resource - await _resourcePermissionManager.DeleteAsync( - resourceName: "MyApp.Document", - resourceKey: id.ToString(), - providerName: "U", - providerKey: null // Deletes for all users - ); - - await _resourcePermissionManager.DeleteAsync( - resourceName: "MyApp.Document", - resourceKey: id.ToString(), - providerName: "R", - providerKey: null // Deletes for all roles - ); + private readonly IPermissionDataSeeder _permissionDataSeeder; + + public MyPermissionDataSeedContributor( + IPermissionDataSeeder permissionDataSeeder) + { + _permissionDataSeeder = permissionDataSeeder; + } + + public Task SeedAsync(DataSeedContext context) + { + return _permissionDataSeeder.SeedAsync( + RolePermissionValueProvider.ProviderName, + "admin", + new[] + { + "MyApp.Books", + "MyApp.Books.Create" + }, + context.TenantId + ); + } } ```` -> ABP modules automatically handle permission cleanup for their own entities. For your custom entities, you are responsible for cleaning up resource permissions when resources are deleted. +The seeder is additive and tenant-aware. It inserts grants that do not exist for the selected provider, key, and tenant; it does not revoke existing grants that are absent from the input. ## Permission Management Providers @@ -279,6 +428,8 @@ Permission Management Module is extensible, just like the [permission system](.. * `UserPermissionManagementProvider`: Manages user-based permissions. * `RolePermissionManagementProvider`: Manages role-based permissions. +The OpenIddict integration also registers a provider for client permissions. + `IPermissionManager` uses these providers when you get/set permissions. You can define your own provider by implementing the `IPermissionManagementProvider` or inheriting from the `PermissionManagementProvider` base class. **Example:** @@ -286,7 +437,9 @@ Permission Management Module is extensible, just like the [permission system](.. ````csharp public class CustomPermissionManagementProvider : PermissionManagementProvider { - public override string Name => "Custom"; + public const string ProviderName = "Custom"; + + public override string Name => ProviderName; public CustomPermissionManagementProvider( IPermissionGrantRepository permissionGrantRepository, @@ -309,10 +462,12 @@ Once you create your provider class, you should register it using the `Permissio Configure(options => { options.ManagementProviders.Add(); + options.ProviderPolicies[CustomPermissionManagementProvider.ProviderName] = + "MyApp.ManageCustomPermissions"; }); ```` -The order of the providers are important. Providers are executed in the reverse order. That means the `CustomPermissionManagementProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. +`IPermissionManager` enumerates `ManagementProviders` in registration order when it reads permission values. A write operation selects the registered provider whose `Name` matches the requested provider name. Every provider name must therefore be unique. ### Resource Permission Management Providers @@ -399,15 +554,20 @@ Permission value providers are used to determine if a permission is granted. The ### Resource Permission Value Providers -Similar to the standard permission system, you can create custom value providers for resource permissions. ABP comes with two built-in resource permission value providers: +Similar to the standard permission system, you can create custom value providers for resource permissions. ABP comes with three built-in resource permission value providers: * `UserResourcePermissionValueProvider` (`U`): Checks permissions granted directly to users * `RoleResourcePermissionValueProvider` (`R`): Checks permissions granted to roles +* `ClientResourcePermissionValueProvider` (`C`): Checks permissions granted directly to clients You can create your own custom value provider by implementing the `IResourcePermissionValueProvider` interface or inheriting from the `ResourcePermissionValueProvider` base class: ````csharp +using System; +using System.Linq; +using System.Security.Principal; using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions.Resources; public class OwnerResourcePermissionValueProvider : ResourcePermissionValueProvider @@ -441,6 +601,33 @@ public class OwnerResourcePermissionValueProvider : ResourcePermissionValueProvi : PermissionGrantResult.Undefined; } + public override async Task CheckAsync( + ResourcePermissionValuesCheckContext context) + { + var permissionNames = context.Permissions + .Select(permission => permission.Name) + .Distinct() + .ToArray(); + var result = new MultiplePermissionGrantResult(permissionNames); + + var currentUserId = context.Principal?.FindUserId(); + if (currentUserId == null || + !await CheckIfUserIsOwnerAsync( + currentUserId.Value, + context.ResourceName, + context.ResourceKey)) + { + return result; + } + + foreach (var permissionName in permissionNames) + { + result.Result[permissionName] = PermissionGrantResult.Granted; + } + + return result; + } + private Task CheckIfUserIsOwnerAsync( Guid userId, string resourceName, @@ -461,6 +648,19 @@ Configure(options => }); ```` +## Database Providers + +The module uses the `AbpPermissionManagement` connection string name. `AbpPermissionManagementDbProperties.DbTablePrefix` and `DbSchema` default to the common ABP database prefix and schema, and can be changed before configuring the database model. + +With the default `Abp` prefix, the EF Core provider maps the following tables and the MongoDB provider maps collections with the same names: + +* `AbpPermissionGrants` +* `AbpResourcePermissionGrants` +* `AbpPermissionGroups` +* `AbpPermissions` + +In EF Core, the permission group and permission definition tables are mapped only when the model is configured as a host database. + ## See Also * [Authorization](../framework/fundamentals/authorization/index.md) From 925512edc9d2dbd8780155ab575131d5bf4e84c8 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:22 +0800 Subject: [PATCH 08/41] Expand Setting Management documentation --- docs/en/modules/setting-management.md | 154 +++++++++++++++++++------- 1 file changed, 114 insertions(+), 40 deletions(-) diff --git a/docs/en/modules/setting-management.md b/docs/en/modules/setting-management.md index dcb021fe14..6943316296 100644 --- a/docs/en/modules/setting-management.md +++ b/docs/en/modules/setting-management.md @@ -74,6 +74,10 @@ namespace Demo So, you can get or set a setting value for different setting value providers (Default, Global, User, Tenant... etc). +The scoped `GetOrNull...` and `GetAll...` extension methods use fallback values by default. Pass `fallback: false` when you need only the value explicitly stored for the requested provider. Setting a value to `null` clears that provider's value. + +For a non-encrypted inherited setting, setting a provider value to the same value as its fallback also clears the provider record by default. The comparison is case-insensitive. Pass `forceToSet: true` to a user- or tenant-scoped `Set...` method when you intentionally need to persist the same value as the fallback. `SetForTenantOrGlobalAsync` honors this parameter only when `tenantId` has a value; its global branch calls `SetGlobalAsync`, which does not expose the parameter. For encrypted settings, fallback-equal clearing does not apply to non-empty values because they are encrypted before comparison; an empty string can still be cleared when it equals the fallback. Pass `null` explicitly when you want to clear the provider value. + > Use the `ISettingProvider` instead of the `ISettingManager` if you only need to read the setting values, because it implements caching and supports all deployment scenarios. You can use the `ISettingManager` if you are creating a setting management UI. ### Setting Cache @@ -88,9 +92,9 @@ Setting Management module is extensible, just like the [setting system](../frame * `ConfigurationSettingManagementProvider`: Gets the value from the [IConfiguration service](../framework/fundamentals/configuration.md). It can not set the configuration value because it is not possible to change the configuration values on runtime. * `GlobalSettingManagementProvider`: Gets or sets the global (system-wide) value for a setting. * `TenantSettingManagementProvider`: Gets or sets the setting value for a tenant. -* `UserSettingManagementProvider`: Gets the setting value for a user. +* `UserSettingManagementProvider`: Gets or sets the setting value for a user. -`ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManagement` service (like `SetForUserAsync` defined by the user setting management provider). +`ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManager` service (like `SetForUserAsync` defined by the user setting management provider). If you want to create your own provider, implement the `ISettingManagementProvider` interface or inherit from the `SettingManagementProvider` base class: @@ -119,23 +123,43 @@ Configure(options => The order of the providers are important. Providers are executed in the reverse order. That means the `CustomSettingProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. -## See Also +## Dynamic Setting Definitions -* [Settings](../framework/infrastructure/settings.md) +The module can persist setting definitions in addition to setting values. `SettingManagementOptions` controls this behavior: + +* `SaveStaticSettingsToDatabase` is `true` by default. During application initialization, definitions contributed by code are synchronized with the `SettingDefinitions` table or collection (`AbpSettingDefinitions` by default). +* `IsDynamicSettingStoreEnabled` is `false` by default. Enable it to include persisted definitions in the setting definition store at runtime. + +```csharp +Configure(options => +{ + options.IsDynamicSettingStoreEnabled = true; +}); +``` + +Both options are disabled automatically in an ABP data migration environment. Static definitions continue to come from the application's setting definition providers when the dynamic store is disabled. ## Setting Management UI -Setting Mangement module provided the Email setting, Feature management and Timezone setting UI by default. +The MVC, Blazor and MudBlazor packages provide built-in Email and Time Zone setting groups. The Angular configuration package provides the built-in Email group. Other modules can contribute their own groups to the same page. For example, the Feature Management module contributes the Feature Management group shown below. ![EmailSettingUi](../images/setting-management-email-ui.png) > You can click the Send test email button to send a test email to check your email settings. +The Email group uses `IEmailSettingsAppService`. Reading and updating the settings requires the `SettingManagement.Emailing` permission. Sending a test email additionally requires `SettingManagement.Emailing.Test`. The operations are exposed under `/api/setting-management/emailing`, with the test operation at `/api/setting-management/emailing/send-test-email`. + +Email operations also require the `SettingManagement.Enable` feature, which is `true` by default. For tenants, they additionally require its child feature, `SettingManagement.AllowChangingEmailSettings`, which is `false` by default. The read operation does not return the stored SMTP password. On update, a blank password leaves the existing password unchanged. + ![FeatureManagementUi](../images/setting-management-feature-management-ui.png) ![TimeZoneSettingUi](../images/setting-management-time-zone-ui.png) -Setting it is extensible; You can add your tabs to this page for your application settings. +The Time Zone group uses `ITimeZoneSettingsAppService` and requires the `SettingManagement.TimeZone` permission. Its HTTP API is exposed at `/api/setting-management/timezone`; `GET /api/setting-management/timezone/timezones` returns the available IANA time zones. Updating the value to `Unspecified` clears the host-global or current-tenant value so the configured fallback is used. + +The built-in MVC, Blazor and MudBlazor contributors add this group only when `IClock.SupportsMultipleTimezone` is `true`. + +The page is extensible, so you can add groups for your application's settings. ### MVC UI @@ -180,9 +204,14 @@ Create a `BookStoreSettingPageContributor.cs` file under the `Settings` folder: The content of the file is shown below: ```csharp -public class BookStoreSettingPageContributor : ISettingPageContributor +public class BookStoreSettingPageContributor : SettingPageContributorBase { - public Task ConfigureAsync(SettingPageCreationContext context) + public BookStoreSettingPageContributor() + { + RequiredPermissions("BookStore.Settings"); + } + + public override Task ConfigureAsync(SettingPageCreationContext context) { context.Groups.Add( new SettingPageGroup( @@ -195,15 +224,13 @@ public class BookStoreSettingPageContributor : ISettingPageContributor return Task.CompletedTask; } - - public Task CheckPermissionsAsync(SettingPageCreationContext context) - { - // You can check the permissions here - return Task.FromResult(true); - } } ``` +Derive from `SettingPageContributorBase` instead of directly implementing `ISettingPageContributor`. Use `RequiredPermissions`, `RequiredFeatures` and `RequiredTenantSideFeatures` in the constructor to declare the conditions for the group. The module batches these checks before calling `ConfigureAsync`. + +`SettingPageGroup` also accepts an optional `parameter` for the view component. Groups are ordered by `order`, then by display name. + Open the `BookStoreWebModule.cs` file and add the following code: ```csharp @@ -246,8 +273,13 @@ The content of the file is shown below: ```csharp public class BookStoreSettingComponentContributor : ISettingComponentContributor { - public Task ConfigureAsync(SettingComponentCreationContext context) + public async Task ConfigureAsync(SettingComponentCreationContext context) { + if (!await CheckPermissionsAsync(context)) + { + return; + } + context.Groups.Add( new SettingComponentGroup( "Volo.Abp.MySettingGroup", @@ -256,18 +288,22 @@ public class BookStoreSettingComponentContributor : ISettingComponentContributor order : 1 ) ); - - return Task.CompletedTask; } - public Task CheckPermissionsAsync(SettingComponentCreationContext context) + public async Task CheckPermissionsAsync(SettingComponentCreationContext context) { - // You can check the permissions here - return Task.FromResult(true); + var authorizationService = context.ServiceProvider + .GetRequiredService(); + + return await authorizationService.IsGrantedAsync("BookStore.Settings"); } } ``` +The settings page calls `ConfigureAsync` for every registered contributor, while menu visibility is checked separately with `CheckPermissionsAsync`. Perform the authorization check before adding the group, as in the example, and return the same result from `CheckPermissionsAsync`. + +MudBlazor provides the equivalent `ISettingComponentContributor`, `SettingComponentCreationContext`, `SettingComponentGroup` and `SettingManagementComponentOptions` types in the `Volo.Abp.SettingManagement.Blazor.MudBlazor` namespace. Use those types for a MudBlazor UI. In both Blazor UI stacks, a group accepts an optional component `parameter` and is ordered by `order`, then by display name. + Open the `BookStoreBlazorModule.cs` file and add the following code: ```csharp @@ -293,34 +329,72 @@ Create a component with the following command: yarn ng generate component my-settings ``` -Open the `app.component.ts` and modify the file as shown below: +Register the module configuration and your setting tab in `app.config.ts`: -```js -import { Component, inject } from '@angular/core'; -import { SettingTabsService } from '@abp/ng.setting-management/config'; +```ts +import { ApplicationConfig, inject, provideAppInitializer } from '@angular/core'; +import { + provideSettingManagementConfig, + SettingTabsService, +} from '@abp/ng.setting-management/config'; import { MySettingsComponent } from './my-settings/my-settings.component'; -@Component({ - // component metadata -}) -export class AppComponent { - private readonly settingTabs = inject(SettingTabsService); - - constructor() { - this.settingTabs.add([ - { - name: 'MySettings', - order: 1, - requiredPolicy: 'policy key here', - component: MySettingsComponent, - }, - ]); - } +function configureSettingTabs() { + const settingTabs = inject(SettingTabsService); + settingTabs.add([ + { + name: 'MySettings', + order: 1, + requiredPolicy: 'BookStore.Settings', + component: MySettingsComponent, + }, + ]); } + +export const appConfig: ApplicationConfig = { + providers: [ + provideSettingManagementConfig(), + provideAppInitializer(configureSettingTabs), + ], +}; ``` +`provideSettingManagementConfig` registers the route, the built-in Email tab and route visibility. The Administration -> Settings route is visible only when it has at least one visible tab and the `SettingManagement.Enable` feature is enabled. A tab can use `requiredPolicy` and `invisible` to control its own visibility. + +The `@abp/ng.setting-management` package exports the `SettingManagementComponent`, `createRoutes` and the `eSettingManagementComponents.SettingManagement` key. Use the key with the [component replacement system](../framework/ui/angular/component-replacement.md) when you need to replace the complete Setting Management page. `SettingManagementModule.forLazy` and `SettingManagementConfigModule.forRoot` are obsolete; use `createRoutes` and `provideSettingManagementConfig` in standalone applications. + #### Run the Application Navigate to `/setting-management` route to see the changes: ![Custom Settings Tab](../images/custom-settings.png) + +## Database Providers + +The Entity Framework Core and MongoDB packages persist setting values and setting definition records. + +### Common + +`AbpSettingManagementDbProperties` exposes the common persistence configuration: + +* `DbTablePrefix` is the prefix used for EF Core tables and MongoDB collections. It defaults to `AbpCommonDbProperties.DbTablePrefix`. +* `DbSchema` is the schema used by EF Core. MongoDB does not use this value. +* `ConnectionStringName` is `AbpSettingManagement`. Define this named connection string to place the module in a separate database; otherwise, it falls back to the `Default` connection string. + +### Entity Framework Core + +The Entity Framework Core provider maps the following tables with the default prefix: + +* `AbpSettings` +* `AbpSettingDefinitions` + +### MongoDB + +The MongoDB provider maps the following collections with the default prefix: + +* `AbpSettings` +* `AbpSettingDefinitions` + +## See Also + +* [Settings](../framework/infrastructure/settings.md) From 950d868180b2432ca1a2ddeb6e85eae531a0a545 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:22 +0800 Subject: [PATCH 09/41] Expand Tenant Management documentation --- docs/en/modules/tenant-management.md | 75 ++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/docs/en/modules/tenant-management.md b/docs/en/modules/tenant-management.md index a8b56d0c6b..101c7f076f 100644 --- a/docs/en/modules/tenant-management.md +++ b/docs/en/modules/tenant-management.md @@ -37,7 +37,7 @@ In this page, you see the all the tenants. You can create a new tenant as shown In this modal; -* **Name**: The unique name of the tenant. If you use subdomains for your tenants (like https://some-tenant.your-domain.com), this will be the subdomain name. +* **Name**: The tenant name. Tenant names are normalized by the `ITenantNormalizer` service, so duplicate-name validation is case-insensitive by default. This validation is not a database uniqueness constraint. If you use subdomains for your tenants (like https://some-tenant.your-domain.com), this will be the subdomain name. * **Admin Email Address**: Email address of the admin user for this tenant. * **Admin Password**: The password of the admin user for this tenant. @@ -55,13 +55,53 @@ The Features action opens a modal to enable/disable/set [features](../framework/ *Manage Host features* button is used to set features for the host side, if you use the features of your application also in the host side. +### Extending the Angular UI + +For a standalone Angular application, register the Tenant Management menu configuration with `provideTenantManagementConfig` from the `@abp/ng.tenant-management/config` package: + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideTenantManagementConfig } from '@abp/ng.tenant-management/config'; + +export const appConfig: ApplicationConfig = { + providers: [provideTenantManagementConfig()], +}; +``` + +Lazy-load the module routes with `createRoutes` from `@abp/ng.tenant-management`: + +```ts +import { Routes } from '@angular/router'; + +export const APP_ROUTES: Routes = [ + { + path: 'tenant-management', + loadChildren: () => + import('@abp/ng.tenant-management').then(m => m.createRoutes()), + }, +]; +``` + +`createRoutes` accepts `entityActionContributors`, `toolbarActionContributors`, `entityPropContributors`, `createFormPropContributors` and `editFormPropContributors`. See the Angular guides for [entity actions](../framework/ui/angular/entity-action-extensions.md), [page toolbars](../framework/ui/angular/page-toolbar-extensions.md), [table columns](../framework/ui/angular/data-table-column-extensions.md) and [dynamic forms](../framework/ui/angular/dynamic-form-extensions.md). + +The `eTenantManagementComponents.Tenants` key identifies the Tenants page for these contributors and for [component replacement](../framework/ui/angular/component-replacement.md). + ## Distributed Events -This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module; +`TenantAppService.CreateAsync` explicitly publishes a `TenantCreatedEto` after it persists a new tenant. Its `Properties` dictionary contains the `AdminEmail` and plain-text `AdminPassword` values supplied by the create request. Treat this event as sensitive: protect it in transit and at rest, and do not log or retain the complete payload. The Framework's EF Core migration/seeding and MongoDB seeding handlers can consume this event when they are configured by the application. -- `TenantEto` is published on changes done on an `Tenant` entity. +`TenantCreatedEto` and `EntityCreatedEto` are separate distributed events. Enabling the automatic selector below publishes `EntityCreatedEto` in addition to the explicitly published `TenantCreatedEto`. If you subscribe to both, ensure that non-idempotent handlers distinguish between them instead of treating them as the same notification. -**Example: Get notified when a new tenant has been created** +The module also defines a `TenantEto` and pre-configures the mapping from `Tenant`. However, ABP does not automatically publish distributed entity-change events by default. Enable them in the application that owns Tenant Management if you want to subscribe to `EntityCreatedEto`, `EntityUpdatedEto` or `EntityDeletedEto`: + +```csharp +Configure(options => +{ + options.AutoEventSelectors.Add(); +}); +``` + +**Example: Subscribe to the opt-in entity-created event** ```cs public class MyHandler : @@ -76,11 +116,9 @@ public class MyHandler : } ``` +See the [Distributed Event Bus](../framework/infrastructure/event-bus/distributed) documentation for details of pre-defined entity events and automatic event selectors. - -`TenantEto` is 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. +> Subscribing to distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application, or listening for events in the same process that runs the Tenant Management module, subscribing to [local events](../framework/infrastructure/event-bus/local) can be more efficient and easier. ## Internals @@ -100,12 +138,16 @@ This section can be used as a reference if you want to [customize](../framework/ * `TenantManager` +`TenantManager` normalizes names on create and rename, and uses `ITenantValidator` to reject duplicate normalized names during those operations. + ### Application Layer #### Application Services * `TenantAppService` +In addition to tenant CRUD operations, `ITenantAppService` provides `GetDefaultConnectionStringAsync`, `UpdateDefaultConnectionStringAsync` and `DeleteDefaultConnectionStringAsync`. The HTTP API exposes these operations as `GET`, `PUT` and `DELETE` on `/api/multi-tenancy/tenants/{id}/default-connection-string`; the `PUT` request receives `defaultConnectionString` as a query parameter. + #### Permissions - `AbpTenantManagement.Tenants`: Tenant management. @@ -113,6 +155,19 @@ This section can be used as a reference if you want to [customize](../framework/ - `AbpTenantManagement.Tenants.Update`: Editing an existing tenant. - `AbpTenantManagement.Tenants.Delete`: Deleting an existing tenant. - `AbpTenantManagement.Tenants.ManageFeatures`: Manage features of the tenants. +- `AbpTenantManagement.Tenants.ManageConnectionStrings`: Read, update or delete the default connection string of a tenant. + +All Tenant Management permissions are available only on the host side. + +### Database Configuration + +`AbpTenantManagementDbProperties` exposes the common persistence configuration: + +* `DbTablePrefix` is the prefix used for EF Core tables and MongoDB collections. It defaults to the common ABP database prefix. +* `DbSchema` is the schema used by EF Core. MongoDB does not use this value. +* `ConnectionStringName` is `AbpTenantManagement`. Define this named connection string to place the module in a separate database; otherwise, it falls back to the `Default` connection string. + +See the [Connection Strings](../framework/fundamentals/connection-strings.md) documentation for named connection-string configuration and fallback behavior. ### Entity Framework Core Integration @@ -133,7 +188,9 @@ This section can be used as a reference if you want to [customize](../framework/ ## Notices -ABP allows to use *database per tenant* approach that allows a tenant can have a dedicated database. This module has the fundamental infrastructure to make that implementation possible (see its source code), however it doesn't implement the application layer and UI functionalities to provide it as an out of the box implementation. You can implement these features yourself, or consider to use the [ABP Saas Module](./saas.md) that fully implements it and provides much more business features. +ABP supports the *database per tenant* approach. This module can store a tenant's default connection string through `ITenantAppService` and the corresponding HTTP API. Its built-in MVC, Blazor, MudBlazor and Angular UIs do not provide a connection-string management screen, and changing the stored value does not create or migrate the tenant database. + +You can build your own UI and migration workflow on top of the application/API contract, or use the [ABP SaaS Module](./saas.md), which provides connection-string management and additional business features. ## See Also From 3831ab109cc4c541251ca2b3b39fb2c7d3032533 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 10/41] Document user lookup and synchronization --- .../modules/identity/user-synchronization.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 docs/en/modules/identity/user-synchronization.md diff --git a/docs/en/modules/identity/user-synchronization.md b/docs/en/modules/identity/user-synchronization.md new file mode 100644 index 0000000000..aec523cb4b --- /dev/null +++ b/docs/en/modules/identity/user-synchronization.md @@ -0,0 +1,117 @@ +```json +//[doc-seo] +{ + "Description": "Learn how reusable ABP modules keep module-specific user records synchronized with the Identity module." +} +``` + +# User Lookup and Synchronization + +Reusable modules sometimes need a small, module-specific user entity. For example, a module may need the user name and display information next to its own records without depending on the concrete `IdentityUser` aggregate. + +The `Volo.Abp.Users` packages provide shared contracts and base services for this pattern. They do not replace the [Identity module](../identity.md), the current-user service or the Identity user-management UI. + +## Core Contracts + +The main contracts are: + +* `IUser` defines the common aggregate data, including the user ID, tenant ID, user name, contact information and active state. +* `IUserData` is the transport-neutral representation used by lookup providers and synchronization code. +* `IUpdateUserData` lets a module-specific user entity opt in to updates from an `IUserData` value. +* `IUserRepository` defines lookup, search and count operations for a module-specific user repository. +* `IUserLookupService` is the service consumed by the rest of the module. + +The `UserLookupService` base class implements the local and external lookup workflow. A derived service only needs to define how an external `IUserData` value creates the module's user entity. + +```csharp +public interface IMyUserLookupService : IUserLookupService +{ +} + +public class MyUserLookupService : + UserLookupService, + IMyUserLookupService +{ + public MyUserLookupService( + IMyUserRepository userRepository, + IUnitOfWorkManager unitOfWorkManager) + : base(userRepository, unitOfWorkManager) + { + } + + protected override MyUser CreateUser(IUserData externalUser) + { + return new MyUser(externalUser); + } +} +``` + +In this example, `MyUser` implements `IUser` and `IUpdateUserData`, while `IMyUserRepository` implements `IUserRepository`. The Blogging and CMS Kit modules use this same pattern for their local user entities. + +## Lookup Behavior + +`FindByIdAsync` and `FindByUserNameAsync` first query the module's local repository. + +* Without an `IExternalUserLookupServiceProvider`, the local result is returned. +* By default, an existing local user is returned without querying the external provider. +* If no local user exists, the external result is converted to the module's user entity and persisted. +* If the local entity implements `IUpdateUserData`, data returned by the external provider can update that entity. +* If the external provider throws an exception, the exception is logged and the existing local result is returned. An exception does not delete local data. + +The default favors local reads over checking the external source on every request. A derived lookup service can set `SkipExternalLookupIfLocalUserExists` to `false` when every find operation must check the external source: + +```csharp +public class FreshMyUserLookupService : + UserLookupService +{ + public FreshMyUserLookupService( + IMyUserRepository userRepository, + IUnitOfWorkManager unitOfWorkManager) + : base(userRepository, unitOfWorkManager) + { + SkipExternalLookupIfLocalUserExists = false; + } + + protected override MyUser CreateUser(IUserData externalUser) + { + return new MyUser(externalUser); + } +} +``` + +With this option disabled, a missing external result causes the corresponding local user to be deleted. This behavior applies to `FindByIdAsync` and `FindByUserNameAsync`; it is not a background synchronization process. + +`SearchAsync` and `GetCountAsync` follow a different rule. If an external provider is available, they delegate directly to that provider instead of combining local and external results. The provider must apply consistent filtering to both methods and implement the search method's paging and sorting. + +The shared repository bases compare `UserName` directly. The Users module does not define a normalization algorithm for custom repositories or providers. If a provider normalizes user names, keep the local repository and external provider lookup rules consistent rather than relying on case-insensitive behavior. + +## External Lookup Providers + +The Identity module supplies two common `IExternalUserLookupServiceProvider` implementations: + +* `IdentityUserRepositoryExternalUserLookupServiceProvider` reads from the local Identity repository. +* `HttpClientExternalUserLookupServiceProvider` calls the Identity integration service in a remote application. + +See the [External User Lookup Service](../identity.md#external-user-lookup-service) section for monolithic and distributed application setup. + +You can replace the external provider with your own implementation. It is responsible for ID and user-name lookups and for matching `SearchAsync` and `GetCountAsync` behavior. + +## Synchronizing with Distributed Events + +`UserEto` implements `IUserData` and carries the tenant ID. The Identity module maps `IdentityUser` to this ETO and enables the standard created, updated and deleted distributed entity events. + +The Users module does not register synchronization handlers for your entity. A reusable module decides which events it needs. A common design is: + +* Create the local user lazily through `IUserLookupService` when the module first needs it. +* Subscribe to `EntityUpdatedEto` to refresh an existing local copy. +* Subscribe to `EntityDeletedEto` when deleted users must be removed immediately. With the default local-first setting, an existing local record is not rechecked against the external source. + +Keep the local entity ID and tenant ID equal to the values in `IUserData`. An `IUpdateUserData.Update` implementation should reject data for another user or tenant and return `false` when nothing changed. + +See the [Distributed Events](../identity.md#distributed-events) section for the Identity event configuration and a basic event-handler example. + +## Database Providers + +The Users module provides `EfCoreUserRepositoryBase` and `MongoUserRepositoryBase`. Both implement the shared repository search and count contract. The EF Core package also provides `ConfigureAbpUser` for the common user properties and length constraints. + +Your module still owns its user table or collection, indexes and additional properties. Provider-specific schema decisions remain in the module that defines the concrete user entity. From 744612bbd564bb0ec1daa06be89489f017d96c61 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 11/41] Expand Identity and Account module documentation --- docs/en/modules/account.md | 39 +++++++++++++++++++++++++++ docs/en/modules/identity.md | 54 +++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/docs/en/modules/account.md b/docs/en/modules/account.md index 844e00e06f..2148ca5d42 100644 --- a/docs/en/modules/account.md +++ b/docs/en/modules/account.md @@ -37,6 +37,8 @@ Social/external login buttons becomes visible if you setup it. See the *Social/E ![account-module-register](../images/account-module-register.png) +New users receive every Identity role marked as `Default`. + ### Forgot Password & Reset Password `/Account/ForgotPassword` page provides a way of sending password reset link to user's email address. The user then clicks to the link and determines a new password. @@ -51,6 +53,34 @@ Social/external login buttons becomes visible if you setup it. See the *Social/E ![account-module-manage-account](../images/account-module-manage-account.png) +`IdentitySettingNames.User.IsUserNameUpdateEnabled` and `IdentitySettingNames.User.IsEmailUpdateEnabled` control whether the profile application service accepts changes to those fields. Both settings are `true` by default. External users can't change a local password; the built-in MVC profile page omits the password group for them and the application service rejects a password change. + +### Login and Registration Settings + +The Account module defines two client-visible settings. Both are `true` by default: + +* `AccountSettingNames.IsSelfRegistrationEnabled` controls self-registration. It is enforced by `IAccountAppService.RegisterAsync` as well as the built-in registration pages. +* `AccountSettingNames.EnableLocalLogin` controls the local username/password login UI and handlers in the MVC Account pages, the OpenIddict and IdentityServer integrations, and the Angular Account layout. In Angular, `AuthWrapperService` reads the setting; the Basic Theme's `AuthWrapperComponent` shows the account content when it is enabled and a no-login-schemes warning when it is disabled. + +These settings are independent. Disabling local login doesn't disable the registration application service. Set `IsSelfRegistrationEnabled` to `false` as well when users must not create local accounts. Change the values with `ISettingManager` like other [settings](../framework/infrastructure/settings.md). Global or tenant values are normally appropriate because the login and registration requests run before a user is authenticated. + +### Extending the MVC Profile Page + +The MVC `/Account/Manage` page is built from the contributors in `ProfileManagementPageOptions.Contributors`. Implement `IProfileManagementPageContributor` to add a group backed by a view component, then register it from your module: + +```csharp +Configure(options => +{ + options.Contributors.Add(new MyProfileManagementPageContributor()); +}); +``` + +Each contributor receives a `ProfileManagementPageCreationContext` and appends `ProfileManagementPageGroup` instances to its `Groups` collection. Contributors run in registration order on both GET and POST requests, and can resolve services through `context.ServiceProvider` when visibility depends on the current user or another runtime condition. + +### Angular UI Extensibility + +The Angular `createRoutes` function accepts three module-specific options: `redirectUrl`, `isPersonalSettingsChangedConfirmationActive` and `editFormPropContributors`. The form contributor key is `eAccountComponents.PersonalSettings`. The login, register, forgot-password, reset-password and manage-profile routes are also registered with the corresponding `eAccountComponents` keys for [component replacement](../framework/ui/angular/component-replacement.md). See [Dynamic Form Extensions](../framework/ui/angular/dynamic-form-extensions.md) for the contributor pattern. + ## OpenIddict Integration [Volo.Abp.Account.Web.OpenIddict](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) package provides integration for the [OpenIddict](https://github.com/openiddict). This package comes as installed with the [application startup template](../solution-templates/layered-web-application). See the [OpenIddict Module](./openiddict.md) documentation. @@ -63,6 +93,15 @@ Social/external login buttons becomes visible if you setup it. See the *Social/E The Account Module has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application. +The MVC login and registration pages also recognize a Windows authentication scheme. `AbpAccountOptions.WindowsAuthenticationSchemeName` identifies that scheme and defaults to `"Windows"`. Set it when the registered scheme uses another name: + +```csharp +Configure(options => +{ + options.WindowsAuthenticationSchemeName = "Negotiate"; +}); +``` + ### Example: Facebook Authentication Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application. diff --git a/docs/en/modules/identity.md b/docs/en/modules/identity.md index d92f4b7b8e..6b66763cf1 100644 --- a/docs/en/modules/identity.md +++ b/docs/en/modules/identity.md @@ -45,15 +45,20 @@ This page is used to see the list of users. You can create/edit and delete users A user can have zero or more roles. Users inherit permissions from their roles. In addition, you can assign permissions directly to the users (by clicking the *Actions* button, then selecting the *Permissions*). +Role changes submitted through the general create or update input are applied only when the current user has the `AbpIdentity.Users.Update.ManageRoles` permission; otherwise, the submitted `RoleNames` value is ignored. Role changes performed by the administration workflow are filtered to prevent privilege escalation: except for an operator in the built-in `admin` role, an operator can only add or remove roles that they already have, and roles outside that set are kept unchanged. An operator in the `admin` role can assign any role. + +When users edit their own record through the Identity administration service, a submitted `IsActive` change is ignored, and the built-in MVC, Blazor and MudBlazor user interfaces hide that field. The current user also can't delete their own account from this service. + #### Roles Roles are used to group permissions assign them to users. ![identity-module-roles](../images/identity-module-roles.png) -Beside the role name, there are two properties of a role: +Beside the role name, there are three properties of a role: * `Default`: If a role is marked as "default", then that role is assigned to new users by default when they register to the application themselves (using the [Account Module](account.md)). +* `Static`: A static role can't be renamed or deleted. Seeded roles, such as the built-in `admin` role, can use this flag to protect their identity in the application. * `Public`: A public role of a user can be seen by other users in the application. This feature has no usage in the Identity module, but provided as a feature that you may want to use in your own application. ## Other Features @@ -79,6 +84,7 @@ Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. - There can be more than one root (where the `ParentId` is `null`). - There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below). +- A parent and all of its children must belong to the same tenant. Creating or moving an OU under an OU from another tenant is rejected. #### OU Code @@ -103,6 +109,10 @@ The `OrganizationUnitManager` class can be [injected](../framework/fundamentals/ - Move an OU in the OU tree. - Getting information about the OU tree and its items. +Roles can be assigned to an OU. A user gets both directly assigned roles and the roles of every OU they belong to. Adding or removing an OU role or membership invalidates the affected users' dynamic claims cache, so the effective role claims are rebuilt on the next refresh. + +`IdentitySettingNames.OrganizationUnit.MaxUserMembershipCount` limits how many OUs a user can belong to. Its default value is `int.MaxValue`. `IdentityUserManager.AddToOrganizationUnitAsync` and `SetOrganizationUnitsAsync` reject changes that exceed the configured value. + ### Identity Security Log The security log system records some important operations or changes about your account (like *login* and *change password*). You can also save the security log if needed. @@ -217,6 +227,38 @@ public class MyService : ITransientDependency `IdentitySettingNames` class (in the `Volo.Abp.Identity.Settings` namespace) defines constants for the setting names. +### ASP.NET Core Authentication Registration + +`AbpIdentityAspNetCoreModule` registers the ASP.NET Core Identity application and external cookie schemes by default. If the host configures authentication and cookies itself, disable this registration in `PreConfigureServices` and add the required schemes in the host: + +```csharp +PreConfigure(options => +{ + options.ConfigureAuthentication = false; +}); +``` + +Disabling this option only skips `AddAuthentication` and `AddIdentityCookies`; the Identity managers, stores, token providers and security-stamp validator remain registered. + +### Dynamic Claims Cache + +The Identity module caches the dynamic claims it builds for a user for one hour by default. Configure `IdentityDynamicClaimsPrincipalContributorCacheOptions.CacheAbsoluteExpiration` to change the absolute lifetime: + +```csharp +Configure(options => +{ + options.CacheAbsoluteExpiration = TimeSpan.FromMinutes(30); +}); +``` + +Identity operations that change a user's claims or effective roles clear the affected cache entries. The expiration remains the upper bound for entries that aren't explicitly invalidated. See the [Dynamic Claims](../framework/fundamentals/dynamic-claims.md) document for the end-to-end refresh pipeline. + +## Angular UI Extensibility + +The Angular `createRoutes` function accepts `entityActionContributors`, `toolbarActionContributors`, `entityPropContributors`, `createFormPropContributors` and `editFormPropContributors` for both `eIdentityComponents.Roles` and `eIdentityComponents.Users`. + +The [entity actions](../framework/ui/angular/entity-action-extensions.md), [page toolbars](../framework/ui/angular/page-toolbar-extensions.md), [table columns](../framework/ui/angular/data-table-column-extensions.md) and [dynamic forms](../framework/ui/angular/dynamic-form-extensions.md) guides use the Identity module to demonstrate these `createRoutes` contributors. To replace the complete roles or users component, register the corresponding key with `ReplaceableComponentsService` as described in the [component replacement](../framework/ui/angular/component-replacement.md) guide. + ## Distributed Events This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module; @@ -327,13 +369,10 @@ Following custom repositories are defined for this module: * `IdentityUserAppService` (implements `IIdentityUserAppService`): Implements the use cases of the user management UI. * `IdentityUserIntegrationService` (implements `IIdentityUserIntegrationService`): Used for module-to-module and service-to-service user and role lookup operations. -* `IdentityRoleAppService` (implement `IIdentityRoleAppService`): Implements the use cases of the role management UI. -* `IdentityClaimTypeAppService` (implements `IIdentityClaimTypeAppService`): Implements the use cases of the claim type management UI. -* `IdentitySettingsAppService` (implements `IIdentitySettingsAppService`): Used to get and update settings for the Identity module. +* `IdentityRoleAppService` (implements `IIdentityRoleAppService`): Implements the use cases of the role management UI. * `IdentityUserLookupAppService` (implements `IIdentityUserLookupAppService`): Kept for backward compatibility and internally delegates to `IIdentityUserIntegrationService`. -* `ProfileAppService` (implements `IProfileAppService`): Used to change a user's profile and the password. -* ```IdentitySecurityLogAppService``` (implements ```IIdentitySecurityLogAppService```): Implements the use cases of the security logs UI. -* ```OrganizationUnitAppService``` (implements ```OrganizationUnitAppService```): Implements the use cases of the organization unit management UI. + +Profile editing and password changes are provided by the Account module's `ProfileAppService`. Claim type, Identity settings, security log and organization unit administration application services are not part of the open-source Identity module. ### Database Providers @@ -379,4 +418,3 @@ You can set the following properties of the `AbpIdentityDbProperties` class to c * `ConnectionStringName` (`AbpIdentity` by default) is the [connection string](../framework/fundamentals/connection-strings.md) name for this module. These are static properties. If you want to set, do it in the beginning of your application (typically, in `Program.cs`). - From 981cc7d59ba1384d8023a2b64fe65bec05bb4846 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 12/41] Expand OpenIddict module documentation --- docs/en/modules/openiddict.md | 106 +++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 28 deletions(-) diff --git a/docs/en/modules/openiddict.md b/docs/en/modules/openiddict.md index 910bb2c738..ebedadd586 100644 --- a/docs/en/modules/openiddict.md +++ b/docs/en/modules/openiddict.md @@ -19,12 +19,14 @@ The source code of this module can be accessed [here](https://github.com/abpfram ## User Interface -This module implements the domain logic and database integrations but does not provide any UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider purchasing the [ABP](https://abp.io/) which provides the management UI for this module. +This module implements the domain logic and database integrations but does not provide a management UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider purchasing the [ABP](https://abp.io/) which provides the management UI for this module. The ASP.NET Core integration includes the authorization consent view used by the protocol flow. ## Relations to Other Modules This module is based on the [Identity Module](./identity.md) and has an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) with the [Account Module](account.md). +The optional `Volo.Abp.PermissionManagement.Domain.OpenIddict` package integrates OpenIddict applications with the [Permission Management Module](./permission-management.md). It provides client permission managers and providers. Its distributed event handlers also move client permission grants when a client ID changes and delete them when the application is deleted. + ## Options ### OpenIddictBuilder @@ -136,20 +138,24 @@ These services contain: ##### OpenIddictApplication -OpenIddictApplications represent the applications that can request tokens from your OpenIddict Server. +OpenIddict applications represent the clients that can request tokens from your OpenIddict server. -- `OpenIddictApplications` (aggregate root): Represents an OpenIddict application. +- `OpenIddictApplication` (aggregate root): Represents an OpenIddict application. + - `ApplicationType` (string): The application type associated with the application. - `ClientId` (string): The client identifier associated with the current application. - `ClientSecret` (string): The client secret associated with the current application. Maybe hashed or encrypted for security reasons. + - `ClientType` (string): The client type associated with the current application. - `ConsentType` (string): The consent type associated with the current application. - `DisplayName` (string): The display name associated with the current application. - `DisplayNames` (string): The localized display names associated with the current application serialized as a JSON object. + - `JsonWebKeySet` (string): The JSON Web Key Set associated with the application, serialized as a JSON object. - `Permissions` (string): The permissions associated with the current application, serialized as a JSON array. - `PostLogoutRedirectUris` (string): The logout callback URLs associated with the current application, serialized as a JSON array. - `Properties` (string): The additional properties associated with the current application serialized as a JSON object or null. - `RedirectUris` (string): The callback URLs associated with the current application, serialized as a JSON array. - - `Requirements` (string): The requirements associated with the current application - - `Type` (string): The application type associated with the current application. + - `Requirements` (string): The requirements associated with the current application, serialized as a JSON array. + - `Settings` (string): The settings associated with the current application, serialized as a JSON object. + - `FrontChannelLogoutUri` (string): The front-channel logout URI associated with the application. - `ClientUri` (string): URI to further information about client. - `LogoUri` (string): URI to client logo. @@ -161,6 +167,8 @@ OpenIddictAuthorizations are used to keep the allowed scopes, authorization flow - `ApplicationId` (Guid?): The application associated with the current authorization. + - `CreationDate` (DateTime?): The UTC creation date of the current authorization. + - `Properties` (string): The additional properties associated with the current authorization serialized as a JSON object or null. - `Scopes` (string): The scopes associated with the current authorization, serialized as a JSON array. @@ -196,14 +204,13 @@ OpenIddictTokens are used to persist the application tokens. - `OpenIddictToken` (aggregate root): Represents an OpenIddict token. - `ApplicationId` (Guid?): The application associated with the current token. - - `AuthorizationId` (Guid?): The application associated with the current token. + - `AuthorizationId` (Guid?): The authorization associated with the current token. - `CreationDate` (DateTime?): The UTC creation date of the current token. - `ExpirationDate` (DateTime?): The UTC expiration date of the current token. - `Payload` (string): The payload of the current token, if applicable. Only used for reference tokens and may be encrypted for security reasons. - `Properties` (string): The additional properties associated with the current token serialized as a JSON object or null. - `RedemptionDate` (DateTime?): The UTC redemption date of the current token. - - `Status` (string): The status of the current authorization. - `ReferenceId` (string): The reference identifier associated with the current token, if applicable. Only used for reference tokens and may be hashed or encrypted for security reasons. @@ -224,7 +231,20 @@ This module implements OpenIddict stores: #### AbpOpenIddictStoreOptions -You can configure the `PruneIsolationLevel/DeleteIsolationLevel` of `AbpOpenIddictStoreOptions` to set the isolation level for the store operations becasue different databases have different isolation levels. +`AbpOpenIddictStoreOptions` controls the transaction isolation levels used by destructive store operations: + +- `PruneIsolationLevel` defaults to `IsolationLevel.RepeatableRead` and is used while pruning tokens and authorizations. +- `DeleteIsolationLevel` defaults to `IsolationLevel.Serializable` and is used when deleting applications and authorizations together with their related records. + +You can change these values when your database requires a different isolation level: + +```csharp +Configure(options => +{ + options.PruneIsolationLevel = System.Data.IsolationLevel.ReadCommitted; + options.DeleteIsolationLevel = System.Data.IsolationLevel.RepeatableRead; +}); +``` ##### Repositories @@ -251,10 +271,12 @@ All tables/collections use the `OpenIddict` prefix by default. Set static proper ##### Connection String -This module uses `AbpOpenIddict` 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 `AbpOpenIddict` 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. +The built-in EF Core and MongoDB contexts are marked with `IgnoreMultiTenancy`, so OpenIddict data belongs to the host/shared database rather than to tenant databases. In addition, the EF Core `ConfigureOpenIddict()` extension skips the OpenIddict model when the current database is configured as tenant-only. + #### Entity Framework Core ##### Tables @@ -275,16 +297,16 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do ## ASP.NET Core Module -This module integrates ASP NET Core, with built-in MVC controllers for four protocols. It uses OpenIddict's [Pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode). +This module integrates with ASP.NET Core and provides pass-through MVC controllers for the authorization, token, end-session and userinfo endpoints. It uses OpenIddict's [pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode). -```cs -AuthorizeController -> connect/authorize -TokenController -> connect/token -LogoutController -> connect/logout -UserInfoController -> connect/userinfo -``` +| Controller | Route | +| --- | --- | +| `AuthorizeController` | `/connect/authorize` | +| `TokenController` | `/connect/token` | +| `LogoutController` | `/connect/endsession` | +| `UserInfoController` | `/connect/userinfo` | -> **Device flow** implementation will be done in the commercial module. +The server enables authorization code, hybrid, implicit, password, client credentials, refresh token, device authorization, none and token exchange flows by default. A client application still needs the corresponding endpoint, grant type, scope and response type permissions. This module handles device-code token requests and registers the end-user verification endpoint, but it does not provide the end-user verification UI. ### AbpOpenIddictAspNetCoreOptions @@ -301,8 +323,10 @@ PreConfigure(options => `AbpOpenIddictAspNetCoreOptions` properties: -- `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the Openiddict claims. +- `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the OpenIddict claims. - `AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**. You must set it to **false** for non-development environments. +- `AttachCultureInfo` (default: true): Adds the current `culture` and `ui-culture` values to authorization responses when those parameters have not already been set. +- `SelectAccountPage` (default: `~/Account/SelectAccount`): Sets the page used when an authorization request specifies `prompt=select_account`. The open-source module performs the redirect but doesn't provide a page at this path. If clients can send this prompt, implement the page in your host and set this option to its route. - `UseDefaultScopesForClientCredentials(default: false)`: When set to `true`, the access token issued for the `client_credentials` grant automatically grants the scopes configured on the client application (permissions prefixed with `oi_scp:`) when the client does not explicitly request any scope. - `UseDefaultScopesForPassword(default: false)`: When set to `true`, the token response for the `password` grant automatically grants the scopes configured on the client application when the client does not explicitly request any scope. If the configured scopes include `openid`/`profile`/`email`/`roles`, the corresponding `id_token` and claim destinations are affected as well. - `UseDefaultScopesForTokenExchange(default: false)`: When set to `true`, the token response for the `urn:ietf:params:oauth:grant-type:token-exchange` grant automatically grants the scopes configured on the client application when the client does not explicitly request any scope. If the configured scopes include `openid`/`profile`/`email`/`roles`, the corresponding `id_token` and claim destinations are affected as well. @@ -318,9 +342,9 @@ PreConfigure(options => > `AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to load a user profile). To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates store of the host machine(s). Please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-development-certificate -#### Automatically Removing Orphaned Tokens/Authorizations +#### Automatically Removing Invalid Tokens and Authorizations -The background task that automatically removes orphaned tokens/authorizations. This can be configured by `TokenCleanupOptions` to manage it. +The cleanup worker prunes old invalid or expired tokens first, and then prunes old invalid authorizations and ad-hoc authorizations that no longer have a token. It uses a distributed lock, so only one application instance performs a cleanup pass at a time. `TokenCleanupOptions` can be configured in the `ConfigureServices` method of your OpenIddict [module](../framework/architecture/modularity/basics.md). @@ -335,12 +359,14 @@ Configure(options => `TokenCleanupOptions` properties: -- `IsCleanupEnabled` (default: true): Enable/disable token clean up. -- `CleanupPeriod` (default: 3,600,000 ms): Setting clean up period. -- `DisableAuthorizationPruning`: Setting a boolean indicating whether authorizations pruning should be disabled. -- `DisableTokenPruning`: Setting a boolean indicating whether token pruning should be disabled. -- `MinimumAuthorizationLifespan` (default: 14 days): Setting the minimum lifespan authorizations must have to be pruned. Cannot be less than 10 minutes. -- `MinimumTokenLifespan` (default: 14 days): Setting the minimum lifespan tokens must have to be pruned. Cannot be less than 10 minutes. +- `IsCleanupEnabled` (default: true): Controls whether the cleanup worker is registered. +- `CleanupPeriod` (default: 3,600,000 ms): Sets the interval between cleanup passes. +- `DisableAuthorizationPruning` (default: false): Disables authorization pruning when set to `true`. +- `DisableTokenPruning` (default: false): Disables token pruning when set to `true`. +- `MinimumAuthorizationLifespan` (default: 14 days): Sets the minimum age of authorizations that can be pruned. +- `MinimumTokenLifespan` (default: 14 days): Sets the minimum age of tokens that can be pruned. + +The worker is registered during application initialization only when `IsCleanupEnabled` is `true`. The global [background worker](../framework/infrastructure/background-workers/index.md) switch must also be enabled for it to run. #### Updating Claims In Access_token and Id_token @@ -433,7 +459,7 @@ This request will be processed by various handlers. They will confirm the endpoi If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the HTTP request at this time. The rest will be based on this object. -Check the `username` and `password` in the request. If it is correct create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing. +After validating the `username` and `password`, the controller creates a `ClaimsPrincipal` and returns a `SignInResult` that uses the `OpenIddict.Server.AspNetCore` authentication scheme. `OpenIddictServerAspNetCoreHandler` then processes the result. `OpenIddictServerAspNetCoreHandler` do some checks to generate json and replace the http response content. @@ -444,6 +470,30 @@ If you need to customize OpenIddict, you need to replace/delete/add new handlers Please refer to: https://documentation.openiddict.com/guides/index.html#events-model +### Custom Token Grant Types + +Implement `ITokenExtensionGrant` to handle a custom token grant. Register the grant type with OpenIddict and add the handler instance to `AbpOpenIddictExtensionGrantsOptions`: + +```csharp +PreConfigure(builder => +{ + builder.Configure(options => + { + options.GrantTypes.Add(MyTokenExtensionGrant.GrantType); + }); +}); + +Configure(options => +{ + options.Grants.Add( + MyTokenExtensionGrant.GrantType, + new MyTokenExtensionGrant() + ); +}); +``` + +When a request uses an otherwise unhandled grant type, `TokenController` resolves the registered `ITokenExtensionGrant` and calls its `HandleAsync` method with the current `HttpContext` and `OpenIddictRequest`. See [How to add a custom grant type in OpenIddict](../Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md) for a longer example. + ### PKCE https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html @@ -484,7 +534,7 @@ await CreateApplicationAsync( ... ``` -> **Note:** You need to re-create this client if you have generated the database already. +> **Note:** The current startup template updates the permissions and redirect URI values of an existing client when data seeding runs. Run the database migrator or the data seeder after changing the contributor. If a custom or older contributor only creates missing clients, add an existing-client update path or recreate the client. #### Configuring Application: From 6b111b71dbfa15ec341233eb11b1f4cdad441b91 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 13/41] Update IdentityServer module documentation --- docs/en/modules/identity-server-pro.md | 81 +++++++---- docs/en/modules/identity-server.md | 126 +++++++++++++++--- .../identityserver-to-openiddict.md | 18 +-- .../identityserver4-step-by-step.md | 22 +-- .../deployment/identityserver-deployment.md | 82 +++--------- 5 files changed, 206 insertions(+), 123 deletions(-) diff --git a/docs/en/modules/identity-server-pro.md b/docs/en/modules/identity-server-pro.md index 48ead33082..14aa5bf278 100644 --- a/docs/en/modules/identity-server-pro.md +++ b/docs/en/modules/identity-server-pro.md @@ -16,19 +16,22 @@ This module provides integration and management functionality for Identity Serve * Set **permissions** for clients. * Create **standard identity resources** (like role, profile) easily. * Create custom **identity resources**. -* Manage **API resources** +* Manage **API resources**. +* Manage **API scopes**. + +> **Legacy module:** Current ABP startup templates use the [OpenIddict module](./openiddict.md). This IdentityServer4 administration module remains available for existing applications that still use the [open-source IdentityServer integration](identity-server.md), but it is not installed in newly generated applications. See [the module description page](https://abp.io/modules/Volo.identityserver.Ui) for an overview of the module features. ## How to Install -Identity Server is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it. +This module was pre-installed in startup templates before ABP v6.0. Current templates use OpenIddict. Install this module only in an application that uses IdentityServer4, and don't install the IdentityServer and OpenIddict provider modules together. ## 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 [Identity module package list page](https://abp.io/packages?moduleName=Volo.Identity.Pro) to see list of packages related with this module. +You can visit the [Identity Server module package list](https://abp.io/packages?moduleName=Volo.IdentityServer.Ui) to see the related packages. ## User Interface @@ -39,6 +42,7 @@ Identity Server module adds the following items to the "Main" menu, under the "A * **Clients**: Client management page. * **Identity resources**: Identity resource management page. * **API resources**: API resource management page. +* **API scopes**: API scope management page. `AbpIdentityServerMenuNames` class has the constants for the menu item names. @@ -54,6 +58,8 @@ You can create new clients or edit existing clients in this page: ![identity-server-edit-client-modal](../images/identity-server-edit-client-modal.png) +New client secrets submitted during create or update are SHA-256 hashed before they are stored. Configure the consuming client with the original secret value, not the stored hash, and retain the original value in your secret-management system because it cannot be recovered from the hash. + #### Identity Resource Management Identity resource page is used to manage identity resources of Identity Server. Identity resources are data like user ID, name, or email address of a user. @@ -76,9 +82,15 @@ You can create a new API resource or edit an existing API resource in this page: ![identity-server-edit-api-resource-modal](../images/identity-server-edit-api-resource-modal.png) +New API resource secrets submitted during update are also SHA-256 hashed before persistence. The consumer must use the original secret value. + +#### API Scope Management + +API scopes define the scopes that clients can request. The API scopes page allows you to create, update and delete scopes independently from API resources. + ## Data Seed -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: +The domain package provides `IIdentityResourceDataSeeder`, which a legacy application can call from its IdentityServer data seed contributor when the `.DbMigrator` application runs (see the [data seed system](../framework/infrastructure/data-seeding.md)): * Creates standard identity resources which are role, profile, phone, openid, email and address. @@ -106,6 +118,7 @@ public override void PreConfigureServices(ServiceConfigurationContext context) * `UpdateAbpClaimTypes` (default: true): Updates `AbpClaimTypes` to be compatible with identity server claims. * `IntegrateToAspNetIdentity` (default: true): Integrate to ASP.NET Identity. * `AddDeveloperSigningCredential` (default: true): Set false to suppress AddDeveloperSigningCredential() call on the IIdentityServerBuilder. +* `AddIdentityServerCookieAuthentication` (default: true): Adds IdentityServer's default cookie authentication handlers. Set it to `false` when the host registers and configures these handlers itself. `IIdentityServerBuilder` can be configured in `PreConfigureServices` method of your Identity Server [module](../framework/architecture/modularity/basics.md). Example: @@ -132,9 +145,15 @@ This module follows the [Entity Best Practices & Conventions](../framework/archi API Resources are needed for allowing clients to request access tokens. * `ApiResource` (aggregate root): Represents an API resource in the system. - * `ApiSecret` (collection): secrets of the API resource. - * `ApiScope` (collection): scopes of the API resource. + * `ApiResourceSecret` (collection): secrets of the API resource. + * `ApiResourceScope` (collection): scope names associated with the API resource. * `ApiResourceClaim` (collection): claims of the API resource. + +##### ApiScope + +* `ApiScope` (aggregate root): Represents an API scope. + * `ApiScopeClaim` (collection): Claims included for the scope. + * `ApiScopeProperty` (collection): Custom properties of the scope. ##### Client @@ -157,12 +176,16 @@ Persisted Grants stores AuthorizationCodes, RefreshTokens and UserConsent. * `PersistedGrant` (aggregate root): Represents PersistedGrant for identity server. +##### DeviceFlowCodes + +* `DeviceFlowCodes` (aggregate root): Stores device authorization data until it expires. + ##### IdentityResource Identity resources are data like user ID, name, or email address of a user. -* `IdentityResource` (aggregate root): Represents and Identity Server identity resource. - * `IdentityClaim` (collection): Claims of identity resource. +* `IdentityResource` (aggregate root): Represents an Identity Server identity resource. + * `IdentityResourceClaim` (collection): Claims of the identity resource. #### Repositories @@ -171,7 +194,9 @@ This module follows the [Repository Best Practices & Conventions](../framework/a Following custom repositories are defined for this module: * `IApiResourceRepository` +* `IApiScopeRepository` * `IClientRepository` +* `IDeviceFlowCodesRepository` * `IPersistentGrantRepository` * `IIdentityResourceRepository` @@ -193,9 +218,10 @@ This module doesn't define any settings. #### Application Services -* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. -* `IdentityServerClaimTypeAppService` (implement `IIdentityServerClaimTypeAppService`): Used to get list of claims. -* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. +* `ClientAppService` (implements `IClientAppService`): Implements client management and client permission operations. +* `IdentityServerClaimTypeAppService` (implements `IIdentityServerClaimTypeAppService`): Gets the available claim types. +* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements API resource management. +* `ApiScopeAppService` (implements `IApiScopeAppService`): Implements API scope management. * `IdentityResourceAppService` (implements `IIdentityResourceAppService`): Implements the use cases of the Identity resource management UI. ### Database Providers @@ -212,15 +238,20 @@ This module uses `AbpIdentityServer` for the connection string name. If you don' See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details. +IdentityServer configuration is host data. The built-in EF Core and MongoDB contexts ignore the current tenant, and the EF Core model isn't added to a tenant-only database. + #### Entity Framework Core ##### Tables * **IdentityServerApiResources** - * IdentityServerApiSecrets - * IdentityServerApiScopes - * IdentityServerApiScopeClaims - * IdentityServerApiClaims + * IdentityServerApiResourceSecrets + * IdentityServerApiResourceScopes + * IdentityServerApiResourceClaims + * IdentityServerApiResourceProperties +* **IdentityServerApiScopes** + * IdentityServerApiScopeClaims + * IdentityServerApiScopeProperties * **IdentityServerClients** * IdentityServerClientScopes * IdentityServerClientSecrets @@ -232,21 +263,25 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do * IdentityServerClientClaims * IdentityServerClientProperties * **IdentityServerPersistedGrants** +* **IdentityServerDeviceFlowCodes** * **IdentityServerIdentityResources** - * IdentityServerIdentityClaims + * IdentityServerIdentityResourceClaims + * IdentityServerIdentityResourceProperties #### MongoDB ##### Collections * **IdentityServerApiResources** +* **IdentityServerApiScopes** * **IdentityServerClients** * **IdentityServerPersistedGrants** +* **IdentityServerDeviceFlowCodes** * **IdentityServerIdentityResources** ### Permissions -See the `AbpIdentityServerPermissions` class members for all permissions defined for this module. +The module defines separate read, create, update and delete permissions for clients, identity resources, API resources and API scopes. Client management also has a permission for managing the permissions granted to a client. See the `AbpIdentityServerPermissions` class for the exact permission names. ### Angular UI @@ -266,7 +301,7 @@ export const appConfig: ApplicationConfig = { }; ``` -The identity server module should be imported and lazy-loaded in your routing module. It has a static `creatRoutes` method for configuration. Available options are listed below. It is available for import from `@volo/abp.ng.identity-server`. +The Identity Server module should be lazy-loaded in your routing configuration. Import and call the `createRoutes` function from `@volo/abp.ng.identity-server`. Available options are listed below. ```js // app.routes.ts @@ -280,17 +315,17 @@ const APP_ROUTES: Routes = [ ]; ``` -> If you have generated your project via the startup template, you do not have to do anything, because it already has both files configured. +> Applications generated from a legacy IdentityServer startup template already have both files configured.

Options

-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 the `createRoutes` function: - **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. #### Services / Models @@ -330,6 +365,6 @@ export const environment = { The Identity Server module remote URL configuration shown above is optional. If you don't set a URL, the `default.url` will be used as fallback. -## Distributed Events +## CORS Cache Invalidation -This module defines events for `Client` aggregate and `ClientCorsOrigin` entity. When a `Client` or `ClientCorsOrigin` changes, `AllowedCorsOriginsCacheItemInvalidator` invalidates the cache for `AllowedCorsOriginsCacheItem`. See the [standard distributed events](../framework/infrastructure/event-bus/distributed) for more information about distributed events. +When a `Client` or `ClientCorsOrigin` changes in the current process, local entity-change handlers invalidate the cached set of allowed CORS origins. Applications don't need to clear this cache after using the module's repositories or application services. diff --git a/docs/en/modules/identity-server.md b/docs/en/modules/identity-server.md index 5df207a17b..f79f135056 100644 --- a/docs/en/modules/identity-server.md +++ b/docs/en/modules/identity-server.md @@ -7,13 +7,15 @@ # IdentityServer Module -IdentityServer module provides a full integration with the [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) (IDS) framework, which provides advanced authentication features like single sign-on and API access control. This module persists clients, resources and other IDS-related objects to database. **This module is replaced by** [OpenIddict module](./openiddict.md) after ABP v6.0 in the startup templates. +IdentityServer module provides a full integration with the [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) (IDS) framework, which provides advanced authentication features like single sign-on and API access control. This module persists clients, resources and other IDS-related objects to a database. -> Note: You can not use IdentityServer and OpenIddict modules together. They are separate OpenID provider libraries for the same job. +> **Legacy module:** The ABP startup templates have used the [OpenIddict module](./openiddict.md) instead of IdentityServer since ABP v6.0. IdentityServer4 is archived and no longer maintained by its owners. ABP still ships the IdentityServer integration packages for applications that already depend on them, but new applications should use OpenIddict. See the [IdentityServer to OpenIddict migration guide](../release-info/migration-guides/identityserver-to-openiddict.md) when upgrading an existing application. + +> Note: You cannot use the IdentityServer and OpenIddict modules together. They are separate OpenID provider libraries for the same job. ## How to Install -You don't need this module when you are using OpenIddict module. However, if you want to keep using IdentityServer4 for your applications, you can install this module and remove the OpenIddict module. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../cli) command) to develop your custom module. +You don't need this module when you are using the OpenIddict module. If an existing application must keep using IdentityServer4, install the corresponding IdentityServer packages and remove the OpenIddict modules. You can use the released packages or include the module source code in your solution (see the `get-source` [CLI](../cli) command) to customize it. ### The Source Code @@ -49,6 +51,7 @@ public override void PreConfigureServices(ServiceConfigurationContext context) * `UpdateAbpClaimTypes` (default: true): Updates `AbpClaimTypes` to be compatible with identity server claims. * `IntegrateToAspNetIdentity` (default: true): Integrate to ASP.NET Identity. * `AddDeveloperSigningCredential` (default: true): Set false to suppress AddDeveloperSigningCredential() call on the IIdentityServerBuilder. +* `AddIdentityServerCookieAuthentication` (default: true): Adds IdentityServer's default cookie authentication handlers. Set it to `false` when the host registers and configures these handlers itself. `IIdentityServerBuilder` can be configured in `PreConfigureServices` method of your Identity Server [module](../framework/architecture/modularity/basics.md). Example: @@ -62,6 +65,61 @@ public override void PreConfigureServices(ServiceConfigurationContext context) } ```` +### AbpClaimsServiceOptions + +`AbpClaimsServiceOptions.RequestedClaims` adds claim types to the set requested from the profile service while IdentityServer creates tokens. The module adds ABP's tenant and edition claim types by default. You can append application-specific claim types in `ConfigureServices`: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.RequestedClaims.Add("department_id"); + }); +} +```` + +The profile service must also issue the claim for the current user; adding its name to this list does not create the claim value. + +### TokenCleanupOptions + +The module registers a background worker that removes expired persisted grants and device-flow codes. Configure it in `ConfigureServices`: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.IsCleanupEnabled = true; + options.CleanupPeriod = 3_600_000; + }); +} +```` + +* `IsCleanupEnabled` (default: `true`) controls whether the worker is registered. The global [background worker](../framework/infrastructure/background-workers/index.md) switch must also be enabled for it to run. +* `CleanupPeriod` (default: `3,600,000` milliseconds) sets the interval between cleanup passes. + +The worker uses the distributed lock named `TokenCleanupBackgroundWorker`, so only the instance that acquires the lock performs a cleanup pass. `CleanupBatchSize` and `CleanupLoopCount` are obsolete and are no longer used by the cleanup service. + +### Wildcard Subdomains for Client URLs + +IdentityServer normally requires exact redirect URI and CORS origin matches. For a multi-tenant application that uses subdomains, the module provides replacement validators for client values containing a `{0}` placeholder, such as `https://{0}.mydomain.com/signin-oidc`: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddAbpStrictRedirectUriValidator(); + context.Services.AddAbpClientConfigurationValidator(); + context.Services.AddAbpWildcardSubdomainCorsPolicyService(); +} +```` + +Register all three services when both redirect URLs and CORS origins use the placeholder. Keep the scheme, host suffix and port as restrictive as possible; these validators expand the configured client URL boundary. + +`AbpStrictRedirectUriValidator` also accepts the placeholder-free form when the configured URL with `{0}.` removed contains the requested URI. For example, configuring `http://{0}.ng.abp.io/index.html` also accepts `http://ng.abp.io`. This fallback can accept a requested URI with a shorter path than the configured value. Do not register the wildcard validator for clients that require exact redirect-path matching; keep IdentityServer's default strict validator and enumerate their exact redirect URIs instead. + +`AbpWildcardSubdomainCorsPolicyService` has equivalent placeholder-free behavior for origins: configuring `https://{0}.abp.io` also accepts the base origin `https://abp.io`. CORS origins have no path component, so constrain the scheme, host suffix and port. + ## Internals ### Domain Layer @@ -73,10 +131,18 @@ public override void PreConfigureServices(ServiceConfigurationContext context) API Resources are needed for allowing clients to request access tokens. * `ApiResource` (aggregate root): Represents an API resource in the system. - * `ApiSecret` (collection): secrets of the API resource. - * `ApiScope` (collection): scopes of the API resource. + * `ApiResourceSecret` (collection): secrets of the API resource. + * `ApiResourceScope` (collection): scope names associated with the API resource. * `ApiResourceClaim` (collection): claims of the API resource. +##### ApiScope + +API scopes model the scopes that clients can request independently from API resources. + +* `ApiScope` (aggregate root): Represents an API scope. + * `ApiScopeClaim` (collection): Claims included for the scope. + * `ApiScopeProperty` (collection): Custom properties of the scope. + ##### Client Clients represent applications that can request tokens from your Identity Server. @@ -98,25 +164,31 @@ Persisted Grants stores AuthorizationCodes, RefreshTokens and UserConsent. * `PersistedGrant` (aggregate root): Represents PersistedGrant for identity server. +##### DeviceFlowCodes + +* `DeviceFlowCodes` (aggregate root): Stores the user and device codes and serialized data used by the device authorization flow until they expire. + ##### IdentityResource Identity resources are data like user ID, name, or email address of a user. -* `IdentityResource` (aggregate root): Represents and Identity Server identity resource. - * `IdentityClaim` (collection): Claims of identity resource. +* `IdentityResource` (aggregate root): Represents an Identity Server identity resource. + * `IdentityResourceClaim` (collection): Claims of the identity resource. #### Repositories Following custom repositories are defined for this module: * `IApiResourceRepository` +* `IApiScopeRepository` * `IClientRepository` +* `IDeviceFlowCodesRepository` * `IPersistentGrantRepository` * `IIdentityResourceRepository` #### Domain Services -This module doesn't contain any domain service but overrides the services below; +The module integrates the following IdentityServer services: * `AbpProfileService` (Used when `AbpIdentityServerBuilderOptions.IntegrateToAspNetIdentity` is true) * `AbpClaimsService` @@ -128,12 +200,7 @@ This module doesn't define any settings. ### Application Layer -#### Application Services - -* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. -* `IdentityServerClaimTypeAppService` (implement `IIdentityServerClaimTypeAppService`): Used to get list of claims. -* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. -* `IdentityResourceAppService` (implements `IIdentityResourceAppService`): Implements the use cases of the Identity resource management UI. +The open-source module doesn't provide application services or HTTP APIs for administration. The [Identity Server Pro module](identity-server-pro.md) provides the management application and user interfaces. ### Database Providers @@ -149,15 +216,22 @@ This module uses `AbpIdentityServer` for the connection string name. If you don' See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details. +IdentityServer configuration is host data. The built-in EF Core and MongoDB contexts ignore the current tenant, and `ConfigureIdentityServer()` skips the model when an EF Core database is configured as tenant-only. Keep the IdentityServer tables or collections in the host/shared database when tenants use separate databases. + +The EF Core and MongoDB provider modules replace IdentityServer's in-memory stores with database-backed stores. If no persistence provider registers a client store, resource store, persisted-grant store or device-flow store, the domain module falls back to values from the `IdentityServer:Clients`, `IdentityServer:ApiResources` and `IdentityServer:IdentityResources` configuration sections and to in-memory grant/device stores. Do not rely on these fallback stores for production persistence. + #### Entity Framework Core ##### Tables * **IdentityServerApiResources** - * IdentityServerApiSecrets - * IdentityServerApiScopes - * IdentityServerApiScopeClaims - * IdentityServerApiClaims + * IdentityServerApiResourceSecrets + * IdentityServerApiResourceScopes + * IdentityServerApiResourceClaims + * IdentityServerApiResourceProperties +* **IdentityServerApiScopes** + * IdentityServerApiScopeClaims + * IdentityServerApiScopeProperties * **IdentityServerClients** * IdentityServerClientScopes * IdentityServerClientSecrets @@ -169,14 +243,26 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do * IdentityServerClientClaims * IdentityServerClientProperties * **IdentityServerPersistedGrants** +* **IdentityServerDeviceFlowCodes** * **IdentityServerIdentityResources** - * IdentityServerIdentityClaims + * IdentityServerIdentityResourceClaims + * IdentityServerIdentityResourceProperties #### MongoDB ##### Collections * **IdentityServerApiResources** +* **IdentityServerApiScopes** * **IdentityServerClients** * **IdentityServerPersistedGrants** -* **IdentityServerIdentityResources** \ No newline at end of file +* **IdentityServerDeviceFlowCodes** +* **IdentityServerIdentityResources** + +## Relations to Permission Management + +The optional `Volo.Abp.PermissionManagement.Domain.IdentityServer` integration lets applications grant permissions to a client by using the `GetForClientAsync`, `GetAllForClientAsync` and `SetForClientAsync` extensions on `IPermissionManager` and `IResourcePermissionManager`. Client permission values are stored on the host side. When a client is deleted, the integration removes both its ordinary and resource permission grants through the client's distributed deletion event. + +## Entity Extensions + +The module's [module entity extension](../framework/architecture/modularity/extending/module-entity-extensions.md) API supports the `Client`, `ApiResource` and `IdentityResource` aggregate roots. Configure these extensions before application startup, and create an EF Core migration when an extra property is mapped to a database column. diff --git a/docs/en/release-info/migration-guides/identityserver-to-openiddict.md b/docs/en/release-info/migration-guides/identityserver-to-openiddict.md index 2770722a51..b44f1c4b55 100644 --- a/docs/en/release-info/migration-guides/identityserver-to-openiddict.md +++ b/docs/en/release-info/migration-guides/identityserver-to-openiddict.md @@ -7,12 +7,12 @@ # Migration Identity Server to OpenIddict Guide -This document explains how to migrate to [OpenIddict](https://github.com/openiddict/openiddict-core) from Identity Server. From now on the ABP startup templates uses `OpenIddict` as the auth server by default since version v6.0.0. +This document explains how to migrate an application from IdentityServer4 to [OpenIddict](https://github.com/openiddict/openiddict-core). ABP startup templates have used OpenIddict as the authentication server by default since v6.0.0. -## History -We are not removing Identity Server packages and we will continue to release new versions of Identity Server related NuGet/NPM packages. That means you won't have an issue while upgrading to v6.0 when the stable version releases. We will continue to fix bugs in our packages for a while. ABP 7.0 will be based on .NET 7. If Identity Server continues to work with .NET 7, we will also continue to ship NuGet packages for our IDS integration. +> The checklist below describes the v6.0 transition. For a layer-by-layer migration, use the [IdentityServer to OpenIddict step-by-step guide](openiddict-step-by-step.md) and apply the package version that matches the ABP version of your application. -On the other hand, Identity Server ends support for the open-source Identity Server in the end of 2022. The Identity Server team has decided to move to Duende IDS and ABP will not be migrated to the commercial Duende IDS. You can see the Duende Identity Server announcement from [this link](https://blog.duendesoftware.com/posts/20220111_fair_trade). +## History +IdentityServer4 is archived and no longer maintained by its owners. ABP did not migrate its integration to the commercial Duende IdentityServer product. The ABP IdentityServer integration packages are still shipped for existing applications, while new applications use OpenIddict. See the [Duende IdentityServer announcement](https://blog.duendesoftware.com/posts/20220111_fair_trade) for the background. ## OpenIddict Migration Steps @@ -21,7 +21,7 @@ On the other hand, Identity Server ends support for the open-source Identity Ser * Replace all `IdentityServer` modules with corresponding `OpenIddict` modules. eg `AbpIdentityServerDomainModule` to `AbpOpenIddictDomainModule`, `AbpAccountWebIdentityServerModule` to `AbpAccountWebOpenIddictModule`. * Rename the `ConfigureIdentityServer` to `ConfigureOpenIddict` in your `ProjectNameDbContext` class. * Remove the `UseIdentityServer` and add `UseAbpOpenIddictValidation` after `UseAuthentication`. -* Add follow code to your startup module. +* Add the following code to your startup module. ```cs public override void PreConfigureServices(ServiceConfigurationContext context) @@ -38,7 +38,7 @@ public override void PreConfigureServices(ServiceConfigurationContext context) } ``` -* If your project is not separate AuthServer please also add `ForwardIdentityAuthenticationForBearer` +* If your project does not have a separate AuthServer, also add `ForwardIdentityAuthenticationForBearer`. ```cs private void ConfigureAuthentication(ServiceConfigurationContext context) @@ -48,9 +48,9 @@ private void ConfigureAuthentication(ServiceConfigurationContext context) ``` * Remove the `IdentityServerDataSeedContributor` from the `Domain` project. -* Create a new version of the project, with the same name as your existing project. -* Copy the `ProjectName.Domain\OpenIddict\OpenIddictDataSeedContributor.cs` of new project into your project and update `appsettings.json` base on `ProjectName.DbMigrator\appsettings.json`, Be careful to change the port number. -* Copy the `Index.cshtml.cs` and `Index.cs` of new project to your project if you're using `IClientRepository` in `IndexModel`. +* Generate a temporary project with the same name and architecture as the existing project so you can compare the current OpenIddict setup. +* Copy the generated `ProjectName.Domain\OpenIddict\OpenIddictDataSeedContributor.cs` into your project and update `appsettings.json` based on `ProjectName.DbMigrator\appsettings.json`. Adjust the ports and client URLs for your application. +* Copy the generated `Index.cshtml.cs` and `Index.cshtml` files into your project if your `IndexModel` still uses `IClientRepository`. * Update the scope name from `role` to `roles` in `AddAbpOpenIdConnect` method. * Remove `options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` from `HttpApi.Host` project. * AuthServer no longer requires `JWT bearer authentication`. Please remove it. eg `AddJwtBearer` and `UseJwtTokenMiddleware`. diff --git a/docs/en/release-info/migration-guides/identityserver4-step-by-step.md b/docs/en/release-info/migration-guides/identityserver4-step-by-step.md index 844d5ae071..da617cd237 100644 --- a/docs/en/release-info/migration-guides/identityserver4-step-by-step.md +++ b/docs/en/release-info/migration-guides/identityserver4-step-by-step.md @@ -7,11 +7,13 @@ # Migrating from OpenIddict to IdentityServer4 Step by Step Guide -ABP startup templates use `OpenIddict` OpenID provider from v6.0.0 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is the default OpenID provider library for ABP templates since v6.0, you may want to keep using [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) library, even it is **archived and no longer maintained by the owners**. ABP doesn't provide support for newer versions of IdentityServer. This guide provides layer-by-layer guidance for migrating your existing [OpenIddict](https://github.com/openiddict/openiddict-core) application to IdentityServer4. +ABP startup templates use the `OpenIddict` OpenID provider from v6.0.0 by default, and `IdentityServer` projects were renamed to `AuthServer` in tiered/separated solutions. This guide provides the v6.0-era, layer-by-layer steps for migrating an existing [OpenIddict](https://github.com/openiddict/openiddict-core) application to IdentityServer4. + +> IdentityServer4 is archived and no longer maintained by its owners. ABP doesn't support newer IdentityServer or Duende IdentityServer versions through this module. Use this guide only when an existing system has a compatibility requirement that prevents migration to OpenIddict; new applications should remain on OpenIddict. ## IdentityServer4 Migration Steps -Use the `abp update` command to update your existing application. See [Upgrading docs](../upgrading.md) for more info. Apply required migrations by following the [Migration Guides](../migration-guides) based on your application version. +These steps target an application that is already on ABP 6.0.x. Keep every `Volo.Abp.*` package on the exact 6.0.x patch version used by the application. Complete any version update separately with the CLI and migration guides for that release line before applying these provider-replacement steps. ### Domain.Shared Layer @@ -138,8 +140,6 @@ typeof(AbpIdentityServerEntityFrameworkCoreModule), ```csharp using Volo.Abp.IdentityServer.EntityFrameworkCore; ... - using Volo.Abp.OpenIddict.EntityFrameworkCore; - ... protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); @@ -150,7 +150,7 @@ typeof(AbpIdentityServerEntityFrameworkCoreModule), builder.ConfigureIdentityServer(); ``` -> Not: You need to create new migration after updating the fluent api. Navigate to *EntityFrameworkCore* folder and add a new migration. Ex, `dotnet ef migrations add Updated_To_IdentityServer ` +> Note: Create a new migration after updating the fluent API. Navigate to the *EntityFrameworkCore* folder and run, for example, `dotnet ef migrations add Updated_To_IdentityServer`. ### MongoDB Layer @@ -186,14 +186,14 @@ typeof(AbpIdentityServerMongoDbModule), ### DbMigrator Project -- In `appsettings.json` **replace OpenIddict section with IdentityServer** since IdentityServerDataSeeder will be using these information for initial data seeding: +- In `appsettings.json` **replace the OpenIddict section with IdentityServer** because IdentityServerDataSeeder uses this configuration for initial data seeding. Rename the `Applications` property to `Clients` and preserve its existing child entries. The minimal valid structure is: ```json - "IdentityServer": { // Rename OpenIddict to IdentityServer - "Clients ": { // Rename Applications to Clients - ... - } + { + "IdentityServer": { + "Clients": {} } + } ``` @@ -223,7 +223,7 @@ typeof(AbpIdentityServerMongoDbModule), ### UI Layer -You can follow the migrations guides from IdentityServer to OpenIddict in **reverse order** to update your UIs. You can also check the source-code for [Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml) and [Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml.cs) files for **AuthServer** project. +You can follow the migration guides from IdentityServer to OpenIddict in reverse order to update your UIs. You can also check the sample source for [Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml.cs) and [Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/OpenId2Ids/src/OpenId2Ids.AuthServer/Pages/Index.cshtml) in the **AuthServer** project. - [Angular UI Migration](openiddict-angular.md) - [MVC/Razor UI Migration](openiddict-mvc.md) diff --git a/docs/en/solution-templates/layered-web-application/deployment/identityserver-deployment.md b/docs/en/solution-templates/layered-web-application/deployment/identityserver-deployment.md index db6975e04f..48f62b9c70 100644 --- a/docs/en/solution-templates/layered-web-application/deployment/identityserver-deployment.md +++ b/docs/en/solution-templates/layered-web-application/deployment/identityserver-deployment.md @@ -7,19 +7,21 @@ # IdentityServer Deployment -IdentityServer configuration may be different based on deployment configurations. Basically, you need update identityserver client related data and update your hosting preferences based on your deployment environment. +> This page applies only to applications that still use the legacy [IdentityServer module](../../../modules/identity-server.md). Current startup templates use OpenIddict; see [Configuring OpenIddict](../../../deployment/configuring-openIddict.md) for current applications. -## Update Cors Origins +IdentityServer configuration changes between deployment environments. Update the IdentityServer client data and the host settings for the deployed URLs before releasing the application. -Cors origins configuration for **gateways**, **microservices** swagger authorization and **Angular/Blazor** (web assembly) must be updated for deployment. This can be found under **App** configuration in *appsettings.json* +## Update CORS Origins + +CORS origin configuration for **gateways**, **microservices** Swagger authorization and **Angular/Blazor WebAssembly** applications must be updated for deployment. It is under the **App** section in *appsettings.json*. ```json "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44325,https://localhost:44353,https://localhost:44367,https://localhost:44388,https://localhost:44381,https://localhost:44361", ``` -## Update Redirect Allowed Urls +## Update Redirect Allowed URLs -This configuration must be done if **Angular** or **Blazor** (web assembly) is used as back-office web application. It is found under **App** configuration in appsettings.json +Update this configuration when an **Angular** or **Blazor WebAssembly** application is used as the back-office application. It is under the **App** section in *appsettings.json*. ```json "RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307" @@ -29,90 +31,50 @@ This configuration must be done if **Angular** or **Blazor** (web assembly) is u `IdentityServerDataSeedContributor` uses **IdentityServer.Clients** section of `appsettings.json` for `ClientId`, `RedirectUri`, `PostLogoutRedirectUri`, `CorsOrigins`. -Update DbMigrator project `appsettings.json` **IdentityServer.Clients.RootUrls** with production values: +Update the DbMigrator project's `appsettings.json` **IdentityServer.Clients.RootUrls** values for production: ![db-migrator-appsettings](../../../images/db-migrator-appsettings.png) Or, manually add production values to `IdentityServerClientRedirectUris`, `IdentityServerClientPostLogoutRedirectUris`, `IdentityServerClientCorsOrigins` tables in your database. -> If you are using microservice template on-the-fly migration and not using dbmigrator project, update **IdentityService** appsettings. +> If you use the microservice template's on-the-fly migration instead of a DbMigrator project, update the **IdentityService** settings. -Eventually, you shouldn't have `localhost` related data. +Remove all `localhost` values from the production client data. ## Update IdentityServer -You need to update token signing certificate and identityserver midware based on your hosting environment. +Update the token-signing certificate and IdentityServer middleware for your hosting environment. ### Signing Certificate -Default development environment uses [developer signing certificates option](https://github.com/abpframework/abp/blob/dev/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderOptions.cs#L29). Using developer signing certificates may cause *IDX10501: Signature validation failed* error on production. +The default `AbpIdentityServerBuilderOptions.AddDeveloperSigningCredential` value enables a developer signing credential. Don't use a developer signing credential in production; configure a real certificate through `IIdentityServerBuilder` pre-configuration. Otherwise, signing keys can change between deployments and cause errors such as *IDX10501: Signature validation failed*. -Update **IdentityServerModule** with using real certificate on `IIdentityServerBuilder` pre-configuration. +Update **IdentityServerModule** to use a real certificate in `IIdentityServerBuilder` pre-configuration. ![idsrv-certificate](../../../images/idsrv-certificate.png) -You can also [create self-signed certificate](https://docs.abp.io/en/commercial/5.0/startup-templates/microservice/tye-integration#create-developer-certificates) and use it. - -> If you are using self signed certificate, do not forget to set the certificate (.pfx file) as `EmbeddedResource` and set `CopyToOutputDirectory`. File needs to exist physically. +Load the production certificate from your deployment platform's certificate or secret store. Don't embed the production private key in the application assembly or commit it to the source repository. ### Use HTTPS -Update **IdentityServerModule** to [enfcore https](https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-6.0&tabs=visual-studio). Add `UseHsts` to add hsts headers to clients, add `UseHttpsRedirection` to redirect http requests to https. +Update **IdentityServerModule** to [enforce HTTPS](https://learn.microsoft.com/aspnet/core/security/enforcing-ssl). Add `UseHsts` to send HSTS headers and `UseHttpsRedirection` to redirect HTTP requests to HTTPS. ![use-https](../../../images/use-https.png) ### Behind Load Balancer -To redirect http requests to https from load balancer, update `OnApplicationInitialization` method of the **IdentityServerModule** with the midware below: +When TLS terminates at a reverse proxy or load balancer, use ASP.NET Core Forwarded Headers Middleware so IdentityServer receives the original scheme and host. Follow the [Forwarded Headers](../../../deployment/forwarded-headers.md) guide and the [ASP.NET Core proxy and load balancer guidance](https://learn.microsoft.com/aspnet/core/host-and-deploy/proxy-load-balancer). -```csharp -app.Use((httpContext, next) => -{ - httpContext.Request.Scheme = "https"; - return next(); -}); -``` +Configure the proxy addresses or networks in `ForwardedHeadersOptions.KnownProxies` or `KnownNetworks`, and call `UseForwardedHeaders` before authentication, IdentityServer, HTTPS redirection and HSTS middleware. If you enable `X-Forwarded-Host`, restrict `AllowedHosts` and configure the proxy to overwrite incoming forwarded headers. + +Don't set `HttpContext.Request.Scheme` unconditionally. Don't derive the IdentityServer origin from a custom request header unless the application first verifies that the request came through a trusted proxy; internet clients can forge ordinary request headers. ### Kubernetes -A common scenario is running applications in kubernetes environment. While IdentityServer needs to face internet on https, internal requests can be done using http. +A common scenario is running applications in Kubernetes. While IdentityServer must be exposed to the internet over HTTPS, internal requests can use HTTP. ![idsrv-k8s](../../../images/idsrv-k8s.png) -**HttpApi.Host** and **Web** applications authority should be set to http since token validations will done using http request. - -![api-resource-internal-idsrv](../../../images/api-resource-internal-idsrv.png) - -> You can use different appsettings files like *appsettings.production.json* to override these values or directly override environment values from kubernetes. +Keep the externally advertised IdentityServer authority and origin on the public HTTPS URL. If services use cluster-local routing, configure internal DNS or the ingress so that the public authority resolves through the trusted internal route without changing the issuer or accepting a client-controlled origin. -To isolate internal identityserver requests from external network (internet), append extra header instead of overwriting. -For ingress, you can use `nginx.ingress.kubernetes.io/configuration-snippet`: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: myidentityserver-ingress - annotations: - nginx.ingress.kubernetes.io/rewrite-target: / - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - nginx.ingress.kubernetes.io/proxy-buffer-size: "32k" - nginx.ingress.kubernetes.io/proxy-buffers-number: "8" - nginx.ingress.kubernetes.io/configuration-snippet: | - more_set_input_headers "from-ingress: true"; -spec: -``` - -You need to set the IdentityServer origin based on header. Update `OnApplicationInitialization` method of the **IdentityServerModule** with the midware below: - -```csharp -app.Use(async (ctx, next) => -{ - if (ctx.Request.Headers.ContainsKey("from-ingress")) - { - ctx.SetIdentityServerOrigin("https://myidentityserver.com"); - } - - await next(); -}); -``` +> You can use environment-specific files such as *appsettings.Production.json* or environment variables to override these values in Kubernetes. From e5836f6b6f52a040562061339ff467a65a3073e3 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 14/41] Add standalone Blogging module documentation --- docs/en/modules/blogging.md | 172 ++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 docs/en/modules/blogging.md diff --git a/docs/en/modules/blogging.md b/docs/en/modules/blogging.md new file mode 100644 index 0000000000..39492a696c --- /dev/null +++ b/docs/en/modules/blogging.md @@ -0,0 +1,172 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to install and configure the standalone ABP Blogging module, including its MVC UI, routes, permissions, files and database providers." +} +``` + +# Blogging Module + +The Blogging module is a free and open-source application module for creating one or more blogs. It provides an MVC / Razor Pages user interface, application services, HTTP APIs and Entity Framework Core and MongoDB integrations for blogs, posts, tags, comments and member profiles. + +> This page documents the standalone `Volo.Blogging` module. [CMS Kit: Blogging](cms-kit/blogging.md) is a different feature family with different entities, configuration and UI. + +## How to Install + +Use the ABP CLI to add the module to an existing solution: + +```bash +abp add-module Volo.Blogging +``` + +The command adds the module packages and dependencies to the compatible projects in your solution. Apply the generated database migration after adding the module. + +### The Source Code + +The source code of this module is available in the [ABP repository](https://github.com/abpframework/abp/tree/dev/modules/blogging). It is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can use and customize it. + +## User Interface and Content Workflow + +The module provides two MVC / Razor Pages surfaces: + +* The public site lists blogs, posts and popular tags, renders post content as Markdown, displays member profiles and allows authenticated users to add comments. +* The administration page manages blogs. Post creation and editing are available from the public blog UI to users with the corresponding permissions. + +When the application has one blog, the blog index redirects directly to that blog. With multiple blogs, the index displays the available blogs. + +Creating a post makes it available to the public list and reading APIs immediately. The standalone module does not add a draft, review or scheduled-publication state. If a post URL is already used in the same blog, the application service appends a generated suffix and returns the resulting URL. + +Tags are entered with a post. The application service normalizes tag names to lowercase, removes duplicates and maintains their usage counts. + +### Member Profiles + +The module keeps a local `BlogUser` record for post and comment authors. It uses ABP's [user lookup and synchronization](identity/user-synchronization.md) infrastructure to obtain Identity user data. The public member page lists an active user's posts and profile information; the current user can edit the Blogging-specific fields on their own profile. + +## Permissions + +The administration menu is visible with the `Blogging.Blog.Management` permission. Blog operations use separate child permissions: + +* `Blogging.Blog.Create` +* `Blogging.Blog.Update` +* `Blogging.Blog.Delete` +* `Blogging.Blog.ClearCache` + +Post creation, update and deletion require `Blogging.Post.Create`, `Blogging.Post.Update` and `Blogging.Post.Delete`, respectively. + +Any authenticated user can create a comment. A comment can be updated or deleted by its creator or by a user with `Blogging.Comment.Update` or `Blogging.Comment.Delete`. + +See the [Authorization](../framework/fundamentals/authorization/index.md) documentation to learn how to grant permissions to roles and users. + +## Routing + +`BloggingUrlOptions.RoutePrefix` controls the public URL prefix. Its default value produces URLs under `/blog/`. The following example moves the public blog under `/articles/` and enables single-blog mode for the blog whose short name is `engineering`: + +```csharp +Configure(options => +{ + options.RoutePrefix = "articles"; + options.SingleBlogMode.Enabled = true; + options.SingleBlogMode.BlogName = "engineering"; +}); +``` + +Single-blog mode removes the blog short-name segment from post URLs. If the application contains multiple blogs, set `SingleBlogMode.BlogName` to the short name of the blog to expose. If it contains exactly one blog, the module can select it without setting `BlogName`. + +Set `RoutePrefix` to an empty string to serve the blog from `/`. In this mode the route constraint uses `IgnoredPaths` to avoid capturing other top-level application routes. The module already adds its framework endpoints, bundle folder and member route; add application-specific top-level paths when needed: + +```csharp +Configure(options => +{ + options.RoutePrefix = ""; + options.IgnoredPaths.Add("health"); +}); +``` + +## Post Images + +Post images are saved through the [BLOB Storing](../framework/infrastructure/blob-storing) system in the `blogging-files` container. Configure a BLOB provider for this container as you would for any other typed container. + +The upload service accepts JPEG, PNG, GIF and BMP images. The maximum file size is 5 MiB by default. `BloggingWebConsts.FileUploading.MaxFileSize` is a process-wide static value, so set it once during application startup, before the application begins accepting uploads: + +```csharp +BloggingWebConsts.FileUploading.MaxFileSize = 10 * 1024 * 1024; +``` + +## Social Media Metadata + +The post page emits Twitter card metadata. Configure the site handle with `BloggingTwitterOptions`: + +```csharp +Configure(options => +{ + options.Site = "@myblog"; +}); +``` + +## Post List Cache + +The time-ordered post list is cached per blog for one hour. Creating, updating or deleting posts through `IPostAppService` invalidates that cache within the current unit of work. The blog administration page also provides a **Clear Cache** action to users with the `Blogging.Blog.ClearCache` permission. + +If custom code changes posts directly through `IPostRepository`, publish the module's `PostChangedEvent` with the affected blog ID. The built-in event handler then removes that blog's cached list as part of the current unit of work. + +## Internals + +### Domain Layer + +The main aggregates are: + +* `Blog`: A named blog identified in public URLs by its short name. +* `Post`: Markdown content, cover image, URL, tags and read count for a blog. +* `Comment`: A comment or direct reply attached to a post. +* `Tag`: A normalized tag and its usage count within a blog. +* `BlogUser`: The module-local representation of an Identity user and Blogging profile. + +### Application Layer + +The public application-service contracts are `IBlogAppService`, `IPostAppService`, `ICommentAppService`, `ITagAppService` and `IFileAppService`. `IBlogManagementAppService` provides blog administration operations. + +The HTTP API uses the `Blogging` remote-service name for public operations and `BloggingAdmin` for administration operations. Add `BloggingHttpApiClientModule` or `BloggingAdminHttpApiClientModule` to a client application when these services run remotely. + +### Database Providers + +#### Common + +The module uses `Blogging` as its connection string name and falls back to `Default` when that connection is not configured. See the [Connection Strings](../framework/fundamentals/connection-strings.md) documentation. + +Tables and collections use the `Blg` prefix by default. Set `AbpBloggingDbProperties.DbTablePrefix` and, for providers that support schemas, `AbpBloggingDbProperties.DbSchema` before the database model is created: + +```csharp +AbpBloggingDbProperties.DbTablePrefix = "MyBlog"; +AbpBloggingDbProperties.DbSchema = "blogging"; +``` + +#### Entity Framework Core + +The Entity Framework Core provider uses these tables: + +* `BlgUsers` +* `BlgBlogs` +* `BlgPosts` +* `BlgComments` +* `BlgTags` +* `BlgPostTags` + +Call `ConfigureBlogging()` from your migration DbContext when integrating the module manually. + +#### MongoDB + +The MongoDB provider uses these collections: + +* `BlgUsers` +* `BlgBlogs` +* `BlgPosts` +* `BlgComments` +* `BlgTags` + +Post-tag links are stored with the post documents, so MongoDB does not use a separate `BlgPostTags` collection. + +## See Also + +* [CMS Kit: Blogging](cms-kit/blogging.md) +* [BLOB Storing](../framework/infrastructure/blob-storing) +* [User Lookup and Synchronization](identity/user-synchronization.md) From dc9cf07055e1a910cffb991f0cb6789f1d81852d Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 15/41] Modernize Docs module documentation --- docs/en/modules/docs.md | 551 ++++++++++++---------------------------- 1 file changed, 160 insertions(+), 391 deletions(-) diff --git a/docs/en/modules/docs.md b/docs/en/modules/docs.md index bfb959f128..a9307733e7 100644 --- a/docs/en/modules/docs.md +++ b/docs/en/modules/docs.md @@ -7,424 +7,132 @@ # Docs Module -## What is Docs Module? +## What is the Docs Module? -Docs module is an application module for ABP. It simplifies software documentation. This module is free and open-source. +The Docs module is a free and open-source application module for publishing software documentation in an ABP application. ### Integration -Currently docs module provides you to store your docs both on GitHub and file system. +The module can load documentation from GitHub or the local file system. You can also add a custom document source. ### Hosting -Docs module is an application module and does not offer any hosting solution. You can host your docs on-premise or on cloud. +The module renders documentation inside your application. It does not provide a separate hosting service, so the application can be hosted on-premises or in the cloud. ### Versioning -When you use GitHub to store your docs, Docs Module supports versioning. If you have multiple versions for your docs, there will be a combo-box on the UI to switch between versions. If you choose file system to store your docs, it does not support multiple versions. +GitHub sources can expose releases or branches as document versions. The UI displays a version selector when multiple versions are available. File-system sources expose a single internal version. -[The documents](../modules) for ABP is also using this module. +The [ABP documentation](../) also uses this module. > Docs module follows the [module architecture best practices](../framework/architecture/best-practices/module-architecture.md) guide. ## Installation -This document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider. - -### 1- Creating an application - -If you do not have an existing ABP project, you can either [generate a CLI command from the get started page of the abp.io website](../get-started) and runs it or run the command below: - -```bash -abp new Acme.MyProject -``` - -### 2- Running The Empty Application - -After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**. - -![Create a new project](../images/docs-module_solution-explorer.png) - -The database connection string is located in `appsettings.json` of your `Acme.MyProject.Web` project. If you have a different database configuration, change the connection string. - -```json -{ - "ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProject;Trusted_Connection=True" - } -} -``` - -Run `Acme.MyProject.DbMigrator` project, it will be responsible for applying database migration and seed data. The database `MyProject` will be created in your database server. - -Now an empty ABP project has been created! You can now run your project and see the empty website. - -To login your website enter `admin` as the username and `1q2w3E*` as the password. - -### 3- Installation Module - -Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project. - -#### 3.1- Use ABP CLI - -It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: +The Docs module supports Entity Framework Core and MongoDB. From the solution directory, use the ABP CLI to add the module packages, module dependencies, client-side package and database integration that match your solution: ```bash abp add-module Volo.Docs ``` -#### 3.2- Manually install - -Or you can also manually install nuget package to each project: - -* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project. - - ```bash - dotnet add package Volo.Docs.Domain - ``` - -* Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project. - - ```bash - dotnet add package Volo.Docs.EntityFrameworkCore - ``` - -* Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project. - - ```bash - dotnet add package Volo.Docs.Application - ``` - -* Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project. - - ```bash - dotnet add package Volo.Docs.Web - ``` - -##### 3.2.1- Adding Module Dependencies - -An ABP module must declare `[DependsOn]` attribute if it has a dependency upon another module. Each module has to be added in`[DependsOn]` attribute to the relevant project. - -* Open `MyProjectDomainModule.cs`and add `typeof(DocsDomainModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsDomainModule), - typeof(AbpIdentityDomainModule), - typeof(AbpAuditingModule), - typeof(BackgroundJobsDomainModule), - typeof(AbpAuditLoggingDomainModule) - )] - public class MyProjectDomainModule : AbpModule - { - //... - } - ``` - -* Open `MyProjectEntityFrameworkCoreModule.cs`and add `typeof(DocsEntityFrameworkCoreModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsEntityFrameworkCoreModule), - typeof(MyProjectDomainModule), - typeof(AbpIdentityEntityFrameworkCoreModule), - typeof(AbpPermissionManagementEntityFrameworkCoreModule), - typeof(AbpSettingManagementEntityFrameworkCoreModule), - typeof(AbpEntityFrameworkCoreSqlServerModule), - typeof(BackgroundJobsEntityFrameworkCoreModule), - typeof(AbpAuditLoggingEntityFrameworkCoreModule) - )] - public class MyProjectEntityFrameworkCoreModule : AbpModule - { - //... - } - ``` - -* Open `MyProjectApplicationModule.cs`and add `typeof(DocsApplicationModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsApplicationModule), - typeof(MyProjectDomainModule), - typeof(AbpIdentityApplicationModule))] - public class MyProjectApplicationModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.DefinitionProviders.Add(); - }); - } - } - ``` - -* Open `MyProjectWebModule.cs`and add `typeof(DocsWebModule)` as shown below; - - ```csharp - [DependsOn( - typeof(DocsWebModule), - typeof(MyProjectApplicationModule), - typeof(MyProjectEntityFrameworkCoreModule), - typeof(AbpAutofacModule), - typeof(AbpIdentityWebModule), - typeof(AbpAccountWebModule), - typeof(AbpAspNetCoreMvcUiBasicThemeModule) - )] - public class MyProjectWebModule : AbpModule - { - //... - } - ``` - -##### 3.2.2- Adding NPM Package - -Open `package.json` and add `@abp/docs": "^5.0.0` as shown below: - - ```json - { - "version": "1.0.0", - "name": "my-app", - "private": true, - "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0", - "@abp/docs": "^5.0.0" - } - } - ``` - -Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command: +For an Entity Framework Core solution, the command adds `builder.ConfigureDocs()` to the migrations `DbContext`, creates a migration and runs the database migrator. Use `--skip-db-migrations` when you want to manage that step yourself. MongoDB does not require an EF Core migration. -````bash -abp install-libs -```` +For a manual installation, add the Docs packages and module dependencies that correspond to each application layer. MVC/Razor Pages hosts also need the `@abp/docs` package. Keep every package on the same version as the rest of your ABP solution, then run `abp install-libs` in the web project. -### 4- Database Integration +### Database configuration -#### 4.1- Entity Framework Integration +The module uses the `Docs` connection string name and falls back to `Default` when a dedicated connection string is not configured. `AbpDocsDbProperties.DbTablePrefix` and `AbpDocsDbProperties.DbSchema` control the EF Core table names; the prefix also controls the MongoDB collection names. Set these static properties before the persistence model is configured. -If you choose Entity Framework as your database provider, you need to configure the Docs Module. To do this; +Both built-in persistence providers mark the Docs database context with `IgnoreMultiTenancy`. Projects, cached documents and generated PDF metadata are application-wide data and are not partitioned by the current tenant. Do not expose the administration permissions to tenant administrators unless this application-wide behavior is intended. -- Open `MyProjectMigrationsDbContext.cs` and add `builder.ConfigureDocs()` to the `OnModelCreating()`. +## Creating a Docs Project - ```csharp - public class MyProjectMigrationsDbContext : AbpDbContext - { - public MyProjectMigrationsDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); +After installation, users with the `Docs.Admin.Projects` permission can open **Administration → Documents → Projects**. The built-in administration UI creates and edits GitHub projects. `Docs.Admin.Projects.Create`, `.Update` and `.Delete` control the corresponding actions. `Docs.Admin.Documents` provides the cached-document administration screen. - /* Include modules to your migration db context */ +The main project fields are: - builder.ConfigurePermissionManagement(); - builder.ConfigureSettingManagement(); - builder.ConfigureBackgroundJobs(); - builder.ConfigureAuditLogging(); - builder.ConfigureIdentity(); - builder.ConfigureIdentityServer(); - builder.ConfigureFeatureManagement(); - builder.ConfigureTenantManagement(); - builder.ConfigureDocs(); //Add this line to configure the Docs Module +* **Name**: Display name of the project. +* **ShortName**: URL-friendly identifier. It is normalized to lowercase when the project is created and cannot be changed later. +* **Format**: The built-in web converter supports Markdown (`md`). Register a custom document converter before using another format. +* **DefaultDocumentName**: Initial document name. The default is `Index`. +* **NavigationDocumentName**: Navigation file name. The default is `docs-nav.json`. +* **ParametersDocumentName**: Scriban parameter file name. The default is `docs-params.json`. +* **MinimumVersion**: Oldest listed GitHub version. +* **MainWebsiteUrl**: Target of the project logo. +* **LatestVersionBranchName**: Branch used for the latest documentation. - /* Configure customizations for entities from the modules included */ +Deleting a project removes only the project record. Before deleting it, remove its cached documents through document administration, verify and remove its Elasticsearch entries when search is enabled, and delete every generated PDF through **Manage PDF Files** so the BLOB objects are deleted. The project delete operation does not perform these cleanup steps automatically. - builder.Entity(b => - { - b.ConfigureCustomUserProperties(); - }); +The public UI starts at `/documents`. You can change this route with `DocsUiOptions.RoutePrefix`, as shown in the [UI options](#ui-options) section. - /* Configure your own tables/entities inside the ConfigureQaDoc method */ - - builder.ConfigureMyProject(); - } - } - ``` +### GitHub source -* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore` as default project. Then write the below command to add the migration for Docs Module. +Set **GitHub Root URL** to a tree URL that contains the `{version}` placeholder and points to the directory above the language folders. For example: - ```csharp - add-migration Added_Docs_Module - ``` - - When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore\Migrations`. - - Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore` is still default project. +```text +https://github.com/abpframework/abp/tree/{version}/docs +``` - ```csharp - update-database - ``` +GitHub projects use releases as the version source by default. Select branches to list repository branches instead. In branch mode, **Version Branch Prefix** filters the branches and removes the prefix from displayed version names. **Latest Version Branch Name** must contain the real branch name, including the prefix when one is used. - Finally, you can check your database to see the newly created tables. For example you can see `DocsProjects` table must be added to your database. +The module builds the edit link from the GitHub tree URL and loads relative images and other resources from the same repository and version. An access token is optional for public repositories and is needed for private repositories or higher API limits. The token is stored in the project's extra properties; public project APIs remove it from their responses, but administrators can retrieve it. Protect the database and use a token with only the repository permissions that the Docs host needs. -### 5- Linking Docs Module +### File-system source -The default route for Docs module is; +The `FileSystem` source loads documents from a local root directory stored in the project's `Path` extra property. Its directory layout is the same as the GitHub source: -```txt -/Documents +```text +/docs-langs.json +// ``` -To add Docs module link to your application menu; +File-system projects always use the internal version `1.0.0` and do not provide a version list or an edit link. The source rejects document and resource paths outside the configured project root. -* Open `MyProjectMenuContributor.cs` and add the below line to the method `ConfigureMainMenuAsync()`. +The built-in project create/edit pages currently expose only GitHub fields. Create a file-system project through `IProjectAdminAppService`, a data seeder or another administration UI by setting `DocumentStoreType` to `FileSystem` and the `Path` extra property. - ```csharp - context.Menu.Items.Add(new ApplicationMenuItem("MyProject.Docs", l["Menu:Docs"], "/Documents")); - ``` +### Language configuration - Final look of **MyProjectMenuContributor.cs** - - ```csharp - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - var l = context.ServiceProvider.GetRequiredService>(); - - context.Menu.Items.Insert(0, new ApplicationMenuItem("MyProject.Home", l["Menu:Home"], "/")); - - context.Menu.Items.Add(new ApplicationMenuItem("MyProject.Docs", l["Menu:Docs"], "/Documents")); - } - ``` - -The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line - -```json -"Menu:Docs": "Documents" -``` - -Final look of **en.json** +Place `docs-langs.json` at the project root, outside the language directories: ```json { - "culture": "en", - "texts": { - "Menu:Home": "Home", - "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP. For more information, visit abp.io.", - "Menu:Docs": "Documents" - } + "languages": [ + { + "displayName": "English", + "code": "en", + "isDefault": true + }, + { + "displayName": "Türkçe", + "code": "tr", + "isDefault": false + } + ] } ``` -The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL. - -You will see a warning says; - -```txt -There are no projects yet! -``` - -As we have not added any projects yet, this warning is normal. - -### 6- Adding New Docs Project - -Open `DocsProjects` in your database, and insert a new record with the following field information; - -* **Name**: The display name of the document name which will be shown on the web page. -* **ShortName**: A short and URL friendly name that will be used in your docs URL. -* **Format**: The format of the document (for Markdown: `md`, for HTML: `html`) -* **DefaultDocumentName**: The document for the initial page. -* **NavigationDocumentName**: The document to be used for the navigation menu (Index). -* **MinimumVersion**: The minimum version to show the docs. Below version will not be listed. -* **DocumentStoreType**: The source of the documents (for GitHub:`GitHub`, for file system`FileSystem`) -* **ExtraProperties**: A serialized `JSON` that stores special configuration for the selected `DocumentStoreType`. -* **MainWebsiteUrl**: The URL when user clicks to the logo of the Docs module page. You can simply set as `/` to link to your website root address. -* **LatestVersionBranchName**: This is a config for GitHub. It's the branch name which to retrieve the docs. You can set it as `master`. - -#### Sample Project Record for "GitHub" - -You can use [ABP](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store. - -- Name: `ABP (GitHub)` - -- ShortName: `abp` - -- Format: `md` - -- DefaultDocumentName: `Index` - -- NavigationDocumentName: `docs-nav.json` - -- MinimumVersion: `` (no minimum version) - -- DocumentStoreType: `GitHub` - -- ExtraProperties: - - ```json - {"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""} - ``` - - Note that `GitHubAccessToken` is masked with `***`. It's a private token that you must get it from GitHub. See https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ - -- MainWebsiteUrl: `/` - -- LatestVersionBranchName: `dev` - -For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table: - -```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"","GitHubUserAgent":""}', N'/', N'dev', N'', N'12f21123e08e4f15bedbae0b2d939659') -``` - -Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string. - -Now you can run the application and navigate to `/Documents`. - -#### Sample Project Record for "FileSystem" - -You can use [ABP](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store. - -- Name: `ABP (FileSystem)` - -- ShortName: `abp` - -- Format: `md` - -- DefaultDocumentName: `Index` +When a GitHub source cannot load this file, it falls back to the language configured by `DocsGithubLanguageOptions.DefaultLanguage`, which is English by default. The file-system source requires a valid `docs-langs.json` file. -- NavigationDocumentName: `docs-nav.json` +The language list is cached for 24 hours using the project short name, not the requested version. Keep `docs-langs.json` consistent across all versions. Clear the project cache after changing the manifest; the first version requested after a clear repopulates the project-wide language cache. -- MinimumVersion: `` (no minimum version) +### Adding a custom document source -- DocumentStoreType: `FileSystem` +Implement `IDocumentSource`, register the implementation in dependency injection and map a unique source name to it: -- ExtraProperties: - - ```json - {"Path":"C:\\Github\\abp\\docs"} - ``` - - Note that `Path` must be replaced with your local docs directory. You can fetch the ABP's documents from https://github.com/abpframework/abp/tree/master/docs and copy to the directory `C:\\Github\\abp\\docs` to get it work. - -- MainWebsiteUrl: `/` - -- LatestVersionBranchName: `` - -For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table: +```csharp +context.Services.AddTransient(); -```mssql -INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'', N'12f21123e08e4f15bedbae0b2d939659') +Configure(options => +{ + options.Sources["MySource"] = typeof(MyDocumentSource); +}); ``` -Add one of the sample projects above and run the application. In the menu you will see `Documents` link, click the menu link to open the documents page. - -So far, we have created a new application from abp.io website and made it up and ready for Docs module. +Use the same source name as the project's `DocumentStoreType`. The factory resolves the mapped type from dependency injection when it loads documents, versions, resources and languages. -### 7- Creating a New Document +## Creating a New Document -In the sample Project records, you see that `Format` is specified as `md` which refers to [Mark Down](https://en.wikipedia.org/wiki/Markdown). You can see the mark down cheat sheet following the below link; - -https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet - -ABP Docs Module can render mark down to HTML. - -Now let's have a look a sample document in markdown format. +The built-in converter renders [Markdown](https://en.wikipedia.org/wiki/Markdown) documents as HTML. The following example demonstrates headings, links, images and code blocks: ~~~markdown # This is a header @@ -449,15 +157,13 @@ public class Person ``` ~~~ -As an example you can see ABP documentation: +You can also browse the [ABP documentation sources](https://github.com/abpframework/abp/tree/dev/docs/en) for complete examples. -[https://github.com/abpframework/abp/blob/master/docs/en/](https://github.com/abpframework/abp/blob/master/docs/en/) +### Conditional Sections with Scriban -#### Conditional sections feature (Using Scriban) +The Docs module uses [Scriban](https://scriban.github.io/docs/) to conditionally show or hide parts of a document. Create one parameter document for each language. It contains the available parameters, their values and their display names. -Docs module uses [Scriban](https://github.com/lunet-io/scriban/tree/master/doc) for conditionally show or hide some parts of a document. In order to use that feature, you have to create a JSON file as **Parameter document** per every language. It will contain all the key-values, as well as their display names. - -For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json): +For example, `en/docs-params.json` can contain: ```json { @@ -488,9 +194,9 @@ For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/ } ``` -Since not every single document in your projects may not have sections or may not need all of those parameters, you have to declare which of those parameters will be used for sectioning the document, as a JSON block anywhere on the document. +Each document declares the parameters it uses in a JSON block anywhere in the document. -For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md): +For example: ​```json //[doc-params] @@ -501,7 +207,7 @@ For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/bl } ​``` -This section will be automatically deleted during render. And f course, those key values must match with the ones in **Parameter document**. +This block is removed while the document is rendered. Its keys and values must match the parameter document. ![Interface](../images/docs-section-ui.png) @@ -538,13 +244,13 @@ You can also use variables in a text, adding **_Value** postfix to its key: This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. ``` -Also, **Document_Language_Code** and **Document_Version** keys are pre-defined if you want to get the language code or the version of the current document (This may be useful for creating links that redirects to another documentation system in another domain). +`Document_Language_Code` and `Document_Version` are predefined keys. They can be used, for example, to build links to another documentation system. ------ -**IMPORTANT NOTICE**: Scriban uses "{{" and "}}" for syntax. Therefore, you must use escape blocks if you are going to use those in your document (an Angular document, for example). See [Scriban docs](https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block) for more information. +> Scriban uses `{{` and `}}` as delimiters. Use the escape block described in the [Scriban language reference](https://scriban.github.io/docs/language/) when the document contains these delimiters as literal text, such as in an Angular example. -### 8- Creating the Navigation Document +## Creating the Navigation Document Navigation document is the main menu of the documents page. It is located on the left side of the page. It is a `JSON` file. Take a look at the below sample navigation document to understand the structure. @@ -596,38 +302,60 @@ Navigation document is the main menu of the documents page. It is located on the } ``` -The upper sample `JSON` file renders the below navigation menu as `HTML`. +The sample JSON file renders the navigation menu shown below. ![Navigation menu](../images/docs-module_download-sample-navigation-menu.png) -Finally a new Docs Module is added to your project which is feeded with GitHub. +Implement `INavigationTreePostProcessor` to modify the deserialized navigation tree before it is returned to the UI. The default implementation makes no changes, so a custom implementation can be registered by replacing that service. -## Full-Text Search(Elastic Search) +## Full-Text Search with Elasticsearch -The Docs module supports full-text search using Elastic Search. It is not enabled by default. You can configure `DocsElasticSearchOptions` to enable it. +Elasticsearch integration is disabled by default. Configure the Elasticsearch URL and enable `DocsElasticSearchOptions`: + +```json +{ + "ElasticSearch": { + "Url": "http://localhost:9200" + } +} +``` ```csharp Configure(options => { options.Enable = true; - options.IndexName = "your_index_name"; //default IndexName is abp_documents + options.IndexName = "docs"; + options.UseApiKeyAuthentication("key-id", "api-key"); }); ``` -The `Index` is automatically created after the application starts if the `Index` does not exist. +The default index name is `abp_documents`. Use `UseBasicAuthentication(username, password)` instead of `UseApiKeyAuthentication` when the Elasticsearch server uses basic authentication. + +The module creates the index during application initialization when it does not exist. Creating, updating or deleting a cached document updates the index. The administration UI can reindex one project or all projects from the documents already stored in the Docs database. -`DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`. -If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](../framework/fundamentals/dependency-injection.md) system. +`DefaultElasticClientProvider` creates the Elasticsearch client from these settings. Replace `IElasticClientProvider` in the [dependency injection](../framework/fundamentals/dependency-injection.md) system when the client needs additional configuration. + +## Document Caching + +The module stores downloaded documents in the Docs database, caches document update metadata and uses the distributed cache for document resources. These configuration values control when that data is refreshed: ```json { - "ElasticSearch": { - "Url": "http://localhost:9200" + "Volo.Docs": { + "DocumentCacheTimeoutInterval": "06:00:00", + "DocumentResource.AbsoluteExpirationRelativeToNow": "06:00:00", + "DocumentResource.SlidingExpiration": "00:30:00" } } ``` +The values shown above are the defaults. `DocumentCacheTimeoutInterval` controls how often a stored document is checked against its source. The resource settings control the absolute and sliding expirations for images and other document resources. Resource caching is bypassed in the Development environment. + +Users with the `Docs.Admin.Documents` permission can clear a project's cache from the administration UI. This removes the cached language and version metadata, invalidates document update information and causes the stored documents to be refreshed on subsequent requests. + +Clearing the project cache does not remove document-resource cache entries. Cached images and other resources remain available until their absolute or sliding cache expiration is reached. + ## Row Highlighting @@ -688,34 +416,75 @@ After you specify the next & previous documents, they will appear at the end of ![](../images/docs-referencing.png) -## Single Project Mode +## UI Options -The **single project mode** allows you to use a single name as a project name in your application. If you are not considering supporting multiple projects with their multiple docs and instead if you have a single project and want to have documentation only for it, it's especially useful for you. - -You just need to configure the `DocsUiOptions`, set the single project mode as **enabled** and also define a constant project name: +Configure `DocsUiOptions` to customize routes and document-page behavior: ```csharp -Configure(options => +Configure(options => { options.RoutePrefix = "docs"; + options.ShowProjectsCombobox = false; + options.ShowProjectsComboboxLabel = false; + options.SectionRendering = true; + options.MultiLanguageMode = true; + options.EnableEnlargeImage = true; options.SingleProjectMode.Enable = true; options.SingleProjectMode.ProjectName = "abp"; }); ``` -## Multi Language Mode +The defaults are: + +| Option | Default | Description | +| --- | --- | --- | +| `RoutePrefix` | `documents` | Route prefix for the public document pages. | +| `ShowProjectsCombobox` | `true` | Shows the project selector when more than one project is available. | +| `ShowProjectsComboboxLabel` | `true` | Shows the label for the project selector. | +| `SectionRendering` | `true` | Enables Scriban-based conditional sections. | +| `MultiLanguageMode` | `true` | Adds the language to routes and displays the language selector. | +| `EnableEnlargeImage` | `true` | Allows document images to be enlarged in the UI. | +| `SingleProjectMode.Enable` | `false` | Removes the project name from document routes. | + +When single-project mode is enabled, `SingleProjectMode.ProjectName` selects the project by short name. If it is empty, the module uses the project automatically only when exactly one project exists. + +`DocumentLinksNormalizer` transforms generated links to other documents. Its default removes a trailing `/Index`. `RedirectUrlResolver` applies the corresponding redirect when an incoming URL ends in `/Index`. Replace either delegate when the application needs a different canonical URL convention. + +## Google Translate and Programmable Search + +The MVC UI can add Google Translate and Google Programmable Search to document pages. Both integrations are disabled by default: + +```csharp +Configure(options => +{ + options.EnableGoogleTranslate = true; + options.IncludedLanguages = ["en", "de", "fr"]; + options.EnableGoogleProgrammableSearchEngine = true; + options.GoogleSearchEngineId = "search-engine-id"; +}); +``` + +`GetCultureLanguageCode` maps the current UI culture to the code passed to Google Translate. The default maps `zh-Hans` to `zh-CN`, `zh-Hant` to `zh-TW` and other cultures to their two-letter ISO language name. -The **multi language mode** allows you to show a combobox that lists and shows all documentation languages and configures the related languages in routes. +## Generating PDF Files -It's enabled by default and supports multiple languages, but if you are considering only supporting a single language, and don't want to show the language combobox in the sidebar of your docs system, you can configure the `DocsUiOptions` and set the multi language mode support as **false** to disable it: +The administration module can generate a PDF archive for a project, version and language. Generation runs as a background job and stores the archive through ABP's BLOB storing system. Configure a BLOB provider for the application, then configure the generator when its defaults do not match the host: ```csharp -Configure(options => +Configure(options => { - options.MultiLanguageMode = false; + options.BaseUrl = configuration["App:SelfUrl"]; + options.IndexPagePath = "index.md"; + options.CalculatePdfFileTitle = project => project.Name; }); ``` +`HtmlLayout` and `HtmlStyle` customize the generated content. `BaseUrl` is used to resolve relative images for local sources. `IndexPagePath` inserts an additional document at the beginning of the archive without adding it to the PDF outline. `CalculatePdfFileName`, `CalculatePdfFileTitle`, `HtmlContentNormalizer` and `DocumentContentNormalizer` provide additional extension points. + +`Docs.Admin.Projects.ManagePdfFiles` allows administrators to generate, list and delete archives. Generation creates a ZIP file that contains the rendered PDF documents. Users need `Docs.Common.PdfDownload` to download it, and the public UI displays the download action only when that permission is granted and an archive exists for the selected project, version and language. + +PDF generation failures are written to the application logs. The background job does not rethrow failures to the job system, including errors already handled by the generator. A failed run does not make a new archive available, and the background-job system does not automatically retry it as a failed job. Check the host logs and manually start generation again after fixing the error. + ## See Also Docs Module is also available as a standalone application. Check out [VoloDocs](../apps/volo-docs.md). From ed30879b345c504348ad05b8a01b5479e6ddf51b Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:23 +0800 Subject: [PATCH 16/41] Expand CMS Kit module documentation --- docs/en/modules/cms-kit/blogging.md | 14 +++-- docs/en/modules/cms-kit/comments.md | 32 +++++++---- docs/en/modules/cms-kit/dynamic-widget.md | 6 +- docs/en/modules/cms-kit/global-resources.md | 6 +- docs/en/modules/cms-kit/index.md | 62 ++++++++++++++++++--- docs/en/modules/cms-kit/marked-items.md | 37 ++++++++---- docs/en/modules/cms-kit/menus.md | 14 ++++- docs/en/modules/cms-kit/pages.md | 25 ++++++++- docs/en/modules/cms-kit/ratings.md | 8 ++- docs/en/modules/cms-kit/reactions.md | 18 +++++- docs/en/modules/cms-kit/tags.md | 2 +- 11 files changed, 174 insertions(+), 50 deletions(-) diff --git a/docs/en/modules/cms-kit/blogging.md b/docs/en/modules/cms-kit/blogging.md index 90356a59fe..1c0ee4713e 100644 --- a/docs/en/modules/cms-kit/blogging.md +++ b/docs/en/modules/cms-kit/blogging.md @@ -15,6 +15,8 @@ By default, CMS Kit features are disabled. Therefore, you need to enable the fea > Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. +> The built-in MVC blog routes are registered by the Pages global feature. Enable both `Blogs` and `Pages` when you use the built-in public blog pages. + ## User Interface ### Menu Items @@ -38,7 +40,7 @@ A screenshot from the new blog creation modal: **Slug** is the URL part of the blog. For this example, the root URL of the blog becomes `your-domain.com/blogs/technical-blog/`. -- You can change the default slug by using `CmsBlogsWebConsts.BlogRoutePrefix` constant. For example, if you set it to `foo`, the root URL of the blog becomes `your-domain.com/foo/technical-blog/`. +- You can change the default route prefix by using the `CmsBlogsWebConsts.BlogsRoutePrefix` property. For example, if you set it to `foo`, the root URL of the blog becomes `your-domain.com/foo/technical-blog/`. ```csharp public override void PreConfigureServices(ServiceConfigurationContext context) @@ -49,7 +51,7 @@ A screenshot from the new blog creation modal: #### Blog Features -Blog feature uses some of the other CMS Kit features. You can enable or disable the features by clicking the features action for a blog. +The blogging feature uses other CMS Kit features. A newly created blog enables comments, reactions, ratings, tags, marked items, the quick navigation bar and XSS prevention by default when the related global features are available. You can enable or disable these features for each blog by clicking the features action. ![blogs-feature-action](../../images/cmskit-module-blogs-feature-action.png) @@ -58,7 +60,7 @@ You can select/deselect the desired features for blog posts. ![features-dialog](../../images/cmskit-module-features-dialog-2.png) ##### Quick Navigation Bar In Blog Post -If you enable "Quick navigation bar in blog posts", it will enabled scroll index as seen below. +If you enable **Quick navigation bar in blog posts**, the public blog post page builds a scroll index from the post headings. ![scroll-index](../../images/cmskit-module-features-scroll-index.png) @@ -72,6 +74,10 @@ You can create and edit an existing blog post on this page. If you enable specif ![blog-post-edit](../../images/cmskit-module-blog-post-edit.png) +Blog posts have three statuses: `Draft`, `WaitingForReview` and `Published`. Creating, updating, deleting and publishing posts use separate permissions. The public blog list requests only published posts and can filter them by author, tag or the current user's marked items. + +The built-in renderer allows HTML in blog post Markdown. The per-blog **Prevent XSS** feature controls whether the Markdown renderer sanitizes that HTML and is enabled for newly created blogs. Keep it enabled when post authors are not trusted to submit arbitrary HTML. + ## Internals ### Domain Layer @@ -138,4 +144,4 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ## Entity Extensions -Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Blogging Feature of the CMS Kit module. \ No newline at end of file +Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Blogging Feature of the CMS Kit module. diff --git a/docs/en/modules/cms-kit/comments.md b/docs/en/modules/cms-kit/comments.md index 0070b5a2a9..c3a595a7d8 100644 --- a/docs/en/modules/cms-kit/comments.md +++ b/docs/en/modules/cms-kit/comments.md @@ -25,16 +25,16 @@ The comment system provides a mechanism to group comment definitions by entity t Configure(options => { options.EntityTypes.Add(new CommentEntityTypeDefinition("Product")); - options.IsRecaptchaEnabled = true; //false by default + options.IsRecaptchaEnabled = true; options.AllowedExternalUrls = new Dictionary> { - { - "Product", - new List { - "https://abp.io/" + "Product", + new List + { + "https://abp.io/" + } } - } }; }); ``` @@ -43,9 +43,9 @@ Configure(options => `CmsKitCommentOptions` properties: -- `EntityTypes`: List of defined entity types(`CmsKitCommentOptions`) in the comment system. +- `EntityTypes`: List of defined entity types (`CommentEntityTypeDefinition`) in the comment system. - `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system. -- `AllowedExternalUrls`: Indicates the allowed external URLs by entity types, which can be included in a comment. If it's specified for a certain entity type, then only the specified external URLs are allowed in the comments. +- `AllowedExternalUrls`: Registers the URL values used by the external-link validation for each entity type. `CommentEntityTypeDefinition` properties: @@ -67,6 +67,17 @@ The comment system provides a commenting [widget](../../framework/ui/mvc-razor-p `entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. `referralLinks` is an optional parameter. You can use this parameter to add values (such as "nofollow", "noreferrer", or any other values) to the [rel attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel) of links. +Creating, updating and deleting a comment requires an authenticated user. Users can update only their own comments. They can delete their own comments, while the `CmsKitPublic.Comments.DeleteAll` permission allows deleting comments created by other users. Deleting a comment also deletes its direct replies. + +Reactions are enabled inside the MVC comments widget by default when the Reactions global feature is available. Disable them without disabling reactions for other entity types by configuring the UI options in the web project: + +```csharp +Configure(options => +{ + options.CommentsOptions.IsReactionsEnabled = false; +}); +``` + ## User Interface ### Menu Items @@ -89,7 +100,7 @@ You can also view and manage replies on this page. ## Settings -You can configure the approval status of comments using the "Comment" tab under the "Cms" section on the Settings page. When this feature is enabled, you can approve and reject comments. In this way, users can only see the comments that you approve. By default, this feature is set to "false." +You can configure the approval status of comments using the **Comment** tab under the **Cms** section on the Settings page. When approval is required, new comments wait for an administrator and public queries return only approved comments. When approval is disabled, waiting comments are also visible. The setting is stored globally, not per tenant, and is `false` by default. Changing it requires the `CmsKit.Comments.SettingManagement` permission. ![comments-settings](../../images/cmskit-module-comments-settings.png) @@ -136,7 +147,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string @@ -155,4 +166,3 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md) ##### Collections - **CmsComments** - diff --git a/docs/en/modules/cms-kit/dynamic-widget.md b/docs/en/modules/cms-kit/dynamic-widget.md index f0646cc00f..6eed96868c 100644 --- a/docs/en/modules/cms-kit/dynamic-widget.md +++ b/docs/en/modules/cms-kit/dynamic-widget.md @@ -122,7 +122,7 @@ Configure(options => In this image, after choosing your widget (on the other case, it changes automatically up to your configuration, mine is `Today`. Its parameter name `parameterWidgetName` and its value is `Format`) you will see the next widget. Enter input values or choose them and click `Add`. You will see the underlined output in the editor. Right of the image, also you can see its previewed output. -You can edit this output manually if do any wrong coding for that (wrong value or typo) you won't see the widget, even so, your page will be viewed successfully. +The stored widget markup is parsed when a page or blog post is rendered. When at least one widget has been registered, an unknown widget type is omitted from the rendered fragments. If no widgets have been registered, the stored markup remains in a Markdown fragment. If a registered view component throws while rendering, CMS Kit keeps the rest of the page visible, renders a localized error alert for that fragment and writes the exception to the application log. ## Options @@ -146,4 +146,6 @@ The `CmsKitContentWidgetOptions` provides two methods for registering widgets: - `parameterWidgetName` (optional): The name of the parameter widget that will be displayed in the "Add Widget" modal to collect parameter values from users. This is only required when your widget needs parameters. - **AddWidgetIfFeatureEnabled:** Registers a widget conditionally, only if a specified [global feature](../../framework/infrastructure/global-features.md) is enabled. It accepts the same parameters as `AddWidget`, plus an additional first parameter: - - `featureType` (required): The type of the global feature that must be enabled for the widget to be available (e.g., `typeof(PagesFeature)`). \ No newline at end of file + - `featureType` (required): The type of the global feature that must be enabled for the widget to be available (e.g., `typeof(PagesFeature)`). + +The registration is shared by the administration editor and the public content parser. `AddWidgetIfFeatureEnabled` evaluates the global feature while the module configures its services; it does not use the runtime tenant feature system. diff --git a/docs/en/modules/cms-kit/global-resources.md b/docs/en/modules/cms-kit/global-resources.md index ff02351be8..e945908ace 100644 --- a/docs/en/modules/cms-kit/global-resources.md +++ b/docs/en/modules/cms-kit/global-resources.md @@ -31,6 +31,10 @@ Global Resources page is used to manage global styles and scripts in the system. ![cms-kit-global-resources-page](../../images/cmskit-module-global-resources-page.png) +The built-in public web module adds the style resource at `LayoutHooks.Head.Last` and the script resource at `LayoutHooks.Body.Last`. The resources are served from `/cms-kit/global-resources/style` and `/cms-kit/global-resources/script`. A cache miss stores the loaded resource in the distributed cache with a two-minute absolute expiration. Updating an existing resource refreshes its cached value through a local entity event and uses the configured default cache options. + +> Global resources are trusted administrator input. The style is returned as CSS and the script is returned as executable JavaScript on every public page that uses the layout hooks. Grant `CmsKit.GlobalResources` only to users who are allowed to execute code in visitors' browsers, and apply your Content Security Policy accordingly. + # Internals ## Domain Layer @@ -76,4 +80,4 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Collections -- CmsGlobalResources \ No newline at end of file +- CmsGlobalResources diff --git a/docs/en/modules/cms-kit/index.md b/docs/en/modules/cms-kit/index.md index 344b719889..02bd58f5f9 100644 --- a/docs/en/modules/cms-kit/index.md +++ b/docs/en/modules/cms-kit/index.md @@ -11,7 +11,7 @@ This module provides CMS (Content Management System) capabilities for your appli > You can see the live demo at [cms-kit-demo.abpdemo.com](https://cms-kit-demo.abpdemo.com/). -> **This module currently available only for the MVC / Razor Pages UI**. While there is no official Blazor package, it can also work in a Blazor Server UI since a Blazor Server UI is actually a hybrid application that runs in an ASP.NET Core MVC / Razor Pages application. +> CMS Kit provides MVC / Razor Pages packages for both the administration and public websites. The `@abp/ng.cms-kit` package provides an Angular administration UI. There is no official Blazor package; a Blazor Server application can host the MVC / Razor Pages UI because it runs on ASP.NET Core. The following features are currently available: @@ -28,7 +28,12 @@ The following features are currently available: > You can click on the any feature links above to understand and learn how to use it. -All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, with the help of the [Global Features](../../framework/infrastructure/global-features.md) system. +CMS Kit uses two feature layers: + +* [Global Features](../../framework/infrastructure/global-features.md) select the CMS Kit subsystems included in the application model. When using Entity Framework Core, changing these features requires a new migration because disabled entities are excluded from the EF Core model. +* The [Feature System](../../framework/infrastructure/features.md) can enable or disable the corresponding subsystem at runtime for a tenant or another feature value provider. Runtime feature changes do not change the database model. + +Most subsystems can be selected independently. The built-in MVC blog pages are currently registered together with the Pages global feature, so enable both `Blogs` and `Pages` when you use the built-in public blog UI. ## Pre Requirements @@ -38,6 +43,23 @@ All features are individually usable. If you disable a feature, it completely di - CMS Kit uses [distributed cache](../../framework/fundamentals/caching.md) for responding faster. > Using a distributed cache, such as [Redis](../../framework/fundamentals/redis-cache.md), is highly recommended for data consistency in distributed/clustered deployments. +## Media Storage and Entity Types + +When the Media global feature is enabled, CMS Kit registers media definitions for blog posts and pages. To upload media for another entity type, register a `MediaDescriptorDefinition` and specify the permissions that can create and delete its media: + +```csharp +Configure(options => +{ + options.EntityTypes.Add( + new MediaDescriptorDefinition( + "Product", + createPolicies: new[] { "Products.Update" }, + deletePolicies: new[] { "Products.Update" })); +}); +``` + +The administration service grants an operation when the current user has any policy in the corresponding list. An empty list grants no access. Media files are downloaded from the anonymous `GET /api/cms-kit/media/{id}` endpoint, so this facility is for public media; use a separately authorized BLOB endpoint for private files. + ## Identity Integration for User Lookup CMS Kit uses `ICmsUserLookupService` when it needs user information for features such as comments, ratings, blog post management and user synchronization. @@ -129,6 +151,31 @@ CMS kit packages are designed for various usage scenarios. If you check the [CMS - `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments. - `Volo.CmsKit.*` (without Admin/Public suffix) packages are called as unified packages. Unified packages are shortcuts for adding Admin & Public packages (of the related layer) separately. If you have a single application for administration and public web site, you can use these packages. +### Angular Administration UI + +The `@abp/ng.cms-kit` package contains the Angular administration components, routes, configuration providers and generated proxies. Register the administration menu configuration in the application configuration and lazy-load the administration routes: + +```typescript +import { ApplicationConfig } from '@angular/core'; +import { Routes } from '@angular/router'; +import { provideCmsKitAdminConfig } from '@abp/ng.cms-kit/admin/config'; + +export const appConfig: ApplicationConfig = { + providers: [provideCmsKitAdminConfig()], +}; + +export const routes: Routes = [ + { + path: 'cms', + loadChildren: () => import('@abp/ng.cms-kit/admin').then(m => m.createRoutes()), + }, +]; +``` + +The Angular administration routes include comments, tags, pages, blogs, blog posts, menus and global resources. The built-in public page and blog UI documented in this guide uses the MVC / Razor Pages packages. + +`createRoutes` accepts a `CmsKitAdminConfigOptions` object for Angular UI extensions. It supports entity-action, entity-property, toolbar-action, create-form-property and edit-form-property contributors. Key the contributor dictionaries with `eCmsKitAdminComponents`; the supported screens cover comment lists/details, tags, pages and page forms, blogs, blog posts and blog post forms, and menus. Each contributor type exposes only the keys supported by that screen. + ## Integrating Public and Admin Packages in a Unified Application If you are using a single application for both admin and public web site, it's important to configure the global layout settings appropriately. By default, the layout is set for a **Public Website**, which is suitable for public-facing pages. However, when your application serves both admin and public pages, you should explicitly set the global layout for all CMS Kit pages. @@ -150,7 +197,7 @@ To do this, add a `_ViewStart.cshtml` file to your web project at `/Pages/Public ### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ### Connection string @@ -209,13 +256,10 @@ public static void ConfigureExtraProperties() * `ConfigureCmsKit(...)` method is used to configure the entities of the CMS Kit module. -* `cmsKit.ConfigureBlog(...)` is used to configure the **Blog** entity of the CMS Kit module. You can add or update your extra properties on the **Blog** entity. +* CMS Kit provides configuration methods for `Blog`, `BlogPost`, `BlogFeature`, `MediaDescriptor`, `Page`, `Tag`, `Comment`, `MenuItem`, `CmsUser` and `GlobalResource`. -* `cmsKit.ConfigureBlogPost(...)` is used to configure the **BlogPost** entity of the CMS Kit module. You can add or update your extra properties of the **BlogPost** entity. +* The built-in MVC create and update forms consume extensions for blogs, blog posts, menu items, pages and tags. The Angular administration UI consumes object extensions and contributor callbacks for its comments, tags, pages, blogs, blog posts and menu screens. * You can also set some validation rules for the property that you defined. In the above sample, `RequiredAttribute` and `StringLengthAttribute` were added for the property named **"BlogPostDescription"**. -* When you define the new property, it will automatically add to **Entity**, **HTTP API**, and **UI** for you. - * Once you define a property, it appears in the create and update forms of the related entity. - * New properties also appear in the datatable of the related page. - +* Each helper exposes the module entity extension configuration for that type. Persistence and DTO propagation depend on the mappings registered by the installed CMS Kit packages. Automatic form and table rendering is available only on the MVC and Angular screens listed above. For other entities or custom screens, read the extra property from the DTO and render it explicitly. diff --git a/docs/en/modules/cms-kit/marked-items.md b/docs/en/modules/cms-kit/marked-items.md index bd0db306eb..7d51e518b3 100644 --- a/docs/en/modules/cms-kit/marked-items.md +++ b/docs/en/modules/cms-kit/marked-items.md @@ -18,15 +18,15 @@ you can also customize the marking icons shown in the toggling components. ## Enabling the Marked Item Feature -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime. +By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before using them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use the ABP [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime. -> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. +> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time. ## Options Marking system provides a simple approach to define your entity type with mark types like favorite or starred. For example, if you want to use the marking system for products, you need to define an entity type named `product` with the icon name. -`CmsKitMarkedItemOptions` can be configured in YourModule.cs, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: +`CmsKitMarkedItemOptions` can be configured in `YourModule.cs`, in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md). Example: ```csharp Configure(options => @@ -42,7 +42,7 @@ Configure(options => `CmsKitMarkedItemOptions` properties: -- `EntityTypes`: List of defined entity types (`CmsKitMarkedItemOptions`) in the marking system. +- `EntityTypes`: List of defined entity types (`MarkedItemEntityTypeDefinition`) in the marking system. `MarkedItemEntityTypeDefinition` properties: @@ -53,8 +53,8 @@ Configure(options => The marking system provides a toggle widget to allow users to add/remove the marks from an item. You can place the widget with the item as shown below: -``` csharp -@await Component.InvokeAsync(typeof (MarkedItemToggleViewComponent), new +```csharp +@await Component.InvokeAsync(typeof(MarkedItemToggleViewComponent), new { entityId = "...", entityType = "product", @@ -65,6 +65,20 @@ The marking system provides a toggle widget to allow users to add/remove the mar * `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. * `needsConfirmation` An optional parameter to let the user confirm when removing the mark. +The widget can be rendered for anonymous visitors, but toggling a mark requires an authenticated user. The mark state is stored for the current user, entity type and entity ID. + +### Customizing MVC Marked Item Icons + +The MVC widget resolves the configured icon name through `CmsKitUiOptions.MarkedItemIcons`: + +```csharp +Configure(options => +{ + options.MarkedItemIcons[StandardMarkedItems.Favorite] = + new LocalizableIconDictionary("fa fa-heart text-danger"); +}); +``` + ### Filtering on Marked Items Users can filter their marked items to easily find their favorites. Here's how to utilize the `GetEntityIdsFilteredByUserAsync` method to filter the user's marked items within your repository queries: @@ -97,7 +111,7 @@ var queryable = (await GetDbSetAsync()) #### Aggregates -This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide. +This module follows the [Entity Best Practices & Conventions](../../framework/architecture/best-practices/entities.md) guide. ##### UserMarkedItem @@ -107,7 +121,7 @@ A user markedItem represents a user has marking on the item. #### Repositories -This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide. +This module follows the [Repository Best Practices & Conventions](../../framework/architecture/best-practices/repositories.md) guide. Following custom repositories are defined for this feature: @@ -116,7 +130,7 @@ Following custom repositories are defined for this feature: #### Domain services -This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide. +This module follows the [Domain Services Best Practices & Conventions](../../framework/architecture/best-practices/domain-services.md) guide. ##### Marked Item Manager @@ -134,13 +148,13 @@ This module follows the [Domain Services Best Practices & Conventions](https://d ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. -See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. +See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details. #### Entity Framework Core @@ -153,4 +167,3 @@ See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-String ##### Collections - **CmsUserMarkedItems** - diff --git a/docs/en/modules/cms-kit/menus.md b/docs/en/modules/cms-kit/menus.md index 429e9226e6..263db66c28 100644 --- a/docs/en/modules/cms-kit/menus.md +++ b/docs/en/modules/cms-kit/menus.md @@ -35,7 +35,15 @@ Menus page is used to manage dynamic public menus in the system. The created menu items will be visible on the public-web side, as shown below: -![cms-kit-public-menus](../../images//cmskit-module-menus-public.png) +![cms-kit-public-menus](../../images/cmskit-module-menus-public.png) + +### Menu Item Behavior + +Menu items form an ordered tree. Moving an item changes its parent and position, and CMS Kit normalizes the sibling order. Inactive root or child items are omitted from the public menu. + +A menu item can target either a URL or a CMS Kit page. When it targets a page, CMS Kit stores the page relationship and updates the menu URL after the page slug changes. It can also define an icon, link target, element ID, CSS class and a required permission. The public menu contributor omits permission-protected items for users who do not have the configured permission. + +The public contributor builds the named `CmsKit.Public` menu. CMS Kit registers that name as a main menu and caches the ordered menu-item DTOs in the distributed cache. Creating, updating, moving or deleting a menu item invalidates the cache. ## Internals @@ -76,7 +84,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string @@ -94,4 +102,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md) ##### Collections -- CmsMenuItems \ No newline at end of file +- CmsMenuItems diff --git a/docs/en/modules/cms-kit/pages.md b/docs/en/modules/cms-kit/pages.md index b2f428d9cd..7c3b1b3a10 100644 --- a/docs/en/modules/cms-kit/pages.md +++ b/docs/en/modules/cms-kit/pages.md @@ -33,9 +33,30 @@ CMS Kit module admin side adds the following items to the main menu, under the * ![pages-edit](../../images/cmskit-module-pages-edit.png) -After you have created pages, you can set one of them as a *home page*. Then, whenever anyone navigates to your application's homepage, they see the dynamic content of the page that you have defined on this page. +After you have created pages, you can set one of them as the *home page*. CMS Kit keeps at most one home page for the current tenant. Setting or clearing it requires the `CmsKit.Pages.SetAsHomePage` permission. ![pages-page](../../images/cmskit-module-pages-page.png) -Also when you create a page, you can access the created page via `/{slug}` URL. +Each page has a `Draft` or `Publish` status. Only published pages are returned by the public application service. A published home page is rendered at `/`, while any other published page is rendered at `/{slug}`. Draft pages return a not-found result on these public routes. +The public page lookup is cached. The home page has a one-hour absolute cache lifetime, and CMS Kit invalidates the relevant entries when an administrator creates, updates, deletes or changes the home page. + +### Layout and Custom Resources + +The optional **Layout Name** selects a layout from the current theme. A page can also contain CSS in its **Style** field and JavaScript in its **Script** field. CMS Kit adds the style to the page's style section and the script to its script section. + +> Page content, style and script are trusted administrator input. The built-in public page renders the content with HTML enabled and XSS prevention disabled, and writes the style and script without sanitization. Grant the page create and update permissions only to users who are allowed to publish executable content. + +## Internals + +### Domain Layer + +`Page` is a multi-tenant aggregate root. `PageManager` normalizes and checks slugs, changes publication status and enforces the single-home-page rule. + +### Application Layer + +`PageAdminAppService` provides permission-gated management operations. `PagePublicAppService` exposes only published pages and manages the distributed page cache. + +### Database Providers + +The Entity Framework Core table and MongoDB collection are named `CmsPages` by default. Use `AbpCmsKitDbProperties` to change the common prefix or the relational schema. diff --git a/docs/en/modules/cms-kit/ratings.md b/docs/en/modules/cms-kit/ratings.md index 492af1c803..9c2460aeb0 100644 --- a/docs/en/modules/cms-kit/ratings.md +++ b/docs/en/modules/cms-kit/ratings.md @@ -55,6 +55,8 @@ The ratings system provides a rating widget to allow users send ratings to resou `entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. +Reading grouped rating counts is anonymous. Creating, changing or deleting a rating requires an authenticated user. A user has at most one rating for an entity; submitting another value updates that rating. The built-in range is one through five stars. + # Internals ## Domain Layer @@ -81,7 +83,7 @@ Following custom repositories are defined for this feature: This module follows the [Domain Services Best Practices & Conventions](../../framework/architecture/best-practices/domain-services.md) guide. -##### Reaction Manager +##### Rating Manager `RatingManager` is used to perform some operations for the `Rating` aggregate root. @@ -97,7 +99,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string @@ -115,4 +117,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md) ##### Collections -- **CmsRatings** \ No newline at end of file +- **CmsRatings** diff --git a/docs/en/modules/cms-kit/reactions.md b/docs/en/modules/cms-kit/reactions.md index 1db865e287..ec573d130f 100644 --- a/docs/en/modules/cms-kit/reactions.md +++ b/docs/en/modules/cms-kit/reactions.md @@ -49,7 +49,7 @@ Configure(options => `CmsKitReactionOptions` properties: -- `EntityTypes`: List of defined entity types (`CmsKitReactionOptions`) in the reaction system. +- `EntityTypes`: List of defined entity types (`ReactionEntityTypeDefinition`) in the reaction system. `ReactionEntityTypeDefinition` properties: @@ -70,6 +70,20 @@ The reaction system provides a reaction widget to allow users to send reactions `entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. +The summary can be read anonymously. Creating or removing a reaction requires an authenticated user. A user can select each configured reaction at most once for the same entity; repeating the create operation doesn't create a duplicate record. + +### Customizing MVC Reaction Icons + +The MVC widget resolves each reaction name through `CmsKitUiOptions.ReactionIcons`. Replace an existing icon or register an icon for a custom reaction in the web project: + +```csharp +Configure(options => +{ + options.ReactionIcons[StandardReactions.Heart] = + new LocalizableIconDictionary("fa fa-heart text-danger"); +}); +``` + # Internals ## Domain Layer @@ -112,7 +126,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string diff --git a/docs/en/modules/cms-kit/tags.md b/docs/en/modules/cms-kit/tags.md index 7b57a49b9b..a3ed3a373a 100644 --- a/docs/en/modules/cms-kit/tags.md +++ b/docs/en/modules/cms-kit/tags.md @@ -149,7 +149,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / Collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string From ceccd77ab01d9f9a9e71e2d1ab5de048834ff160 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:38 +0800 Subject: [PATCH 17/41] Expand Elsa module documentation --- docs/en/modules/elsa-pro.md | 154 ++++++++++++++++++++++++++---------- 1 file changed, 112 insertions(+), 42 deletions(-) diff --git a/docs/en/modules/elsa-pro.md b/docs/en/modules/elsa-pro.md index 63e95bdb59..825854345a 100644 --- a/docs/en/modules/elsa-pro.md +++ b/docs/en/modules/elsa-pro.md @@ -7,7 +7,7 @@ # Elsa Module (Pro) -> You must have an ABP Team or a higher license to use this module. +> You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. This module integrates [Elsa Workflows](https://docs.elsaworkflows.io/) into ABP Framework applications and is designed to make it easy for developers to use Elsa's capabilities within their ABP-based projects. For creating, managing, and customizing workflows themselves, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/). @@ -17,7 +17,7 @@ The Elsa module is not installed in [the startup templates](../solution-template ### Using ABP CLI -ABP CLI allows adding a module to a solution using the ```add-module``` command. You can check its [documentation](../cli#add-module) for more information. So, the Elsa module can be added using the following command: +ABP CLI allows adding a module to a solution using the `add-module` command. See its [documentation](../cli#add-module) for more information. Add the Elsa module using the following command: ```bash abp add-module Volo.Elsa @@ -27,7 +27,7 @@ abp add-module Volo.Elsa If you modified your solution structure, adding the module using ABP CLI might not work for you. In such cases, you can add the Elsa module into your solution manually. -In order to do that, add packages listed below to the matching project in your solution. For example, `Volo.Abp.Elsa.Application` package to your **{ProjectName}.Application.csproj** as shown below: +To do this, add the packages listed below to the matching projects in your solution. For example, add the `Volo.Abp.Elsa.Application` package to your **{ProjectName}.Application.csproj** as shown below: ```xml @@ -42,11 +42,9 @@ After adding the package references, open the module class of the project (e.g.: )] ``` -> If you are using Blazor Web App, you need to add the `Volo.Elsa.Admin.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Elsa.Admin.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. - ### `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` -These two modules generally will be added to your authentication project. Please add `Volo.Abp.Elsa.AspNetCore` and `Volo.Abp.Elsa.Identity` packages to your project and add the `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of your module class based on your project structure: +Add these two modules to the project that hosts authentication. Add the `Volo.Abp.Elsa.AspNetCore` and `Volo.Abp.Elsa.Identity` packages to that project, then add `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of its module class: ```xml @@ -63,22 +61,15 @@ These two modules generally will be added to your authentication project. Please ## The Elsa Module -The Elsa Workflows has its own database provider, and also has a Tenant/Role/User system. They are under active development, so the ABP Elsa module is not yet fully integrated. Below is the current status of each module in the ABP's Elsa Module: +The ABP Elsa module provides the following integration points: -- `AbpElsaAspNetCoreModule(Volo.Abp.Elsa.AspNetCore)` module is used to integrate Elsa authentication. -- `AbpElsaIdentityModule(Volo.Abp.Elsa.Identity)` module is used to integrate ABP Identity authentication. -- `AbpElsaApplicationModule(Volo.Abp.Elsa.Application)` and `AbpElsaApplicationContractsModule(Volo.Abp.Elsa.Application.Contracts)` modules are used to define the Elsa permissions. +- `AbpElsaAspNetCoreModule` maps ABP permissions to the `permissions` claims expected by Elsa. +- Calling `UseAbpIdentity` replaces Elsa's user credential validator and access token issuer with implementations backed by the ABP Identity module. `AbpElsaIdentityModule` provides the required ABP module dependencies. +- `AbpElsaApplicationContractsModule` defines the permissions used by the Elsa Workflow API. -The rest of the projects/modules are basically empty and will be implemented in the future based on the Elsa features: +The module does not add its own workflow aggregates, application services or HTTP API controllers. Workflow definitions, instances and runtime data are managed by Elsa and must be configured through Elsa's own persistence providers. The ABP Entity Framework Core and MongoDB packages register empty module `DbContext` shells; they do not store workflow data or replace Elsa's workflow management and runtime stores. -- `AbpElsaDomainModule(Volo.Abp.Elsa.Domain)` -- `AbpElsaEntityFrameworkCoreModule(Volo.Abp.Elsa.EntityFrameworkCore)` -- `AbpElsaHttpApiModule(Volo.Abp.Elsa.HttpApi)` -- `AbpElsaHttpApiClientModule(Volo.Abp.Elsa.HttpApi.Client)` -- `AbpElsaBlazorModule(Volo.Abp.Elsa.Blazor)` -- `AbpElsaBlazorServerModule(Volo.Abp.Elsa.Blazor.Server)` -- `AbpElsaBlazorWebAssemblyModule(Volo.Abp.Elsa.Blazor.WebAssembly)` -- `AbpElsaWebModule(Volo.Abp.Elsa.Web)` +The MVC, Blazor and MudBlazor packages do not embed Elsa Studio. Use the standalone Elsa Studio application described in the [Elsa Studio](#elsa-studio) section. ## Configure the Elsa Server @@ -86,7 +77,7 @@ You need to configure Elsa in your ABP application to use its features. You can > For more information about configuring Elsa, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/). -```cs +```csharp private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("Default")!; @@ -110,38 +101,80 @@ private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration c } ``` +The example binds Elsa's HTTP activity options from the `Http` configuration section. `BaseUrl` is the public URL of the workflow server and `BasePath` is the path used for HTTP endpoint activities: + +```json +{ + "Http": { + "BaseUrl": "https://localhost:5001", + "BasePath": "/api/workflows" + } +} +``` + +Do not hard-code the signing key in production. Store a sufficiently long key in a secure configuration source and assign it to `identity.TokenOptions`. + +### Configure Authentication + +An ABP host normally accepts OpenIddict access tokens, while Elsa Identity issues its own access tokens for the Elsa Studio login. Register the composite authentication scheme so the same workflow API can accept both token types: + +```csharp +private void ConfigureAuthentication(ServiceConfigurationContext context) +{ + context.Services.ForwardIdentityAuthenticationForBearer( + AbpElsaJwtBearerDefaults.AuthenticationScheme + ); + context.Services.AddElsaJwtBearer( + OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme + ); +} +``` + +`ForwardIdentityAuthenticationForBearer` forwards bearer requests from the application cookie to the composite Elsa scheme. The argument passed to `AddElsaJwtBearer` is the existing bearer scheme that the composite handler tries in addition to Elsa's own scheme. Use the scheme configured by your application if it is different from `OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme`. + +Enable the Elsa endpoints and workflow middleware after authentication and authorization in the application initialization pipeline: + +```csharp +app.UseAuthentication(); +app.UseAbpOpenIddictValidation(); +app.UseAuthorization(); + +app.UseWorkflowsApi(); +app.UseWorkflows(); +``` + ## Elsa Database Migration -Elsa module uses its own database context and migration system, ABP Elsa module doesn't contain any `aggregate root/entity` at the moment. So, **you don't need to create any initial migration for Elsa module**. You just need to configure the Elsa Services as follows: +Elsa uses its own database contexts and migration system. The ABP Elsa module does not define workflow aggregate roots or entities, so you don't add Elsa tables to your application's ABP migration `DbContext`. Configure the Elsa workflow management and runtime stores instead: -```cs +```csharp .UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString))) .UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString))) ``` -When you run your application, Elsa will create its own database tables if they do not exist. +With the Entity Framework Core configuration shown above, Elsa runs its embedded migrations by default. Automatic migrations are convenient during development. For production, review Elsa's migration options and use a controlled deployment strategy before disabling automatic migrations. -> See [how to configure Elsa Workflows to use different database providers for persistence, including SQL Server, PostgreSQL, and MongoDB](https://docs.elsaworkflows.io/getting-started/database-configuration) for more information. +> See [Elsa's database configuration guide](https://docs.elsaworkflows.io/getting-started/database-configuration) and [EF Core migrations guide](https://docs.elsaworkflows.io/guides/persistence/ef-migrations) for provider and migration options. -### Elsa Module Permissions +## Elsa Module Permissions -The Elsa Workflow API endpoints check permissions. Also, it has a `*` wildcard permission to allow all permissions. +The Elsa Workflow API endpoints check permissions. The ABP Elsa module defines these permissions for the host side. They are not available to tenant users. -The ABP Elsa module defines all permissions that are used in the Elsa workflow. You can use ABP Permission Management module to manage the permissions. +You can use the ABP Permission Management module to grant individual Elsa permissions or the `*` wildcard permission. When `*` is granted, the claims contributor emits only the wildcard instead of adding every individual permission. -`AbpElsaAspNetCoreModule(Volo.Abp.Elsa.AspNetCore)` module will check and add these permissions to the current user's claims: +`AbpElsaAspNetCoreModule` checks the granted permissions and adds them to the current user's `permissions` claims: ![Elsa Permissions](../images/elsa-permissions.png) -You can also grant parts of the permissions to a role or user. It will add the `permissions` claims to the current user's `Cookies` or `Token`. Elsa Server will read the claims and allow or deny access: +You can also grant individual permissions to a host role or user. Elsa Server reads the resulting claims and allows or denies access: ![Elsa Part Permissions](../images/elsa-part-permissions.png) -### Elsa Studio +## Elsa Studio [Elsa Studio](https://docs.elsaworkflows.io/application-types/elsa-studio) is a **standalone** web application that allows you to design, manage, and execute workflows. It is built using **Blazor Server/WebAssembly**. -`ElsaDemoApp.Studio.WASM` is a sample Blazor WebAssembly project that demonstrates how to use Elsa Studio with ELSA Server with ABP Framework. +`ElsaDemoApp.Studio.WASM` is a sample Blazor WebAssembly project that demonstrates how to use Elsa Studio with an Elsa Server hosted in an ABP application. > Elsa Studio has its own layout and theme, and you can't integrate it into an ABP Blazor project for now. @@ -149,16 +182,36 @@ You can also grant parts of the permissions to a role or user. It will add the ` Please check the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) document to download its source code for review. -#### Elsa Studio Authentication +Configure Studio with the workflow server's Elsa API URL. The default Elsa API base path is `/elsa/api`: -Elsa Studio requires authentication and there are two ways to authenticate Elsa Studio: +```json +{ + "Backend": { + "Url": "https://localhost:5001/elsa/api" + } +} +``` + +### Elsa Studio Authentication + +Elsa Studio supports two authentication methods: * Password Flow Authentication * Code Flow Authentication -##### Elsa Studio - Password Flow Authentication +Configure the claim types used by Elsa Studio before choosing an authentication flow: + +```csharp +builder.Services.Configure(options => +{ + options.NameClaimType = "preferred_username"; + options.RoleClaimType = "role"; +}); +``` + +#### Elsa Studio - Password Flow Authentication -The `AbpElsaIdentityModule(Volo.Abp.Elsa.Identity)` module is used to integrate with [ABP Identity module](./identity-pro.md) to check Elsa Studio *username* and *password* against ABP Identity. +`AbpElsaIdentityModule` integrates with the [ABP Identity module](./identity-pro.md) to check the Elsa Studio *username* and *password* against host-side ABP Identity users. You need to replace `UseIdentity` with `UseAbpIdentity` when configuring Elsa in your Elsa server project as follows: @@ -168,25 +221,25 @@ context.Services .UseAbpIdentity(identity => { identity.TokenOptions = options => options.SigningKey = "large-signing-key-for-signing-JWT-tokens"; - }); + }) ); ``` -After that, you can add the below code to use `Identity` as the login method in your Elsa Studio client project: +After that, add the following code to use Elsa Identity as the login method in your Elsa Studio client project: ```csharp builder.Services.AddLoginModule().UseElsaIdentity(); ``` -Then, you can log in to the Elsa Studio application with the default credentials (`admin` as the username, and `1q2w3E*` as the password): +Then, you can log in to the Elsa Studio application with the host admin credentials: ![elsa-login](../images/elsa-password-login.png) -Once, you logged in to the application, you can start defining workflows, manage them and see their execution instances and more: +Once you have logged in, you can define and manage workflows and view their execution instances: ![elsa-main](../images/elsa-main-page.png) -##### Elsa Studio - Code Flow Authentication +#### Elsa Studio - Code Flow Authentication ABP applications use [OpenIddict](./openiddict-pro.md) for authentication. So, you can use the [Authorization Code Flow](https://oauth.net/2/grant-types/authorization-code/) to authenticate Elsa Studio. @@ -206,6 +259,23 @@ builder.Services.AddLoginModule().UseOpenIdConnect(connectConfiguration => After that, Elsa Studio will redirect to your ABP application's login page, then redirect back to Elsa Studio after the successful login. -### Elsa Workflows - Sample Workflow Demo +The ABP authentication server must also contain a public OpenIddict application for Studio. The sample's data seed reads the following configuration and registers `https://localhost:5003/signin-oidc` as the redirect URI: + +```json +{ + "OpenIddict": { + "Applications": { + "ElsaStudio_BlazorWasm": { + "ClientId": "ElsaStudio_BlazorWasm", + "RootUrl": "https://localhost:5003" + } + } + } +} +``` + +If your solution uses a different OpenIddict data-seeding implementation, register an equivalent public client with the authorization-code and refresh-token grants, including the Studio redirect URI, post-logout redirect URI and API scope. + +## Elsa Workflows - Sample Workflow Demo -ABP provides a complete demo application that shows how to use the Elsa module in your ABP application. You can download the demo application and see the integration points, if you stuck at any point. Please see the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) page for more information. +ABP provides a complete demo application that shows how to use the Elsa module in an ABP application. If you get stuck, download the demo to review the integration points. See the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) page for more information. From 8e5595519acfd59f2eade1a7e23d9297ea26a0e7 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:38 +0800 Subject: [PATCH 18/41] Document Operation Rate Limiting module --- docs/en/modules/operation-rate-limiting.md | 32 ++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/en/modules/operation-rate-limiting.md b/docs/en/modules/operation-rate-limiting.md index 6ef1444eb6..fdaf1b93b4 100644 --- a/docs/en/modules/operation-rate-limiting.md +++ b/docs/en/modules/operation-rate-limiting.md @@ -81,6 +81,8 @@ Instead of injecting `IOperationRateLimitingChecker` manually, you can use the ` > **Application Services** are handled by the ABP interceptor (built into the Domain layer). > **MVC Controllers** are handled by `AbpOperationRateLimitingActionFilter`, which is automatically registered when you reference the `Volo.Abp.OperationRateLimiting.AspNetCore` package. +For application and domain services, the decorated service must be resolved from dependency injection and the method must be eligible for [ABP interception](../framework/infrastructure/interceptors.md). In particular, keep the decorated method `virtual` when class-based dynamic proxies are used. MVC controller actions are enforced by the action filter and don't depend on service interception. + ### Applying to an Application Service ````csharp @@ -132,6 +134,8 @@ public virtual async Task SendCodeAsync([RateLimitingParameter] string phoneNumb } ```` +Only one parameter can have `[RateLimitingParameter]`. If multiple parameters are marked, the module throws an `AbpException` when it resolves the method arguments. + #### Using `IHasOperationRateLimitingParameter` Implement the interface on an input DTO when the partition key is a property of the DTO: @@ -220,7 +224,7 @@ options.AddPolicy("Login", policy => }); ```` -> When multiple rules are present, the module uses a **two-phase check**: it first verifies all rules without incrementing counters, then increments only if all rules pass. This prevents wasted quota when one rule would block the request. +> When multiple rules are present, the module first checks every rule without incrementing and starts incrementing only if that phase passes. This prevents quota consumption when a rule is already blocked. However, the policy isn't an atomic transaction across all rule counters: if a concurrent request exhausts a later rule during the increment phase, the current call is rejected, counters already incremented by earlier rules remain consumed, and the remaining rules are not incremented. ### Overriding an Existing Policy @@ -447,6 +451,8 @@ When multi-tenancy is enabled, the cache key includes the tenant ID, so each ten * **Global key format:** `orl:{PolicyName}:{RuleKey}:{PartitionKey}` * **Tenant-isolated key format:** `orl:t:{TenantId}:{PolicyName}:{RuleKey}:{PartitionKey}` +The resolved partition value is included in the distributed cache key without hashing. Email values are uppercased and phone numbers are normalized first, but they are still present in the key. Don't use secrets as partition values. If your cache backend exposes or logs keys, use a named custom resolver that returns a stable, non-reversible representation of sensitive identifiers. + ## Checking the Limit Inject `IOperationRateLimitingChecker` to interact with rate limits. It provides four methods: @@ -539,7 +545,10 @@ Configure(options => ```` * **`IsEnabled`** (`bool`, default: `true`): Global switch to enable or disable rate limiting. When set to `false`, all `CheckAsync` calls pass through without checking. This is useful for disabling rate limiting in development (see [below](#disabling-in-development)). -* **`LockTimeout`** (`TimeSpan`, default: `5 seconds`): Timeout for acquiring the distributed lock during counter increment operations. + * `CheckAsync` and `ResetAsync` become no-ops. + * `IsAllowedAsync` returns `true`. + * `GetStatusAsync` returns an allowed result with `MaxCount` and `RemainingCount` set to `int.MaxValue`, `CurrentCount` set to `0`, and no retry window. +* **`LockTimeout`** (`TimeSpan`, default: `5 seconds`): Timeout for acquiring the distributed lock during counter increment operations. If the lock can't be acquired in this period, the default store throws an `AbpException` for an infrastructure failure; it doesn't convert the failure into a rate-limit violation or HTTP 429 response. ## Advanced Usage @@ -590,6 +599,8 @@ await checker.CheckAsync("ApiCall", new OperationRateLimitingContext }); ```` +Don't put secrets or other values that shouldn't reach an error response in `ExtraProperties`. The checker copies every entry to the thrown exception's `Data` dictionary, which can be exposed by the application's exception handling configuration. + ### Pre-checking Before Expensive Operations Use `IsAllowedAsync` or `GetStatusAsync` to check the limit **before** performing expensive work (e.g., validating input or querying the database): @@ -672,28 +683,29 @@ A common pattern is to use ASP.NET Core middleware for broad API protection and The default store uses ABP's `IDistributedCache`. You can replace it by implementing `IOperationRateLimitingStore`: ````csharp +[Dependency(ReplaceServices = true)] public class MyCustomStore : IOperationRateLimitingStore, ITransientDependency { public Task IncrementAsync( string key, TimeSpan duration, int maxCount) { - // Your custom implementation (e.g., Redis Lua script for atomicity) + throw new NotImplementedException(); } public Task GetAsync( string key, TimeSpan duration, int maxCount) { - // Read-only check + throw new NotImplementedException(); } public Task ResetAsync(string key) { - // Reset the counter + throw new NotImplementedException(); } } ```` -ABP's [dependency injection](../framework/fundamentals/dependency-injection.md) system will automatically use your implementation since it replaces the default one. +`ITransientDependency` registers the implementation by convention, while `[Dependency(ReplaceServices = true)]` removes the default registration for the same service. See [Dependency Injection](../framework/fundamentals/dependency-injection.md) for other replacement approaches. A custom store is responsible for preserving the expected fixed-window and concurrency semantics; the default implementation coordinates increments with `IAbpDistributedLock`. ### Custom Rule @@ -703,13 +715,15 @@ You can implement custom rate limiting algorithms (e.g., sliding window, token b policy.AddRule(); ```` +The checker resolves every custom rule type from dependency injection for each check. Register the rule type as a service (for example, by implementing `ITransientDependency`) before adding it to a policy. + ### Custom Formatter -Replace `IOperationRateLimitingFormatter` to customize how time durations are displayed in error messages (e.g., "5 minutes", "2 hours 30 minutes"). +Replace `IOperationRateLimitingFormatter` to customize how time durations are displayed in error messages (e.g., "5 minutes", "2 hours 30 minutes"). Register the replacement explicitly, such as with `[Dependency(ReplaceServices = true)]` on a conventionally registered implementation. ### Custom Policy Provider -Replace `IOperationRateLimitingPolicyProvider` to load policies from a database or external configuration source instead of the in-memory options. +Replace `IOperationRateLimitingPolicyProvider` to load policies from a database or external configuration source instead of the in-memory options. The replacement must also be registered for `IOperationRateLimitingPolicyProvider`; the same ABP service replacement pattern can be used. When loading pre-built policies from an external source, use the `AddPolicy` overload that accepts an `OperationRateLimitingPolicy` object directly (bypassing the builder): @@ -730,6 +744,8 @@ options.AddPolicy(new OperationRateLimitingPolicy }); ```` +> The `AddPolicy(OperationRateLimitingPolicy)` overload stores the object as-is and doesn't run the builder validations. Before publishing a pre-built policy, validate that its name is not empty and that it contains at least one fixed-window or custom rule. For fixed-window definitions, validate positive durations, non-negative maximum counts, valid partition configuration and unique rule names. For non-custom partitions, also reject duplicate definitions with the same duration, maximum count, partition type and multi-tenancy flag. A custom policy provider has the same responsibility when it returns pre-built policies. + To remove a policy (e.g., when it is deleted from the database), use `RemovePolicy`: ````csharp From a2593a7e69de18a18fdfe68640214a7b7c472e01 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:38 +0800 Subject: [PATCH 19/41] Expand Chat module documentation --- docs/en/modules/chat.md | 70 +++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/docs/en/modules/chat.md b/docs/en/modules/chat.md index fa87a8eef6..8c2128d990 100644 --- a/docs/en/modules/chat.md +++ b/docs/en/modules/chat.md @@ -36,7 +36,7 @@ If you modified your solution structure, adding module using ABP Suite might not In order to do that, add packages listed below to matching project on your solution. For example, ```Volo.Chat.Application``` package to your **{ProjectName}.Application.csproj** like below; -```json +```xml ``` @@ -49,7 +49,7 @@ After adding the package reference, open the module class of the project (eg: `{ )] ``` -> If you are using Blazor Web App, you need to add the `Volo.Chat.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and ad the `Volo.Chat.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. +> If you are using Blazor Web App, you need to add the `Volo.Chat.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Chat.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. The `Volo.Chat.SignalR` package must be added according to your project structure: @@ -107,18 +107,38 @@ You can visit [Chat module package list page](https://abp.io/packages?moduleName ## User interface -### Manage chat feature +### Chat feature and permissions -Chat module defines the chat feature, you need to enable the chat feature to use chat. +The `Chat.Enable` feature is disabled by default. Enable it for a tenant or edition before granting chat permissions. ![chat-feature](../images/chat-feature.png) +The module defines the following permissions: + +* `Chat.Messaging`: Allows a user to open the chat page and exchange messages. A message can only be sent if the target user also has this permission. +* `Chat.Searching`: A child permission of `Chat.Messaging`. It allows a user to start a conversation with a user who is not already in the conversation history. Without this permission, the user can continue existing conversations. +* `Chat.SettingManagement`: Allows a user to manage the chat settings. + ### Chat page This is the page that users send messages to each other. ![chat-page](../images/chat-page.png) +Message text is required. By default, it must contain between 1 and 4,096 characters. The conversation API returns the newest messages first; the built-in user interfaces reverse that result to display messages in chronological order. + +### Deleting messages and conversations + +The following settings control deletion behavior: + +| Setting | Default | Description | +| --- | --- | --- | +| `Volo.Chat.Messaging.DeletingMessages` | `Enabled` | `Enabled` allows deletion at any time, `Disabled` prevents deletion, and `EnabledWithDeletionPeriod` allows deletion only during the configured period after the message is created. | +| `Volo.Chat.Messaging.MessageDeletionPeriod` | `0` | Deletion period in seconds when message deletion is set to `EnabledWithDeletionPeriod`. | +| `Volo.Chat.Messaging.DeletingConversations` | `Enabled` | Enables conversation deletion. It is effective only when message deletion is set to `Enabled`. | + +Deleting a message removes the shared message and both users' message records. Deleting a conversation removes both users' conversation records and all messages between them. These operations are shared deletions, not a "hide for me" operation. Deletion notifications are sent through the real-time channel. + ### Chat icon on navigation bar An icon that shows unread message count of the user and leads to chat page when clicked is added to navigation menu. @@ -213,9 +233,9 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do #### Installation -In order to configure the application to use the chat module, you first need to import `provideChatConfig` from `@volo/abp.ng.chat/config` to root application confiuration. Then, you will need to append it to the `appConfig` array. +In order to configure the application to use the chat module, you first need to import `provideChatConfig` from `@volo/abp.ng.chat/config` to the root application configuration. Then, you will need to append it to the `providers` array. -```js +```ts // app.config.ts import { provideChatConfig } from '@volo/abp.ng.chat/config'; @@ -228,20 +248,24 @@ export const appConfig: ApplicationConfig = { ``` -The chat module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. It is available for import from `@volo/abp.ng.chat`. +The chat module should be imported and lazy-loaded in your routing array. It exports a `createRoutes` function from `@volo/abp.ng.chat`. -```js +```ts // app.routes.ts const APP_ROUTES: Routes = [ // ... { path: 'chat', loadChildren: () => - import('@volo/abp.ng.chat').then(c => c.createRoutes(/* options here */)), + import('@volo/abp.ng.chat').then(c => c.createRoutes()), }, ]; ``` +#### Component replacement + +The Angular chat route uses the `Chat.ChatComponent` replacement key and `ChatComponent` as its default component. See the [Angular component replacement documentation](../framework/ui/angular/component-replacement.md) if you need to replace the page. + #### Services / Models Chat 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: @@ -254,7 +278,7 @@ abp generate-proxy --module chat The Chat module remote endpoint URLs can be configured in the environment files. -```js +```ts export const environment = { // other configurations apis: { @@ -274,12 +298,24 @@ The Chat module remote URL configurations shown above are optional. > If you don't set the `signalRUrl`, `Chat.url` will be used as fallback. If you don't set the `Chat` property, the `default.url` will be used as fallback. -### Blazor WebAssembly UI +### Blazor and MAUI UIs #### Remote Endpoint URL +The SignalR base URL can be configured with the option type that matches the UI package: + +| UI package | Option type | Fallback when `SignalrUrl` is not set | +| --- | --- | --- | +| Blazor Server | `ChatBlazorServerOptions` | The current application URL. | +| Blazor WebAssembly | `ChatBlazorWebAssemblyOptions` | `AbpRemoteServiceOptions.RemoteServices.Default.BaseUrl`. | +| Blazor MAUI | `ChatBlazorMauiBlazorOptions` | `AbpRemoteServiceOptions.RemoteServices.Default.BaseUrl`. | +| MudBlazor Server | `ChatBlazorMudBlazorServerOptions` | The current application URL. | +| MudBlazor WebAssembly | `ChatBlazorMudBlazorWebAssemblyOptions` | `AbpRemoteServiceOptions.RemoteServices.Default.BaseUrl`. | +| MudBlazor MAUI | `ChatBlazorMudBlazorMauiBlazorOptions` | `AbpRemoteServiceOptions.RemoteServices.Default.BaseUrl`. | -The Chat module remote endpoint URLs can be configured via the `ChatBlazorWebAssemblyOptions`. +The module appends the `/signalr-hubs/chat` path to the resolved base URL. + +For example, the Chat module remote endpoint URL can be configured for Blazor WebAssembly via the `ChatBlazorWebAssemblyOptions`: ```csharp Configure(options => @@ -303,4 +339,12 @@ Configure(options => ## Distributed Events -This module defines an event for messaging. It is published when a new message is sent from a user to another user, with an Event Transfer Object type of `ChatMessageEto`. See the [standard distributed events](../framework/infrastructure/event-bus/distributed) for more information about distributed events. +The module defines the following Event Transfer Object types for real-time operations: + +| Event Transfer Object | Operation | SignalR client method | +| --- | --- | --- | +| `ChatMessageEto` | A message is sent. | `ReceiveMessage` | +| `ChatDeletedMessageEto` | A message is deleted. | `DeleteMessage` | +| `ChatDeletedConversationEto` | A conversation is deleted. | `DeleteConversation` | + +The default application-layer implementation of `IRealTimeChatMessageSender` publishes these events to the distributed event bus. The `Volo.Chat.SignalR` package replaces that implementation in the SignalR host, handles the distributed events and sends them to the target user through the corresponding SignalR client method. See the [standard distributed events](../framework/infrastructure/event-bus/distributed) for more information about distributed events. From 4c3d6a40a6c2e68444ab0a8f91fb405924227bf9 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:38 +0800 Subject: [PATCH 20/41] Expand File Management module documentation --- docs/en/modules/file-management.md | 153 +++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 18 deletions(-) diff --git a/docs/en/modules/file-management.md b/docs/en/modules/file-management.md index 8a5f7aae29..cec220ba00 100644 --- a/docs/en/modules/file-management.md +++ b/docs/en/modules/file-management.md @@ -33,7 +33,7 @@ If you modified your solution structure, adding module using ABP CLI might not w In order to do that, add packages listed below to matching project on your solution. For example, `Volo.FileManagement.Application` package to your **{ProjectName}.Application.csproj** like below; -```json +```xml ``` @@ -46,7 +46,7 @@ After adding the package reference, open the module class of the project (eg: `{ )] ``` -> If you are using Blazor Web App, you need to add the `Volo.FileManagement.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and ad the `Volo.Chat.FileManagement.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. +> If you are using Blazor Web App, add the `Volo.FileManagement.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and the `Volo.FileManagement.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. If your project is using `EntityFrameworkCore`, you need to add following configuration to `OnModelCreating` method at your `DbContext`. @@ -61,31 +61,48 @@ builder.ConfigureFileManagement(); File Management module's MVC user interface depends on following npm packages. add `@volo/file-management` npm package to your `package.json` file. ```json -"dependencies": { - ... - "@volo/file-management": "^2.9.0" +{ + "dependencies": { + "@volo/file-management": "~x.x.x" } +} ``` > After adding packages, you need to run `abp install-libs` command in the folder of your `Web` project. #### Angular UI -For user interface, an Angular module called `FileManagementModule` is included in the `@volo/abp.ng.file-management` library. +For a standalone Angular application, register the File Management menu configuration in `app.config.ts`: -Please visit [document on feature libraries](../framework/ui/angular/feature-libraries.md) to learn how you can install and set it up in your Angular application. +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideFileManagementConfig } from '@volo/abp.ng.file-management/config'; -#### Blazor & Blazor Server +export const appConfig: ApplicationConfig = { + providers: [provideFileManagementConfig()], +}; +``` -[There is a known problem with ASP NET Core](https://github.com/dotnet/aspnetcore/issues/38842#issuecomment-1342540950), You have to set `DisableImplicitFromServicesParameters` of `HubOptions` to `true`. +Lazy-load the File Management routes in `app.routes.ts`: -```csharp -Configure(options => -{ - options.DisableImplicitFromServicesParameters = true; -}); +```ts +import { Routes } from '@angular/router'; + +export const APP_ROUTES: Routes = [ + { + path: 'file-management', + loadChildren: () => + import('@volo/abp.ng.file-management').then(m => m.createRoutes()), + }, +]; ``` +The `createRoutes` function accepts `entityActionContributors`, `toolbarActionContributors`, `entityPropContributors` and `xsrfHeaderName`. These contributors target `eFileManagementComponents.FolderContent`. See the Angular guides for [entity actions](../framework/ui/angular/entity-action-extensions.md), [page toolbars](../framework/ui/angular/page-toolbar-extensions.md) and [table columns](../framework/ui/angular/data-table-column-extensions.md). + +Use `withUppyOptions` with `provideFileManagementConfig` when you need to customize the Angular uploader's Uppy options. The startup templates already register the provider and lazy route when the module is selected. + +Please visit [document on feature libraries](../framework/ui/angular/feature-libraries.md) to learn how you can install and set it up in your Angular application. + ## Setting BLOB Provider File Management module is based on the [BLOB Storing](../framework/infrastructure/blob-storing) system as defined before, and it uses `FileManagementContainer` as a BLOB container. @@ -104,6 +121,10 @@ Configure(options => Please check the [BLOB Storage Providers documentation](../framework/infrastructure/blob-storing#blob-storage-providers) for more information about providers and how to use them. +File contents are stored with the file descriptor's ID as the BLOB name. Renaming or moving a file only changes its descriptor. Deleting a file removes both the descriptor and its BLOB. Deleting a directory recursively deletes its subdirectories, file descriptors and file BLOBs, so treat directory deletion as a destructive operation. + +The descriptor database and the configured BLOB provider are separate resources. A custom workflow that calls the domain services directly should handle failures between metadata and BLOB operations; a database transaction cannot roll back an external BLOB provider. + ## 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. @@ -144,12 +165,52 @@ You can move files by clicking `Actions -> Move` on the table. You can rename a file by clicking `Actions -> Rename` on the table. +The **Download** action first requests a short-lived download token and then navigates to the download endpoint. The **Preview** action is available for supported image files. The **Delete** action removes the stored content in addition to its file descriptor. + +The built-in UIs require an explicit overwrite choice when a file with the same name already exists in the current directory. Their upload pre-checks validate names, detect duplicates and check the current storage quota before the file content is sent. The upload HTTP API does not require this pre-check, and `CreateFileInputWithStream.OverrideExisting` defaults to `true`. A direct API client should call the pre-check endpoint and set `OverrideExisting` explicitly when it needs the same protection. + ###### File Sharing To share a file, click `Actions -> Share` in the table. Once sharing is enabled, you can copy the shared link directly from the table. > Anyone with the shared link will be able to access the file while sharing is enabled. +Shared links use a protected token that identifies the tenant and file. The token has no built-in expiration time; disabling sharing or deleting the file makes the link unavailable. Disabling sharing is not permanent link revocation: re-enabling the same file makes previously issued links valid again, and individual links cannot be revoked. Treat the URL as a secret. Persist the ASP.NET Core Data Protection key ring across restarts and deployments, and configure every application instance that serves these links to use the same key store and application name. + +### Share URL Origin in Tiered Applications + +MVC, Blazor and MudBlazor use a UI-specific option when they build copied share URLs. Configure the option when the HTTP API is hosted at a different public origin than the UI: + +```csharp +Configure(options => +{ + options.FileDownloadRootUrl = "https://api.example.com"; +}); +``` + +Use `FileManagementBlazorOptions` for the standard Blazor UI or `FileManagementBlazorMudBlazorOptions` for the MudBlazor UI. For copied share URLs, MVC falls back to the browser origin, while the Blazor UIs fall back to `NavigationManager.BaseUri`. These options only change copied share URLs. MVC authenticated downloads use the same-origin download endpoint; the Blazor UIs resolve the download origin from the File Management remote service configuration. + +### Resource-Based Permissions + +The module supports both module-wide permissions and resource permissions for individual directories and files. Users with a module-wide create, update, delete or view permission can perform that operation throughout File Management. A user without the corresponding module-wide permission can perform it when a matching resource permission is granted. + +Directory resource permissions are inherited by descendants. For example, `View` on a directory grants view access to its descendant directories and files, while `Add` grants creation in that directory and its descendants. File-level `View`, `Edit`, `Move` and `Delete` grants apply to the selected file. Creating an item at the root still requires the module-wide create permission because the root is not a directory resource. + +The `ManagePermissions` permissions show a **Permissions** action in the MVC, standard Blazor and Angular UIs. This action opens the Resource Permission Management UI for the selected directory or file. The MudBlazor UI does not currently provide this action. File sharing is controlled separately by `FileManagement.FileDescriptor.Share`; a resource permission does not grant sharing access. + +### HTTP API and Download Tokens + +The directory API is rooted at `/api/file-management/directory-descriptor` and exposes get, list, content, create, rename, move and delete operations. The file API is rooted at `/api/file-management/file-descriptor` and exposes list, upload pre-check, upload, content, rename, move, delete, storage information, download and sharing operations. + +Rename and move inputs carry the descriptor's concurrency stamp. API clients should return the latest stamp received from a descriptor or directory-content response so concurrent changes are detected instead of silently overwritten. + +Authenticated clients download a private file in two steps: + +1. Call `GET /api/file-management/file-descriptor/download/{id}/token`. This operation checks the module-wide or resource `View` permission. +2. Navigate to `GET /api/file-management/file-descriptor/download/{id}?token=...`. + +The download endpoint is anonymous because the token is the credential. A token is bound to one file and tenant, expires after 60 seconds and can be reused during that interval. Do not log or expose it. Public shared files use the separate anonymous `GET /api/file-management/file-descriptor/share?shareToken=...` endpoint. + ## Data Seed This module doesn't seed any data. @@ -162,7 +223,7 @@ This module doesn't seed any data. This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide. -##### TextTemplateContent +##### Directory and File Descriptors - `DirectoryDescriptor` (aggregate root): Represents a folder. - `FileDescriptor` (aggregate root): Represents a file. @@ -194,8 +255,15 @@ This module doesn't define any setting. ### Features -You can enable or disable this module for each tenant, also you can set maximum storage size for each tenant. -See the `FileManagementFeatures` class members for all features defined for this module. +You can enable or disable this module and set a maximum storage size for each tenant. The module defines these features: + +- `FileManagement.Enable`: Enables the module. The default is `true`. +- `FileManagement.StorageSize`: Sets the numeric quota from `1` through `8000`. The default is `1`. +- `FileManagement.StorageSizeUnit`: Selects `Byte`, `Kilobyte`, `Megabyte`, `Gigabyte` or `Terabyte`. The default is `Terabyte`. + +The default quota is therefore 1 TB. Usage is calculated from file descriptors in the current tenant context. An upload is rejected when the existing usage plus the incoming content reaches or exceeds the configured maximum. + +Quota checking reads the current total before storing a file; it does not reserve capacity or lock concurrent uploads. If the quota is a strict security or billing boundary, serialize uploads for a tenant or add an application-level reservation mechanism around the upload operation. ### Application Layer @@ -204,6 +272,45 @@ See the `FileManagementFeatures` class members for all features defined for this - `DirectoryDescriptorAppService` (implements `IDirectoryDescriptorAppService`): Implements the use cases of the file management UI. - `FileDescriptorAppService` (implements `IFileDescriptorAppService`): Implements the use cases of the file management UI. +### File Icon Configuration + +`FileIconOption` maps file extensions to Font Awesome classes or image URLs. Configure it in a module to add an extension, replace a built-in mapping or change the default icon: + +```csharp +Configure(options => +{ + options.SetFileIcon( + "cad", + new FileIconInfo("fa-solid fa-cube", FileIconType.FontAwesome)); + options.SetDefaultIcon( + new FileIconInfo("/images/file.svg", FileIconType.Url)); +}); +``` + +The MVC, standard Blazor and Angular UIs render the `IconInfo` returned by the application service and therefore use this configuration. The MudBlazor UI currently selects Material icons from the file extension and is not affected by `FileIconOption`. + +### Extending the Entities + +`DirectoryDescriptor` and `FileDescriptor` support the [Module Entity Extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) system. Configure extra properties in the `Domain.Shared` project before the database model is created: + +```csharp +ObjectExtensionManager.Instance.Modules() + .ConfigureFileManagement(fileManagement => + { + fileManagement.ConfigureDirectoryDescriptor(directory => + { + directory.AddOrUpdateProperty("Classification"); + }); + + fileManagement.ConfigureFileDescriptor(file => + { + file.AddOrUpdateProperty("ExternalId"); + }); + }); +``` + +The module maps these extra properties through its extensible input and output DTOs and applies the corresponding EF Core object-extension mappings. Add the matching UI extension when the property must be editable or visible in a built-in UI. + ### Database Providers #### Common @@ -238,4 +345,14 @@ See the `FileManagementPermissions` class members for all permissions defined fo ## Distributed Events -This module doesn't define any additional distributed event. See the [standard distributed events](../framework/infrastructure/event-bus/distributed). +This module doesn't explicitly publish a custom distributed event. It defines `DirectoryDescriptorEto` and `FileDescriptorEto` mappings for ABP's standard distributed entity events. Automatic entity events are disabled by default; enable the selectors in the application that owns the File Management data when consumers need create, update or delete notifications: + +```csharp +Configure(options => +{ + options.AutoEventSelectors.Add(); + options.AutoEventSelectors.Add(); +}); +``` + +This publishes the standard `EntityCreatedEto`, `EntityUpdatedEto` and `EntityDeletedEto` envelopes with the corresponding ETO payload. See the [distributed event bus documentation](../framework/infrastructure/event-bus/distributed) for delivery and handler configuration. From b33d0653e8d7575a820be98303a1ac701efe416f Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:38 +0800 Subject: [PATCH 21/41] Expand Forms module documentation --- docs/en/modules/forms.md | 182 +++++++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 73 deletions(-) diff --git a/docs/en/modules/forms.md b/docs/en/modules/forms.md index f2456b792b..0224801ea6 100644 --- a/docs/en/modules/forms.md +++ b/docs/en/modules/forms.md @@ -9,151 +9,187 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. -This module allows you to create questionnaires to gather information. The forms module can store responses as they come in and you can export the data to a CSV file. You can share your form with others with your form unique link. You can request authentication or allow anonymous reply. It is similar to the Google Form application. Usage area is quite wide, you can create surveys, manage event registrations, collect email addresses for a newsletter, create a quiz, and even receive an order request. +The Forms module allows you to create questionnaires, collect responses and export the results to CSV. A form can accept anonymous responses or require authentication, collect email addresses and allow respondents to edit their responses. You can share a form by using its unique link or sending an invitation email. See [the module description page](https://abp.io/modules/Volo.Forms) for an overview of the module features. -## How to install +## How to Install -The form module doesn't come pre-installed. You need to install it manually. There are 2 ways of installing it: +The Forms module isn't pre-installed. You can install it in one of the following ways: -* **Via ABP CLI:** Open a command line window in your solution folder (in the folder where the `* .sln` file is located) and type the following command: +* **ABP CLI:** Open a command-line terminal in the solution folder (the folder containing the solution file) and run the following command: ```bash abp add-module Volo.Forms ``` -* **Via ABP Suite:** Open ABP Suite and select your project. Then go to the modules page from the top menu. Find **Forms** card and click add as project (with source-code) or add as package (without source-code). +* **ABP Suite:** Open ABP Suite, select your solution and go to the modules page. Find the **Forms** card and add it as source code or as a package. ## 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. +This module follows the [module development best practices guide](../framework/architecture/best-practices) and consists of several NuGet packages. See the guide if you want to understand the packages and their relationships. -You can visit the [forms module package list page](https://abp.io/packages?moduleName=Volo.Forms) to see list of packages related with this module. +Visit the [Forms module package list](https://abp.io/packages?moduleName=Volo.Forms) to see the packages provided by this module. -## User interface +## User Interface -### Menu items +### Menu Item -SaaS module adds the following item to the root main menu. +The module adds a **Forms** item under the **Administration** menu when the current user has the `Forms.Form` permission. The page is used to create forms, manage their questions and settings, share them and inspect their responses. -* **Forms**: Add a new form, manage your form questions, delete your form. +The `FormsMenus` class contains the menu item names. +### Forms Page -The `FormsMenus` class has the constant variable for the menu item name. - -### Pages - -#### Forms - -Forms page is used to manage the forms. You can view the form contents, send it to others or delete it from the actions menu. +The Forms page lists the forms that you can manage. Its actions menu opens the form designer, opens the invitation dialog or deletes the form. Use the designer to manage questions and settings or preview the form. The **Responses** tab opens the response page, where you can inspect and export responses. ![form-list-page](../images/forms-list.png) -To see the other features of the Forms module, visit [the module description page](https://abp.io/modules/Volo.Forms). +### Question Types -## Data seed +The built-in form designer supports the following question types: -This module adds a sample initial form (see [the data seed system](../framework/infrastructure/data-seeding.md)) to the database when you run the `.DbMigrator` application: +* Short text +* Multiple choice +* Checkboxes +* Dropdown list -* **Form title:** "Test Form" -* **Form description:** "Test Description" +Questions and choices are displayed in their configured order. Multiple-choice and checkbox questions can also include an **Other** option. -## Internals +### Response Settings -### Domain layer +New forms accept responses by default. The other response settings are disabled by default. -#### Aggregates +| Setting | Behavior | +| --- | --- | +| `RequiresLogin` | The packaged MVC page redirects anonymous users to the login page. `GetQuestionsAsync` and response submission also reject anonymous users. The anonymous `GetAsync` endpoint still returns the form details, including its questions, so it is not an authorization boundary for question data. | +| `HasLimitOneResponsePerUser` | Rejects a second response from the same authenticated user. This setting is automatically disabled when `RequiresLogin` is disabled. | +| `IsCollectingEmail` | In the packaged MVC response flow, displays the email field and requires a non-empty value when a response is created or updated. | +| `CanEditResponse` | In the packaged MVC response flow, allows a saved response to be updated. If login is required, that flow only updates the current user's response. A direct update API client must send the target response's actual `FormId`; the application service uses the supplied form ID when it checks these settings and ownership. | +| `IsAcceptingResponses` | Enables or disables response entry on the packaged MVC form page. | +| `IsQuiz` | Stores the quiz-mode flag. The packaged module doesn't calculate a quiz score. | -This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide. +### Sharing and Routing -- ##### Form +The public form page uses the `/Forms/{formId}/ViewForm` route. When login is required, this route redirects an anonymous visitor to the account login page and uses the form route as the return URL. - - The main aggregate root of the form entities. The form options, title and description is being stored on this entity. +By default, the share dialog builds the form link from the current request. Configure `FormRoutingOptions` in the host module when forms must be shared through a dedicated application: -- ##### QuestionBase +```csharp +Configure(options => +{ + options.HostUrl = "https://forms.example.com/"; + options.OnlyViewInHostProject = true; +}); +``` - - It stores questions of the form. This entity is dependent to form entity by `FormId`. +`HostUrl` becomes the base URL for links generated by the share dialog. When `OnlyViewInHostProject` is `true` and `HostUrl` has a value, the form route returns a not-found result if the current display URL does not start with the configured `HostUrl`. `OnlyViewInHostProject` has no effect when `HostUrl` is empty. -- ##### FormResponse +The invitation action sends the configured subject and body through ABP's `IEmailSender`. See the [email sending documentation](../framework/infrastructure/emailing.md) to configure an email provider. - - Each form submit is a new form response record. The form response has answer records. +### CSV Export -#### Repositories +The response page can export a requested response page to a `{form-title}.csv` file. The packaged download action requests responses sorted by ID and doesn't send the page or filter state, so it exports the default maximum of 10 responses. API clients can set `MaxResultCount` to `0` to export all matching responses. The export: -This module follows the [Repository Best Practices & Conventions](../framework/architecture/best-practices/repositories.md) guide. +* uses UTF-8 with a byte-order mark; +* uses the current UI culture and quotes all fields; +* writes `Date` as the first column and then the question titles in their configured order; +* writes one row per response and uses the response's last modification time, or its creation time when it hasn't been modified; and +* contains answer values, but doesn't include the separately collected email value. -Following custom repositories are defined for this module: +Response answers can contain personal or confidential data. Grant form-management and export access only to trusted users, and store downloaded CSV files according to your application's data-handling requirements. -* `IFormRepository` -* `IQuestionRepository` -* `IChoiceRepository` -* `IResponseRepository` +## Feature and Permissions -#### Domain services +The module defines the `Volo.Forms.Enable` feature. It is enabled by default. Disabling it prevents the Forms application services from being used, and all Forms permissions require this feature. -This module follows the [Domain Services Best Practices & Conventions](../framework/architecture/best-practices/domain-services.md) guide. +See the [Feature System documentation](../framework/infrastructure/features.md) for feature configuration and the [Feature Management module](feature-management.md) for managing feature values. -##### QuestionManager +The module defines the following permissions: -`QuestionManager` is used to manage the questions of your form. +| Permission | User interface and application task | +| --- | --- | +| `Forms.Form` | Opens the Forms administration page and its form and question management UI. It also protects form operations such as sharing, response inspection and CSV export. | +| `Forms.Form.Delete` | Deletes a form. | +| `Forms.Response` | Displays the response-management tab and response results in the packaged UI. | +| `Forms.Response.Delete` | Deletes an individual response or all responses of a form. | -### Application layer +## UI Support -#### Application services +The module provides an MVC/Razor Pages UI in the `Volo.Forms.Web` package. It doesn't provide an Angular or Blazor WebAssembly UI package. Since the packaged UI uses Razor Pages, it can also be hosted by a Blazor Server application that supports Razor Pages. -- `FormApplicationService` -- `QuestionAppService` -- `ResponseAppService` +## Internals -### Database providers +### Domain Layer -#### Common +#### Aggregates -##### Table / collection prefix & schema +This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide. -All tables/collections use the `Frm` prefix by default. Set static properties on the `FormsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +* `Form` is the aggregate root that stores the form title, description and response settings. +* `QuestionBase` is the base aggregate root for form questions. A question references its form through `FormId`; the concrete question type stores its choices when applicable. +* `FormResponse` is the aggregate root for a form submission and owns its answer collection. -##### Connection string +#### Repositories -This module uses `Forms` 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 follows the [Repository Best Practices & Conventions](../framework/architecture/best-practices/repositories.md) guide and defines the following custom repositories: -See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details. +* `IFormRepository` +* `IQuestionRepository` +* `IChoiceRepository` +* `IResponseRepository` -#### Entity Framework Core / MongoDB +#### Domain Services -##### Tables / Collections +This module follows the [Domain Services Best Practices & Conventions](../framework/architecture/best-practices/domain-services.md) guide. + +`QuestionManager` creates, updates and deletes questions and their choices, including their stored display order. -- **FrmForms**: Form list. -- **FrmQuestions**: Questions of the forms. -- **FrmAnswers**: Answers of the form response. -- **FrmChoices**: Choices of questions. -- **FrmFormResponses**: A new form response is being created each time user submits the form. +### Application Layer +The module defines the following application services: +* `FormAppService` manages forms, form settings, questions, sharing and response exports. +* `QuestionAppService` updates, reads and deletes individual questions. +* `ResponseAppService` reads, creates, updates and deletes form responses. -##### Entity Relationships![Entities](../images/forms-entity-relationship.png) +### Database Providers -### Permissions +#### Connection String -See the `FormsPermissions` class members for all permissions defined for this module. +This module uses `Forms` as its connection string name. If a connection string with this name isn't defined, it falls back to the `Default` connection string. +See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details. -### Angular UI +#### Entity Framework Core -Forms module doesn't support Angular UI for now. +Entity Framework Core tables use the `Frm` prefix by default. Set the static `FormsDbProperties.DbTablePrefix` and `FormsDbProperties.DbSchema` properties to change the table prefix or schema. -### Blazor UI +The module creates the following tables by default: -Forms module doesn't support Blazor UI. +* `FrmForms` +* `FrmQuestions` +* `FrmChoices` +* `FrmFormResponses` +* `FrmAnswers` -### Blazor-Server UI +![Entities](../images/forms-entity-relationship.png) -Forms module support Blazor-Server. Blazor-Server uses Razor pages and it's almost the same with the MVC version. +#### MongoDB +The MongoDB provider doesn't apply the Entity Framework Core `Frm` table prefix or schema settings. It configures the following collections: +* `Forms` +* `Questions` +* `Choices` +* `Checkboxes` +* `ChoiceMultiples` +* `DropdownLists` +* `ShortTexts` +* `FormResponses` +Answers are embedded in their `FormResponse` document instead of being stored in a separate `Answers` collection. ## Distributed Events -This module doesn't define any additional distributed event. See the [standard distributed events](../framework/infrastructure/event-bus/distributed). \ No newline at end of file +This module doesn't define additional distributed events. See the [standard distributed events](../framework/infrastructure/event-bus/distributed). From 30686e7fec1f8c4bfbb9b954d75bdc58dca72e00 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:38 +0800 Subject: [PATCH 22/41] Expand GDPR module documentation --- docs/en/modules/gdpr.md | 86 ++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/docs/en/modules/gdpr.md b/docs/en/modules/gdpr.md index 0a0bd7fb09..8689791963 100644 --- a/docs/en/modules/gdpr.md +++ b/docs/en/modules/gdpr.md @@ -9,9 +9,9 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. -This module allows users to download and delete their personal data collected by the application. +This module allows users to request a download of their personal data and request deletion of their personal data and account. -> The GDPR module requests the information from the other modules that reference the `Volo.Abp.Gdpr.Abstractions` package and merges the response data into a single JSON file and the personal data can be downloaded later by the user. Also, the user can delete her/his personal data and account permanently. +> The GDPR module uses distributed events from the `Volo.Abp.Gdpr.Abstractions` package. Participating modules collect their own data and publish prepared-data events. The GDPR module stores each prepared payload and later returns the available payloads in a ZIP archive. See [the module description page](https://abp.io/modules/Volo.Gdpr) for an overview of the module features. @@ -41,29 +41,33 @@ You can visit the [Gdpr module package list page](https://abp.io/packages?module The GDPR module adds the following item to the "User" profile menu. -* **Personal Data**: Personal data management page. You can request your personal data, list all personal data requests, download and/or delete personal data, and delete the account permanently. +* **Personal Data**: Personal data management page. You can request your personal data, list all personal data requests, download available data and request deletion of personal data and the account. ![gdpr-menu](../images/gdpr-personal-data-menu.png) -The `GdprMenus` class has the constant variable for the menu item name. +The `GdprMenuNames.PersonalData` constant contains the menu item name. ### Pages #### Personal Data -The "Personal Data" page is used to manage personal data requests. You can view the past requests, current status of the latest request, create a new request, download data or delete all your personal data and account from the application. +The "Personal Data" page is used to manage personal data requests. You can view past requests, check the latest request, create a new request, download available data or request deletion of personal data and the account. ![gdpr](../images/gdpr-personal-data-page.png) -The GDPR module is designed for distributed architectures. When a user requests their personal data, the module publishes two events: +The GDPR module is designed for distributed architectures. It publishes different events for the two user actions: -- `GdprUserDataRequestedEto`: Triggers personal data collectors to prepare user data -- `GdprUserDataDeletionRequestedEto`: Triggers personal data collectors to delete user data +- `GdprUserDataRequestedEto` is published when the user requests a data download. Collectors respond with `GdprUserDataPreparedEto`. +- `GdprUserDataDeletionRequestedEto` is published when the user requests deletion. You can subscribe to these events to implement custom data collection and deletion logic in your modules. See the [Distributed Events](#distributed-events) section for more details. > To see the other features of the GDPR module, visit [the module description page](https://abp.io/modules/Volo.Gdpr). +### Authorization + +The module doesn't define a grantable GDPR permission. Its application service requires an authenticated user, and list and token operations verify that the request belongs to the current user. The download action is the exception: it allows anonymous access with the short-lived bearer token issued to the request owner. Keep this token confidential and use HTTPS. + ## Options ### AbpGdprOptions @@ -81,8 +85,8 @@ Configure(options => `AbpGdprOptions` properties: -* `RequestTimeInterval` (default: 1 day): It uses to indicate the allowed request time interval. You can configure this property if you want to increase or decrease the personal data request interval. By default, users can request their personal data once a day. -* `MinutesForDataPreparation` (default: 60 minutes): Since the GDPR module is designed to support distributed scenarios, it should take a while to collect and prepare personal data. You can configure this property if you want to increase or decrease data preparation time by the size of your application. +* `RequestTimeInterval` (default: 1 day): Defines the minimum interval measured from the latest stored personal-data request. You can configure this property to increase or decrease that interval. The `IsNewRequestAllowedAsync` application-service method reports whether the current request is allowed. +* `MinutesForDataPreparation` (default: 60 minutes): Sets the earliest time at which the archive can be downloaded. This is a time window for distributed collectors, not a collector-completion check. Set it long enough for your event transport and slowest collector. ### AbpCookieConsentOptions @@ -93,10 +97,10 @@ Example: ```csharp Configure(options => { - IsEnabled = true; - CookiePolicyUrl = "/CookiePolicy"; - PrivacyPolicyUrl = "/PrivacyPolicy"; - Expiration = TimeSpan.FromDays(180); + options.IsEnabled = true; + options.CookiePolicyUrl = "/CookiePolicy"; + options.PrivacyPolicyUrl = "/PrivacyPolicy"; + options.Expiration = TimeSpan.FromDays(180); }); ``` @@ -121,8 +125,8 @@ The main aggregate root of the GDPR requests. This aggregate root stores general * `GdprRequest` (aggregate root): Represents a GDPR request made by users. * `UserId`: Id of the user who made the request. - * `ReadyTime`: Indicates the end time for the data preparation process. The `MinutesForDataPreparation` property of the `AbpGdprOptions` sums with the creation time of the request and this property is calculated. - * `Info` (collection): This collection contains the collected personal data of the user. + * `ReadyTime`: Indicates the earliest time at which the archive can be downloaded. It is calculated by adding `AbpGdprOptions.MinutesForDataPreparation` to the request creation time. + * `Infos` (collection): Contains the prepared personal-data payloads received for the request. #### Entities @@ -133,7 +137,7 @@ This entity is used to store the collected data from a module/provider. * `GdprInfo` (entity): Represents the personal data of a user. * `RequestId`: Id of the GDPR request. * `Data`: Uses to store personal data. - * `Provider`: Indicates the module where the personal data is collected. + * `Provider`: Identifies the collector or provider that prepared the personal data. It is an arbitrary identifier supplied with the prepared-data event and doesn't have to be a module name. #### Repositories @@ -173,8 +177,8 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do ##### Tables / Collections -- **AbpGdprRequests** -- **AbpGdprInfos** +- **GdprRequests** +- **GdprInfo** ##### Entity Relationships @@ -184,9 +188,9 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do ### Installation -In order to configure the application to use the gdpr module, you first need to import `provideGdprConfig` from `@volo/abp.ng.gdpr/config` to the root configuration. Then, you will need to append it to the `appConfig` array. +To configure the application to use the GDPR module, import `provideGdprConfig` from `@volo/abp.ng.gdpr/config` and append it to the root `ApplicationConfig.providers` array. -```js +```ts // app.config.ts import { provideGdprConfig, @@ -205,9 +209,11 @@ export const appConfig: ApplicationConfig = { }; ``` -The gdpr module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. Available options are listed below. It is available for import from `@volo/abp.ng.gdpr`. +The cookie-consent configuration accepts `isEnabled`, `cookiePolicyUrl`, `privacyPolicyUrl` and `expireDate`. Cookie consent is enabled when `isEnabled` is omitted; an explicit `false` disables it. `expireDate` is a JavaScript `Date` and defaults to six months from initialization when omitted. + +The GDPR module should be imported and lazy-loaded in your routing array. It exports a `createRoutes` function from `@volo/abp.ng.gdpr`. Available route options are listed below. -```js +```ts // app.routes.ts const APP_ROUTES: Routes = [ // other route definitions @@ -223,47 +229,55 @@ const APP_ROUTES: Routes = [

Options

-You can modify the look and behavior of the module pages by passing the following options to the `createRoutes` static method: +You can modify the look and behavior of the module page by passing these options to the `createRoutes` function: -- **entityActionContributors:** Changes the grid actions. Please check [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md) for details. +- **createFormPropContributors:** Changes the personal-data table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details. - **toolbarActionContributors:** Changes the page toolbar. Please check [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md) for details. -- **entityPropContributors:** Changes the table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details. -- **createFormPropContributors:** Changes the create form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details. -- **editFormPropContributors:** Changes the create form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details. +The personal-data page is also replaceable. Use `eGdprComponents.PersonalData` as the replacement key. See [Component Replacement](../framework/ui/angular/component-replacement.md) for the replacement API. ## Distributed Events -The GDPR module collects the data asynchronous to work that is compatible with microservice solutions. An event is published when a user requests their information. +The GDPR module collects data asynchronously so it can work with distributed and microservice solutions. A data request creates a `GdprRequest` and publishes an event for collectors. ### GdprUserDataRequestedEto -This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published to trigger all personal data collectors to begin preparing their data. If you want to collect personal data for your module, you need to subscribe to this ETO class and publish the `GdprUserDataPreparedEto` event with your collected data. +This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) contains the user and request identifiers. To include data owned by your module, subscribe to this ETO and publish a `GdprUserDataPreparedEto` with the same request identifier, your provider name and the collected data. ### GdprUserDataPreparedEto -This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is used to save the collected personal data into a single JSON file per module. Typically, you don't need to implement this event handler since the module already has an implementation that returns the collected data within a zip file containing multiple JSON files, with each file containing data collected from a specific module. +The GDPR module handles this [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object), serializes its data and adds it to the matching request. Each stored prepared-data event becomes one JSON entry when the ZIP archive is generated. + +`ReadyTime` is only the download time gate. The module does not track an expected collector count or wait for an explicit "all collectors completed" signal. At or after `ReadyTime`, the archive contains the prepared-data events stored at that moment; it can be incomplete or empty when collectors are delayed or fail. Monitor event delivery and choose `MinutesForDataPreparation` for the slowest expected collector. + +Before downloading, an authenticated request owner obtains a download token. The token expires after 60 minutes. After a request presents a matching token and request identifier, the service removes the token before checking `ReadyTime`, so an early sequential attempt consumes it. A mismatched request identifier doesn't consume the token. The cache read and removal are separate operations, so this isn't a concurrency-safe single-use guarantee. The download endpoint accepts the request identifier and token without an authenticated session; use HTTPS and keep the token out of application and proxy logs. + +A successful download does not remove the stored `GdprRequest` or its `GdprInfo` data. Define a retention and cleanup policy appropriate for the personal data collected by your application. ### GdprUserDataDeletionRequestedEto -This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published when a user requests to permanently delete their personal data and account. By default, only the `IdentityGdprEventHandler` in the [Identity Pro Module](../modules/identity-pro) subscribes to this event to anonymize the user's data and delete their account (using soft-delete unless configured otherwise). +This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published when a user requests deletion of personal data and the account. The GDPR module first deletes its stored requests and prepared payloads for the current user, then publishes the event for participating modules. -If you want to delete additional sensitive user data stored in other modules, you can subscribe to this event and implement custom deletion (or anonymization) logic in those modules. +When the standard Identity Pro module is installed, its built-in subscriber anonymizes the identity user's personal fields, deactivates the user and deletes the identity-user record. Other participating modules remain responsible for their own data. Subscribe to the event to implement additional deletion or anonymization logic for application-specific data. + +Treat deletion as a distributed workflow. A successful GDPR API response does not by itself prove that every subscriber has completed its module-specific deletion. Make handlers idempotent, monitor failed event deliveries and define how your application revokes active sessions and tokens when the account is deleted. ## Cookie Consent ![](../images/cookie-consent.png) -Cookie Consent can be used to inform the users of the application, before saving any specific data about the users. +Cookie Consent displays a banner and stores the user's acceptance in the consent cookie. It doesn't automatically block nonessential cookies, browser storage or tracking scripts. Your application must prevent those operations until consent when its policy requires that behavior. This feature is enabled by default for the [Application](../solution-templates/layered-web-application) and [Application Single Layer](../solution-templates/single-layer-web-application) Startup Templates. You can easily enable/disable showing Cookie Consent by configuring the `AbpCookieConsentOptions` If you want to override the texts in the Cookie Consent component, you just need to define the following localization keys in your localization resource files and change text as you wish: ```json +{ "ThisWebsiteUsesCookie": "This website uses cookies to ensure you get the best experience on the website.", "CookieConsentAgreePolicies": "If you continue to browse, then you agree to our {0} and {1}.", - "CookieConsentAgreePolicy": "If you continue to browse, then you agree to our {0}.", + "CookieConsentAgreePolicy": "If you continue to browse, then you agree to our {0}." +} ``` > Refer to the [Localization documentation](../framework/fundamentals/localization.md) for more info about defining localization resources and overriding existing localization entries that comes from pre-built modules. @@ -286,7 +300,7 @@ context.Services.AddAbpCookieConsent(options => **2. Add the middleware (`UseAbpCookieConsent`) to the request pipeline (in the `OnApplicationInitialization` method):** ```diff -public override void OnApplicationInitialization(ApplicationInitializationContenxt context) +public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); //... From 64e4286bc613a3360bbb8e7109d3a1b2eaa25164 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 23/41] Expand Language Management module documentation --- docs/en/modules/language-management.md | 121 ++++++++++++++++++++----- 1 file changed, 98 insertions(+), 23 deletions(-) diff --git a/docs/en/modules/language-management.md b/docs/en/modules/language-management.md index a4c2968c2b..1596afdbd1 100644 --- a/docs/en/modules/language-management.md +++ b/docs/en/modules/language-management.md @@ -18,6 +18,8 @@ This module implements the Language management system of an application; See [the module description page](https://abp.io/modules/Volo.LanguageManagement) for an overview of the module features. +The `LanguageManagement.Enable` feature is enabled by default. The module's permissions, application services and menu items require this feature. + ## How to Install The language management module is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it. @@ -30,6 +32,20 @@ You can visit [Language Management module package list page](https://abp.io/pack ## User Interface +### Permissions + +The module defines the following permissions. All of them require the `LanguageManagement.Enable` feature. + +| Permission | Multi-tenancy side | Description | +| --- | --- | --- | +| `LanguageManagement.Languages` | Host and tenant | View the language list. | +| `LanguageManagement.Languages.Create` | Host | Create a language. | +| `LanguageManagement.Languages.Edit` | Host | Change a language's display name and enabled state. | +| `LanguageManagement.Languages.Delete` | Host | Delete a language. | +| `LanguageManagement.Languages.ChangeDefault` | Host and tenant | Set the default language for the current tenant context. | +| `LanguageManagement.LanguageTexts` | Host and tenant | View localization texts and restore an override to its default value. | +| `LanguageManagement.LanguageTexts.Edit` | Host and tenant | Create or update localization-text overrides. | + ### Menu Items The language management module adds the following items to the "Main" menu, under the "Administration" menu item: @@ -53,6 +69,12 @@ You can create a new language or edit an existing language in this page: * **Enabled** languages can be selected as the system language. +Language definitions are global: tenants use the same language list, while each tenant can select its own default language. Setting a default language writes the framework's `Abp.Localization.DefaultLanguage` setting for the current tenant context. + +The culture name and UI culture name are selected when a language is created and can't be changed from the update operation. Creating a language is rejected when another language with the same culture name already exists. Editing a language changes only its display name, enabled state and configured extra properties. + +The module replaces the framework's `ILanguageProvider` with a database-backed provider. If the database contains at least one enabled language, those enabled records are the available languages. If it contains no enabled language, the provider falls back to the languages configured in `AbpLocalizationOptions`. + #### Language Texts The language texts page is used to manage texts in different languages. @@ -63,16 +85,47 @@ You can translate a text for a language or edit the already existing translation ![language-management-edit-language-text-modal](../images/language-management-edit-language-text-modal.png) +The page works with backend localization resources registered in `AbpLocalizationOptions` or discovered through the external localization store. UI-only strings stored exclusively in frontend locale files aren't included. + +You can select a base culture, target culture and resource, filter by key or value and show only entries whose target value is empty. The base-culture list includes fallback values from the resource's default culture and parent cultures. The target value represents only the selected target culture, without default-culture, parent-culture or base-resource fallback values. + +An edited text is stored as an override for the current tenant context. **Restore to default** deletes that context's override so the normal localization contributors and fallback rules provide the value again. Text changes invalidate the related distributed cache entry; restarting the application isn't required. + +### UI Extension Points + +The MVC UI uses `languageManagement.language` and `languageManagement.texts` as its [entity action extension](../framework/ui/mvc-razor-pages/entity-action-extensions.md) keys. The language list also uses `languageManagement.language` for [data table column extensions](../framework/ui/mvc-razor-pages/data-table-column-extensions.md). + +The standard Blazor (Blazorise) UI exposes entity actions and table columns through the `LanguageManagement` and `LanguageTextManagement` page component types. See the Blazor [entity action](../framework/ui/blazor/entity-action-extensions.md) and [data table column](../framework/ui/blazor/data-table-column-extensions.md) extension documents. The MudBlazor UI doesn't currently expose the same entity-action and table-column dictionaries. + ## Data Seed 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: * Creates language records configured using `AbpLocalizationOptions`. +Language records are seeded only in the host context because the language list is global. The seeder inserts missing culture/UI-culture pairs; it doesn't update or remove existing records when the configured list changes. + If you want to change the seeded language list, see the [Localization](../framework/fundamentals/localization.md#Supported-Languages) document. ## Internals +### Dynamic and External Localization + +The module adds a dynamic contributor to every backend localization resource. Tenant-specific `LanguageText` overrides take precedence when the resource is localized, and changing an override invalidates its resource/culture cache entry. + +The module also implements the framework's external localization store. By default, application initialization starts a background synchronization that saves the application's static resource metadata and compatible localization texts to that store. Application startup doesn't wait for this synchronization to finish. You can disable it for a regular application host as follows: + +`AbpExternalLocalizationOptions` is defined in the `Volo.Abp.LanguageManagement.External` namespace. + +```csharp +Configure(options => +{ + options.SaveToExternalStore = false; +}); +``` + +`SaveToExternalStore` is automatically disabled in a data-migration environment. Disabling it stops the startup synchronization; it doesn't disable reading resources from the external localization store or applying existing `LanguageText` overrides. + ### Domain Layer #### Aggregates @@ -89,6 +142,8 @@ This module follows the [Entity Best Practices & Conventions](../framework/archi * `LocalizationResourceRecord` (aggregate root): Represents a localization resource in the system. * `LocalizationTextRecord` (aggregate root): Represents all texts of a localization resource in the system. +The `Language` aggregate supports the module entity extension system through `ConfigureLanguageManagement(...).ConfigureLanguage(...)`. See the [module entity extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) document for the general configuration pattern. + #### Repositories This module follows the [Repository Best Practices & Conventions](../framework/architecture/best-practices/repositories.md) guide. @@ -102,7 +157,7 @@ Following custom repositories are defined for this module: #### Domain Services -This module doesn't define any domain service. +* `LanguageManager`: Creates language records and rejects duplicate culture names. ### Settings @@ -147,33 +202,38 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do * **AbpLocalizationResources** * **AbpLocalizationTexts** -### Permissions - -See the `LanguageManagementPermissions` class members for all permissions defined for this module. - - ### Angular UI #### Installation -To configure the application to use the language management module, you first need to import `provideLanguageManagementConfig` from `@volo/abp.ng.language-management/config` to root configuration. Then, you will need to append it to the `appConfig` array. +To configure the application to use the language management module, import `provideLanguageManagementConfig` from `@volo/abp.ng.language-management/config` and append it to the root `providers` array. The module's locale loader should also be registered in the existing `provideAbpCore` configuration so Angular can load locale data for languages added at runtime. -```js +```ts // app.config.ts +import { ApplicationConfig } from '@angular/core'; +import { provideAbpCore, withOptions } from '@abp/ng.core'; import { provideLanguageManagementConfig } from '@volo/abp.ng.language-management/config'; +import { registerLocale } from '@volo/abp.ng.language-management/locale'; +import { environment } from '../environments/environment'; export const appConfig: ApplicationConfig = { providers: [ // ... - provideLanguageManagementConfig() + provideAbpCore( + withOptions({ + environment, + registerLocaleFn: registerLocale(), + }), + ), + provideLanguageManagementConfig(), ], }; ``` -The language management module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. Available options are listed below. It is available for import from `@volo/abp.ng.language-management`. +The language management module should be imported and lazy-loaded in your routing array. It exports a `createRoutes` function from `@volo/abp.ng.language-management`. Available options are listed below. -```js +```ts // app.routes.ts const APP_ROUTES: Routes = [ @@ -181,7 +241,7 @@ const APP_ROUTES: Routes = [ { path: 'language-management', loadChildren: () => - import('@volo/abp.ng.language-management').then(c => c.createRoutes(/* options here */)), + import('@volo/abp.ng.language-management').then(c => c.createRoutes()), }, ]; ``` @@ -190,13 +250,13 @@ const APP_ROUTES: Routes = [

Options

-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 the `createRoutes` function: - **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. #### Services / Models @@ -211,6 +271,11 @@ abp generate-proxy --module languageManagement `eLanguageManagementComponents` enum provides all replaceable component keys. It is available for import from `@volo/abp.ng.language-management`. +The available keys are: + +* `eLanguageManagementComponents.Languages` +* `eLanguageManagementComponents.LanguageTexts` + Please check [Component Replacement document](../framework/ui/angular/component-replacement.md) for details. @@ -218,7 +283,7 @@ Please check [Component Replacement document](../framework/ui/angular/component- The Language Management module remote endpoint URL can be configured in the environment files. -```js +```ts export const environment = { // other configurations apis: { @@ -226,8 +291,8 @@ export const environment = { url: 'default url here', }, LanguageManagement: { - url: 'Language Management remote url here' - } + url: 'Language Management remote url here', + }, // other api configurations }, }; @@ -238,14 +303,24 @@ The Language Management module remote URL configuration shown above is optional. ## Distributed Events -This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module; +The module maps its entities to the following Event Transfer Objects (ETOs): -- `LanguageEto` is published on changes done on a `Language` entity. -- `LanguageTextEto` is published on changes done on a `LanguageText` entity. +- `Language` maps to `LanguageEto`. +- `LanguageText` maps to `LanguageTextEto`. -**Example: Get notified when a new tenant has been created** +Registering an ETO mapping doesn't enable automatic distributed entity events by itself. Add the entity types to `AbpDistributedEntityEventOptions.AutoEventSelectors` if your application should publish these events automatically: +```csharp +Configure(options => +{ + options.AutoEventSelectors.Add(); + options.AutoEventSelectors.Add(); +}); ``` + +**Example: Get notified when a language has been created** + +```csharp public class MyHandler : IDistributedEventHandler>, ITransientDependency @@ -260,6 +335,6 @@ public class MyHandler : -`LanguageEto` and `LanguageTextEto` are configured to publish the events automatically. You should be able to 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. +The module already configures the `Language` to `LanguageEto` and `LanguageText` to `LanguageTextEto` mappings. Language entity changes also publish `LanguageChangedEto`, which consumers can use to refresh their language list. See the [Distributed Event Bus](../framework/infrastructure/event-bus/distributed) document for details of the pre-defined entity events. -> Subscribing to 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. +> Subscribing to distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application or listening for events in the same process that runs the Language Management Module, subscribing to the [Local Event Bus](../framework/infrastructure/event-bus/local) can be simpler. From 9ea4bda02fdef61d294509eae7b171c7b4d946dd Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 24/41] Rewrite OpenIddict Pro module documentation --- ...niddict-client-credentials-application.png | Bin 84288 -> 296948 bytes docs/en/modules/openiddict-pro.md | 631 ++++-------------- 2 files changed, 146 insertions(+), 485 deletions(-) diff --git a/docs/en/images/openiddict-client-credentials-application.png b/docs/en/images/openiddict-client-credentials-application.png index fdcf4a2e5bae4198e7004213f59b474aec37c37c..3e2ce78ae2169f97f1d16540d838b8356ea926df 100644 GIT binary patch literal 296948 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 dsgqM=Cve8hvWa`Ga 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

>BUB&Hn`R^mxOd^v)hg)?T5jGh|K(Fx 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�Ox?j;k%pz@yfz(zR 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�zv9VJ7QOSbG zc1J|97yQ7tcc!FSGH%$vrEU%@EC5Xk8?fi017OEdpr#-yBH9 zg@b)~Fwn$n_~=JKkoBA{2zn2%>s#Gv@8C(>(2vBDam5Bw8rvGl95p8daq&k2e`X$@ zjMr+*gGDTxR3)x!7%z)al)nEvdXKJ>OFuYie0dWo}s+?oUIW`#x9>g3lRrV`-XLqS3ej^>x zdF_IUHjvItut}R3waX!WuOvZf>p)Ghil0a2IUkaMQ7#p>u7cIrTJ0rxip*LGhcU%Uosu$vu7@)A4#_cx3hC3QOOQZ4$NY()qJ4IP zulXkJEiEsydYt2$XpB#Ru9j=l+xA?!oDD+&;nP3F`mDYFxV-Rch-BvwNYRhKwVS;b zMkP3+UpXS;?8^BjWm4$rM=@cgaMshqjTYp^Y5JgO5p#LCEI$9W`Jq5ITq&+w{y8WI z%k|=K7wWJ5XpT^roPoRG78j2c_D|2FF5-`>Y%{cr?7JtgaGl@jyYmq)-Gz2XvZh;8 z3n%EPR!t)%xR60ypk=d1PWWfqIm+Al?p+C&dAB5;tH2xs51D-=>6kQOQ&y5u)=L^j zI&s{#Ie41pV=Ayo$Ag?W&bD+{6RUpkERY;H#AehsGF(@2D-v2yHqtlf)V#&uYaUtE zqEx6#laYhgz6>`}D#R1la~}_i)xoIr-ycPZvohb&cq&* zDUK4*8Oj~zbZ`C~IJmswXi}RY1=sESn&V=*$(Z0WOEAsx8IWa1036qt7p~wbfTC_guErn(jp+wu*QeBv5M2cT zWiZC$KKE*j_oz(HrgE5JtNQ_4z3Vm-^8q?l*(3HJAs2#K0N;9gwhWCIB_^}!VX-|c zO*)vGfxQ7FR$@sBgI%T;jsaM-^s>ADvKzOTcQTAYi4X}mA=MfHsUx5n!}J!C4+tF= zpWV1g0OWHnPX)HEg2e^a-%GP3$zpe_t=!M}aSkSiT<+nXMnT*F?IH-kQ_F|nW8QOW zrPHwLuq)^`$ptiY22RfB(1j{*uF><<%FkQyH7RY{-*5vlcyqgrNn)qPP%fVROe9vN zE_DJ~mrtsOn_2i$gW$@X529IFFkQx=NKKTu-dP!K=B-(753K)S_| z?Oc$#OugD3rpn#tPfOYWd8&0JXcp|cNCBjJpUpCSVpn^83%Sk!vs*JK74i1XP*-l! zUVs7rQMTODYRoE9GUOVNUPB{L+ihjKg036EPJx64G2BdjnPUOaQ#b^0{X{bAZ3)-V z`u!0$#F?+q<@{hJK-L8XM^Xs^&Cgr)s?Q9)qZmL+s+Oy~W^MWTsrx>)Cw`Cq?QUAa z$wM9!j|E)BEB-uHG-yb8WJE-9!dte1bRwwb3Xw9SP3FnYrdY-YKroxZKO+nU16BE(2lQAkZ50;SQ*wNlk;^N$w zpVJSo2 zU_BUHn>C7`<1Ard*M3Z+pdOF}@P32Y=TzaPv7Gpg#_lxlXHi$bo_^PJA3b@@`@OGi zDDjw`H-eg@fwC*IJi^l3cgZYbFRXO^(+F1hG5=B;4^$EkQmlf|~f(lHkDKiwE*S(H!cBTXl19@tJ>}!{#g~`v(S+-*sn( zvr^SVB&l#-VvU;(Ez} zCQTQf^^ML7NQ0Y+Q|`+r?mz5R-NAa(PtO+EeM3k!0f_j>3^S^;&P`(V@vIOJJ%Vh# z*K3*}z_y;HTJq6frDb&}P|AaduXfzSuRE`V`z{rJ(H!H_?zEeI1%xac2uyxP;0FsY+rDzYdn)W`B?wD|7bo zRsdupA71(Ufb{c$;e)qmwiyg~)eg6?mp8<=se0U#`Oa-K z7pGfMjah1Yu9@`wK4$dun|);%EjnCS?ai&4cdWEgTTuISwl|nA>rz;DX7-ldk@@>& zj6^b<8~3xdTs#AtwuBuZ;{v(1T<}tb`jF3x+bsHhAksQmBSq^oB&ml?DtK<=G{C*hw zPI%I3Wvsq_6+g%P#dmSi4Lr@XcEZx{)h|p$8qZj3btax!jtFiTYR@{HWLu4X@>(4E zaE9FL>VjY^gY}!9t0)%h`yJy51wnh%}D?d%KbG zj}VByr`yOJpda+w^5z2ILjY)%vhs(HPMuzp{G>9S3^Cp4F@w*iji$x}yqb*kShosP z6LfF4W0?ThA0YveUQ{oT{r<{*+q8|H+iC~j-EsqF?+Kme8K%Q#6R-yMzNRT$&R(psjP!@LF)t%2<~l94VX8litPsp&G|3q zakFaQ96b609)>h;KG&r)Q&j`u9t~$y*@rH_&fXWp(=h7Jca`;+ESQ-cmFW-x0@{KS z4MaZmM>9ZVV;ri|R&BT8I&Z8Z=Ohvh0Y%RusFmZ=e|y{Z)IdsRN4DCJLKh2E3o2?$9o)3jT1U z{brZ1R@LNyubS3_SottlYdq35h}C*JSl5yopPDH1Udz}*M)B)^l4ro6{aVR3*w^>S7>;UCG*QFMkT8&SbgeZ;G}bw<(|6KkrfTsqrX*&6lwTcqCeSoi+suH$ zIKsLHRAnwop>df{eNXt}PM~V=Wrvnc5uZWjxps9Gp^(5<<=P9Cd^d39F^7@Wwre#7 z2#T{_+Q=OzE6JFiK0?3o`Tc}#<&-nRUZqp~YZ&rqf%LvP5Fw@Tr$H`2#iq1?C^#>% zAO{vb0L}pGMM}NmQeU=%a4x7xl2BpnI1LDs+-xbhqyn8Kqfz!YXwzzWJ0YRvm{M?aQ9nb~#2 zTpFSw1KOggh^ty|#&NN!`2O^s6s42h0sXMG-vjkOE686@3o?-!6q#I{-T!A8Ha8ZC zCMvq2v;HM{@;?L8K(a#ynLv}VFpd0=`~1)7@O>0z4&bbldyb=b{ok$kpE2{wSiCPD zu-KV#`d_*s|N9FVqXg#idiMOKW%7>;UC?`iLHTACqnz@&PVpwA>*ZjVd;}uTZa9_$ zvOK|9T6R7aly%U);t;?zdD$Cud*r4Mck*mb{{*g>F;rdqjj+6&hZB?wFJon&;PHI1 z=IZ{WmLA6cdzTv*K;5?pnr`NJ|J%gZcTj*_5fDcOvS33%Yg;q3($dl(TU%T9IAvqa z>(5`F;vpA_8ed^R3>cbOGO}?51Si1-xdU%@I8BC1&Ey&SCI?1G5vKO=-qG6d;@al4 z@?+=xv|LUO^^L6@8R^Y;o&38Iy!tzkC5EEPA7@KyYjbC%`3VSTGjKu0_d-rq1rv+h zBc=j)@9+kyty@JMCp=g=r-iHN@MZ!2nPBO~zG?V`VufA>%M?y?i!J9a85 zAK*nT3mlF4j{O%8G5+=Lsbhe{ae*QyS%jUPog!I^j8qtrxrK?z@WezcJg7=lw*X=J zJuQt>P?hfjy&l;Iz+r%L1vePGV$6kiS$P7`o2j;5lga_5`F_^nIdtOh>b+q_D`nvpfKj)!ph*+*z&Cy4= z``VH+p#b`C!vj)LfC$qSE=teR@96~*x~14BpC9-cY|qQbComC7ud;uF4v#?oLXpee z(|BpEV8Iuq?+4+D&LX*i70z)tg|fzG^A6mX*>(=Yu=9}v*v)j(_5N7>Q0BgXI7-zS|7)$GS zq?u?^!L>pdgd05OZr8ihcXjo~48EN>vmLWG(ZhZ=IkQUs$zd!L#W}gYJ}b)v@E617 zk7loyFXe^K&K80pdJfQv;Wth;{fbe>`V$NnwKc2~NCBvy|Dvhy-x3IZ{ORVtP zMc!fzoP`TtfKPcWB#nQ12%!f<{{HC!>M-AAt;yNyc;5t+%3axeqO2bh*LCdBZa&8)~M`%9XuN}UnIweG^SWTF#u=LQk(K5?7M73_6Fns3`Gh^CKo~t z> zuX|nrJwjJTCkdE9)z&&CwBHirqhn$d^SNGbA|l<)GeDaMt_(KC%?t->31Hu~RUyzV z^@N%q;8ef8wfTxYXOOt&jxcC)+yY{wAtx8{;ooVL#jKiKR2Qn>?8W!H%-k6%w6m{; z`35%)$Dd{1{*yg9rbPi3(dyy8UXF5%uqRKqNCGGl^WkMs`1!FAWp{*JqREMNEVQ(s zAZX7org8q&GIU^IDZva3&M7SP4Jc4lXR-|_z)S}PADCAv2=`TBhJ(sG6?NBy4%|PM z4@b{3PLmN4sn|43z==j3>~gFUG;5?td)97SDiX82YX&`uAvb3j;KCsI^;8-?6!?zzPeYv~s4S{N;&~80DPS z?cgZKj7ZhN_BZK+$8eguSD6Mm13^OqsXVMac1I8U8GJ{kO6)-0Uq;y90(~PGu5(|9 z0QowtL=ViRoXn5;#V6tGX6dZ8X0&N0Hb*?N)k_)K8Umk*PpDYO1`vX5d`nm2&=gfO zu&HZaX{pIlPU|5H*YwEr{7&3S45Dg{3VnKBq>u$dNmRNeA|j%@0H1-84mwR%nAzIP z$wi&DwYT3>N}irsRnVL6O3BXw?w;pM%gAvx#aOf4#Y$*{ahCDz53oo}*sZrFZ%O~L zw>ARfg^7C*^;Z(({y6CFDnhbrx7Wr-=GsX#T308vekVM7)Pg&x%O?H~ zrz>5eJL5V2x}e;tN?L&eZ+0Gaw{!h*wGH`#^TW*^TM)q;eiakh(x%cO`Jr(6ob-DK zPKdB;03_v_P%qK*s~{5*0ma&wN!_{A)<(6QQ?(wrPLhj=Psrh#%>1$)`!uI-!u~Qw zu1%H>N}HSWyoTR|-hM3A4ARhagE>^4NF05LGbBd|n-W6&n%8^ySG5>#f_og6KoK0Wh^s;&L%@n=i} z%09vOLoK?Fj^m*C*;xfDQP8qK$x2RgRfa@!!XMK{#^&>?rEQr59<3+yX zq?|gVeO*4nu$PnQ$iB4~re$dlPR-}6v4=}iRS(Rl)o=gk+{4JCYNuWSPdq+4rujlj zI>pJtDJaOvT2xtII?(?dEmugX4bF5H)X5o&?kGa75|ZEir3nxmruWD zNdIqA;;yF;GMeMF6l3`J4SwLs@*y)HZc!56QC1Qt%BaqvrxK7OZeMO{oI%1hu1jO7 zw3gS?f8)BLrKy?JmM2=_{;6Teo&0lMMXjY{r`-0E6USWaXGXr(o|ZpMDwseWf`ZF_C|`BYW@2T~YlMP~Q@I66ju zHACm5X^$e`x{Ipjh4`tGPn)7j8BTw`AjwEkMU`_6OX-NNOMU`$`#UsWy?j3iD&#u3 zJaNlpawXG%Gk=zymwoj8j%u;3zH(Yk+6;>zD+j0AWTp9)yU8d$$rH#fKb`T|pHO(wTXphXl=h~t{tn^SsuV44elZ{ZhPNMd?2Zm8@y zo|3;loK8g)G|Xu}e&|)&>%7`bdK?)pOGr#QP*grJz``NGCdl%t!~BHjKxbuV8nL9f z)rFS-LecPg3qng%xY5x`odhT@T}$tTiq*hM5;FKN{n>6v?)yhv5Ru&^o+|gSioe!6 zxo?=ggy6KGfCp6z9+W>NQ&8NY8L@SbNl1J(1baw+>}fJ{bJ6;mdg32*1mY*sPIqL>RB(#B?!c28ZbS5TAd6@uBxn8vLK5$ zF`weRyjWZQJagR%b~DLOQ`&e^$N}c9CJMJ1qQDOq6v)}hFthpN58*!~L|-WozEjp@ zTOF899PM?LGPW>Y+CZn1*`~w3*YOXTy_y8%+WOD&W*_kfN8ScdE5G1aCdA5bTwgZ> z_?x3B%}J@jG`g^HkAcwQ5(|C@eip$_3&L3q3nuYH8-UB3J^Ef#cR)f-hqt-2BY#_f zZ^L1F!#*<;TsFG8y}kR$3)iWmIWx;ZTs&84U}^wJN5G)9OX)!F$)~Q)EnJ?>%*;-P zXVZd+_}IrM2UxPB&FWGN(&vLeKRSLB7dJE$@kdtX-6`XbE-q>OgM!zNIm6?!Dql@w ztYWZqF!rJBUt?nu%QY@Od(Ij0n5eh);giGfS+=M^+Qiyh=Z7ehJ#D1C&wMqH#EI~v zw%3PM)3q~5Nn+yT1y|xVg#h7VHjVo*_p9oAAMhmQO!@TfgDL3_Vh3P~-y&iQ6yF6I zpta;&y=vY|NtqW*Yt|7LZ|AS5dWA2~_${FCPESz1*FOK%Ayd5Te$CR>T7Umr<*1g- zOCxnf@e2Sg*abp~G757WCC_1~m&I%!%Gb@KKoYppF~Pwy+-M1=SH(MT&(a&+-?Zy( zZ5dArL?$SaxQ^^kW)TFf3hP8S8ZSB=wZ`u$JD}JcEeDJeZIy1>6`n5>PpcUjs`D)K zPQGi4vh)$2viw$e?}MFysi+i{v6yp`lXGC|}8pJ*Ga->agozgPm)z@%{M2d=Nd za&YX?iL7-JvwMYdkR8GiY6HpiZfaS($SVH)0GkahC;Ho}=!M%{FE@S%Gb-1Sp_`)< z^1sVp)Pg*mL4)C~dUph5W#EjOuol_0feM{g*3oF#A}Ywz zK37*?<02Dnv-fZ~QXU!5fr6CWR~b^jq#XEqsNx8E@|D z#)g#|m+@ne)crAkEsVVL!Hg>cHA|KoI!9&Ynkrtt4fO^7yoKTdt4sBDOyKTW~M6t1-E zUnHMELg--2aCAuP`yxGp>)_|2PGURc>uZ-5>BF8g z*~Ju9F^pX<(cVrS$+$*9rYCJHsRA?gbD>4Y_~>&yy9~H!#UR{-R@H1blf$NvgK9gX z8pRSWHwESmvA8#qs^#~`89LPSFEldDcy!lisHw1FQ{5bLB3e;*9T}X|fxzvcNoTJ4 zQqIU!alI7~-Mm=~CICRKSP@(SR~_Ty?UTwrPjq@nJD=Rep#iMS)k!%mt-^FIM>ojn zFef4xb}~&?w*=oYv9+fw*C=yL9{7HZa&GxX529TdB2DuJ)-+H2J1%h7Q{av8yu?4w zed9HE4H31D#f6O4lifA{+ECAv_;}FQ?>(*Z#FPH;*6=at{bE^}ixK5VFLG0LN z{msb@DNr1luU>OZ$>h`tD6gagX`E}jLKl*!)nIt6yeva==Jxk0>kKo-Ci$Ocf6A+^M!)BwE+WS>Otek4~Vx`Lm$>y0F1j>IM@NcX;^ z1@Zd3rpqT;PSry;KsP`gB;*F!LkURZ$hsdi+{SeUlId{A!B~J($ceV5;zi09;bLQd zYyvuuYyuP46D$O@4AD4P$DcjTgjU8|a_j(^rKZSUcuykG4Yr-cVG2uQ7e#ixgCf|q z3&@`CN7Y{f9XQGxkeVq#rxEAw$i`r|%>!iTr7y}+9goRPoQC3NZ09^LkX_U$$j&-c z3=)=VIz-#N+w5Eo4G&-8I$4T~M@?H@7hG9x`qMlXiG`A&2v1i zE41s0`!VS5j^j#Az~%W^J(4;=vTJ}i4OxrB7(XDulSCU>6|;;~lR4P{qymHh-L~oM zKU*9DOvEg}wtXzgB0KN*o%#eYM~si9_LwwkKT>V?B6TQI;dPJy>=tSPxd3q)YH1bo znqQc=BkOVTWx3~hsQD*NnrlA`9H!uMP(0nz#a?q-s^mb55CVvYS|gIKDgZ$`kqR+` zh^;3?ah9`J`cH@nKSxb3q;=2U<4RrsGQ9v=U=*lyxGI<6ytwO$h*@48U)?-h+?*0E z<#d8h&KT8~mtT!--^9q(cX!KV9Ne?896H*lAa%98v2d?C5*NDctYGjO6S@?GRv*0r zK?8`LS{PiaO?G(C1O)1?eARE_Jw4-sR%=*zre{V|jNaK4$n3hIiVXff_Ep{im}%IZ z=}mf$pY9SMqurxL!^WVPyY8f%uet~58yUCyKe_}P81PXz&HE70k}4ov0c|U*!XAsX zfonjBtNShlKw!>+wkKVrusJj)!KH5}0Ks%)e~gI35Vb3b ziIQ9a)g;LP!5gPOD}UZ7Y{WUB8WI!X4!}X}m#m4;UAIeKHb^vx-}sHh%~3@1UinS7bz|q}zxb721NzQ;p?3*y;gbsQv;vIj`A-j|& zS~jr_;BWcK@%IYs6GkT_XjIs08CTQF!DfyM+TRHQBvWaRBFr^kiEnvaTxE0Kmu!1f zY7Xf9xVE;<9_IBpX72d86L8m@`+@Y6>u#gwS(maqAXvpZ%=j&_1_I1--;XCcr7uT8 z6P$mTjiv_sfoEmQHJHO&psGe=&i5Wlmn0V=el=(?Nk-1K@a0ZBZ~zKK02meLd^c{49sg z*b#IY*^!Bfnnwbfme&f(CqMkMms4T-Zgu5}+cQDE+g0mhZ^~@V&%HZOh_=TiZyWa6 z_oydW7V$4TXoFtt|4yzHU0^IGk<=tvfq$?1<8M%;S;BMoyqXk)?DX{FaTT9+TZGZq z=8&jA7&mZZd(wx_KX(hf%eVT1Ui}n6=*AtJUrI$Pt!)g5&nAQp8%8JS+0n@A4obDyElj$eCF=n>X{Q4QsaUGQZ`@mx8%1_MNwYd!g!_c>}|96<I($|Gdndi>bVZe$_~}x ziX)2Cvq!_FrNszEp7)gcM z^lMnBf+401vts1sYCGhzeTCq)BD!@C$f7R7JL;UM(CG;B%!*2eY9#~hTx_sQP?~PJ zp?E-B!wu1QL*K|dzbT(|u29|1tN(I!9Eo41TfdE{8c|la8v(@Zgaiex&z7R_QHKnU zsc&ak9EO~AAI_)X)_s#0orKCwy8%iNB49X8g&=gI56)B~ zH016#EFvAzhhH0nJ(iu{^Z18C&aHlsSl4Te2UI9zg8U+=auZmOvb2JD-ZK&A0nK)2 z%_b{+c-D|j25kmuEMz1vY=ka8oQrk$h%eVU0=iLhN!h`#1`Mde@F&8CHJw^1FggGl zaFRPR96-P2UJz=inF=mEm0M_V*{<@vjqrRIS2{$fU%7_cvDm#f4z!U(nMv^S&7|?R zzB(FX(~ZqKnQ@r~BvwYUgtiDd_Y)3~JE1miO@IZdwZTjCs-?%VZUCp)eMP-SyVEIm zy(s6t%$9^G*4?1f*gFUa&3j7L%g3)^fHt2m4~mRsi2WfvBdGBsFbx%bHoVq3cD8rh zoe`_0jO)?eH{)k13oe1QtmwGJDHA=tBvk@6pqt(7KKIQu$i+48<q5D3yoPdb#M@5xAZ+v9<(K{~JSL{dMHBBER1aB==0+TOi-EcA=RdS4C> zQk?JDgF@6+3b=gHbm}@@TT@v_Bja}ragEXGYDrH2{!M|ZfYSa& zaYF3G>!&&&-rJL_FWGyQ8^<)SJ)38JzcxF4c>+MWHDTXuyq&w!tIMRv2`>=c@0}}m zZ`@BQ=xasWB-yan8$7)v5@l1?)%d-`$Q4GCZ?OGQV@e7Z2h!{4AEuFWQKx`^a5D73 zeT4aYJJABAV}1~^OYxanK(T~Q4uqmu!(`+;V-q@UB)l;;VyJp@%Q|aKGq0@grX@dN zV~gV9i{ZF<*M6Irm^(z0JeruF8$S8!u{7UTn|s19TYCDFaeU%!v>y()Qk{~LJe{c7 zX~@+$qE*$6WVsjueEW)~UTVzv2bQdc&Oy(VurC40Wh#Re+0QN~+ZhvW<1O5097BVJ zHQT(Z%M6)Phn}8D@9(YPj#J+i06w?--tS?|qP?3iPcrYakmv$)*lQ)&{dQ8Il)HaV zWbeM-mz!1WtYr$!=hvNndvzDbJL51j_h&7K0ijW=*ufww>Mz?kWQkzNx&?dWYawy* zN`egNbLkl{nbITj#d{|g3t!{8$jn_kdsaPt$arBlRq&TW-^Vm>nkgt;gW=oLHebGC z2pNiln{nrk#)NhblIj-#S-}p-xttu`BkLN61789jbLaEU`kTX`gQP1Fx$4b{EOq;_ zY2s)Tj+$`%$sp;zz2=dvvM55S2-=&Lg<&rmn-Wq8cQ8V*pF#Q2pM&k4uqOvTvxf$o zDv=U$@Bz=!X#ojD9fTf^>0OB!Kk3A`I8rSVhEFJ7vo>B`G>fX1sO!8U_0x3JgGh4S z*B?Z2dhSHIk)EqpDO$~Ezd0vt{z{x!qW{tta_Wl|2Ccdxkaxz&Hp-gbexS+P96J}* z7DES5)PqY~Qpr+!1-++9&@N$M+M2P6b}D)3FjHqhc92Z7xvNZead+W*rSA$$g8dmi zRwErra-)cECK5;v&1r`m8|)YuGq;Mo#FaViF4Mu2Qw7`VReGm;j}bR{+D4NQ23is| zp{Me4s4+L5q**niCC|o{JVUorB?qqq&!+1>S zAelMmTT&Tcp2*AC$j(eZFP_WZnq&F*-ur|=;>5-VOLRrlfcRS~jOG^N!iWX%1Z7u%D3Rr;SC!tKsI`00w|%wjfe zO)hYWrz-6H=e-)3&+IW9ZTAJ*%o#D`Kax}Rw!p-@wbS_=K+}@Nth~JZTzm&m$VDeE z#IYh|=fQJ=10Nmtz1urC-8Xxluj@Xn*hJmzH0!z+?&G&_iK%o&6f?Q*jrO@7V?Aq{ zfkHJ_c6g`^f_nDYlsgV=1MaehipjHRXE(1Z>Q-t@1gUH0JB-qOJw;r z#WE$fCFZ0qoq@)M@SdJQR_OB=Zz&0>wgTs=Zl~a4d!~K=Ai%;RtBr|69e2GAXuFOS z&UEpjTYr$0v!s_e1cXz&MYU+{09X>@afgL>8qlQSTgAX&-D1v)E<5S1x%LW(79_W9 zeWd(uBj!=+86X_7Q0xCkY1P{8V(Xqpr$=0)Jn3qT1CWaQX5l_o)FRfPRLgS-zxDee za3(yKQrrpt45GupW{hp5)LO#Oo@}U~SDQ<_V0ieqWe=J;W-+QnHsc9u;b03&3ENFl zefBW&8u84Yi`S5k+#wo!Fq?whLRhR0;X7nQX1%)#k||4oHl>^Bar`axne#0EC7Ku0 z=_3kV;DA5yR&Js3K{fOY2rn0I-0}5m&I;z#!So7~r-0rk+>OFv2O*`7GtLttU)r>~ zTJaTy`aZd{tN~f6bGz6 z>M>!yY$0>8{^Zna8*or?iuKpP-o=M<+4F2urbnmzerF@PX$!8PeFn+H9_un)ZASYK zk=2?`3#)VmG`}{~4H{rW1v=`e{kgqjzO>9iY5BJ3_Uyexs#r+`QekLHY^Ry*n%O52 zIk)ghNzk{{EN7}cZb5`>B`q^I>z~>j60-}cAkMmL~*?KrlAxmM#uggpdt`JFAh;i*7s;uW7 z9bXKS1bAqIr=0iR0EHgng2(Ug02DuL50GOflnEubiF{H3hjVG{F7z4baN`&A5WHdz zrdJ@-=2Cq-8rL#g>lYBaHmTKgiZ8UMWA6Bt#7tJ)Wb-L=cdJrO_tWh3S9Uwdr~vzG zbsS8adY}l6n$jSa?Vu5ngqS@{dc@lX>cRBnmQRn_m1WuHMos+S73X=V^z|;H{f(#m z56g=)uz)C#GDY+d=4%%Dwg@;($e zOn!4@v|e}>Da)r!I~JR7Ui@y$)&}cqzOfm%IT>lJA!k#|x-0DCnliD9q-BqqUj>AT&GGU?N%*034Z!!@3i1(`xd8xTm@$m{GKQ$)D896 zXZi_sALm0MpZ0NIyK}5)rk^-`FQh{b#=I2aT5g+9m0^Mz-WB5@^_? za!{_}*krZa3?UiT-0yWA)e3IGd8zd&ZcM%y1n*%&>SUKcWIv2)?|SNIm@WSxT)$}* zsI+2kkJ$|sW*a@z7o^`R6+$Bu6Z16+)PgF|OZ#?|NP5mq*PLull!xCC_=vLlW%1D5 zP)ojEWSwWdP^P17BG3FoUd+p{M-Lt!`zrI*}Q3h=Gq;sgq;dLX*t;8;MeFcaES%6bGeHt1xtI*Z?r*69FFPw zsuZPQ!^Nf%+LO0EER5G#5}uneF;4d5)e*HW7y=s8kljC6^M>fNG8b9kq=`5eHkg?w z+lZQALFs^CmMyM3@M-&~>~;kTZS>wZTc*o`r9Pa+sg8FyIu2+Lq70Uo$9+GLTDVM3 zRt`}RO5?3Oe8|wqSsSOm9n$8>@$F@3;|qN;c23^c+V_}wD43=W>RhsEi!5eg2liX( z7Vln(Kf}RfWMbq%Xxp26K1om+(PCnxVm+cIyTh!1;Q%>tm_2~Zwvdo#6Hgn`?-e)F zXPC)mZ6`Oku|I5lv2`QQEF^=yO`xdBM0S=#7p`5=6;;`dCuZ z@-X_Y90rcD$br)S`&%Rp%&-XT{v|d1xvmC%@1%DHck)WfiwyeQ@Y8gcKIALpIeZP& z&@P^&V^)V1GapxvO>uMe85m|r*1b#r=ms6!QgE)5g>iipiD!3e4ZHTEjo`7q%2^l3 z;yPI0^CdP*iR-iDU5WWu!~E`|xuXePUyBlDK~ftjx+=t>^7F(CVFj4m-9 z75%oFS*?pX&|ue5(7js4FW8H2Pm%1m1E@fLbwxG(!~d`22}N0q7R#TW9fYu6CLdqh8+bW`0NKw z??X+<8#Yn2ez8syjvLC1$6-xw#ger!%>MTJ?b^!ym(ZFr5i8u1w_RNA5-|LQH1>{5 z!{Zm~86@^!8}HGu*;q@+W~0IdiMwaJ@xS4_mE2}|Ok@X--W?5Kuj&jgoP+P3gvLiF5Frl`rQ&I+_}xP#Nj zU6pQe&oV^c=RGhJ#wwGU>bqz)Bf=%-Ztz>h^5k~eZ`;eX;a;=ZG#D33T5!RBiD_L_ z3T&@1AM5;+k>-%1E@2gy6y0F|Oq+t8kmVZ*j{Yi#h;JZ=!0e0*GXeLO-xAGFUjVtubR= z+=rCjexu6G;bBY4Qr&JDjU&e>x7t&42jvWss%pgL%vWJS@fbbH{xOmEAXo9}hf>jx zee;L@ZHQh63X!>Qb=WiGh zP(Ue3X(^HJ4pHe65RfiG1P*1gK+44E)=}|{=UEG{qtS#TJQbO zS!bQN_TIB+&&;0r%#4n>;LxWJp)XhFnFqO|=O+_K#C`E{Wzr6xwR3l9J_o=2C^{FF zRzarci~KeD$gn=PeQBKpJ9zXWNYfIZ8FZKT{-xghy)=O*7ZqA&7R#QS1W>5M|HE~SHb2>gQ zE(s3-xDh0_Mt4Y@bj5ve@O`iv(9fGnQuVB1*=$9P#mjybLil>&yV%d3qh06SnXmNR ziBs1=0_)^_lSs~?nda+=DD)>iz-3Ge<6vkA6DWgO=WOI3+7ZBot~(!+!O0+>&=G0q zItn55gh&dmysLWo6vA&6v;eP-*)(NLHw6=ngb&x8?A;+3wRaf>)W$cxIx9pW)9S19-k!7P-dzUc%3%x_=a@X*m>!VB8JA7e zbdqZ_fBY=8Aas=yKc43qwACb!F%V_JtQp1CPF~)eu~cN7=4R%LGf5cwT1P}kWE|xQ_Sjg5yG8V$xc5jgv!^wLL7*c$c52h*gO*tl`s{$)$9sC6YFM zh%XHe#&DK1@zB9M-~#vD;}Bh1GHLBt=S1cZ)$9A-VfHc1#9{-3)?EV1vNG*Rp(%-FO9pvq-#^4F6Vo=M$;!Cx zDF0i%FGM6z_B0xnQdg2f-?de=AC>vz7p*M;v*0KF97sleu!N}uEaO&Zd=0t4sTE6E z7oL(n&T=aGU=ioH`Y71>VIBIMhF9}up4k`ja1cvSY%3nW_m&^8DVgetOHu_vdqPL^ zPjT`TRKURoh5W=Y&?7EyhPER18_ue|)$)Q#jBJTJe&G6Z&nfn;Se)s}5PaU_T5<$CBGUCspJw1}MUC7!zX+&bvzQC?O<`^MUCKX-v)I<;^ z3E6&I6h+Q~h!#DeMw*)T+M-xF!=E`kG3>fRHX<1dlvwe-d43ql-bhqG?0VOe=Rs(< z1-}MqGZ*udua5p9-11@WA`wfJcI1=Vlnfs@9Kf<4!W#+av&6w_*>c$)Iyi9StSv+9 za?#xF5By#?&ByPYj6JL#eAS%wSjtnpWc<^9+rH;C{D9d`dAnxM;Jp=*CwWxy)M3*W zj9^BQ5O(xn=6lhY;h?aT5(r3d)Hu_?KjtDP^AX)a0DhuZXxp1CuDC1D1S)7A7h5TF zW+rJ0y-9n!ouAjkXu(*PeVe|9A09rft-^l$<35h}F}6z4Emg=AiKK*^J#}8ZgtmjD z_~W`|G#4O72cDL1oya0lIieqViBH6*3w!j!2$8{6d-lQSSbjq#$Kf2bqnq`o8%T&+!lhdY^av!paIWm-&_9wRm1pO1Som%qwG zdTPHO6i-Z3rwMNVSsG6B9r}hIo|JCoC`Fe{6_qGP!0x9gL)+_fwcPm4@2Amo)l?U1 z#MKA&sSjfwl>1lY8Zx@wxL8aa%Lx!j|PV;2OSEHp*|W^Eq)R zO-OW%i#@#Cjk0`Y95Lcu+c{9`pL&PL6>Wgn$7&fa0Q~;e^#oA|Y@WF4-C8oGUp%VS zgcOW5Sb=6h9`^Y?)q|ywMm)XGp7oyI&4H&vS=~JR=o^&K{VAvC%iZ0Y;y}$~N1Lkn z?EEmeGKr3hk598lgi6Gw#K_1+meR<>$;HWo-~xF+j4*Inf8-rI475R<6%%AknF5|I{vmu4 zk_$U8XYZpO#q(5{W80wEA2U)geL6oPPHGfRm)%<4_}(fi7SJ@b-;WMEMW;#`CD=p$ zabYe5{gWI6qok0Fiq=C`(MY>Ty0qb1^Fv8e($WJ8B%j#*{N8JG%w(@e|14*go56%# zCO^nTY30o?eibDr3DFm(Ka20pzL*bo2xwZTi?@BN^`0cURFW_fwk1A0 zQDZp-E~e}&JuUBY2Wo6AcvW>4%ooXkq9h)Fq?X=_$}<{_C2`#=_v-RcH3^Nnwjky(jZjYBqST_6>TF=)65L zU2R(tBN;+}s1pWFs=^!<0UL!wLn2OZ8o>!C^i2~#b56#xCw6k`p}3f zxLfNnM;}?ER-@q~+krIQZHQ49@KwV)l9{{C{Bt5*n4Xp|z7f_DX>6aTVG~EuJ69ai zhHKAc_XZ?-XUk1Cd|cLiIKQB7d|Af$U@m<;lvyXx!m;-90za_7So;dj{c4|!I4piK zK=52DgvUpPfc|ba4Gs@+1q*#jR`0?c@8CsE9)38<#Ze5U_QYJJD{4$^%&V%$2Q-d5 z3q`|+FU`>s*f`IhsblLu7{n{kxWvG_&!^jJ?wBpMB3XPOUdHEas{Q(M(nWJY#D zPck@BhoAp=$O5(kNuP(Q5mAr-ETy$WG z^yg}ym+yA(VB&0|>eg6~d+l}*-HE>?nA8+MJ0REwb4LELF3k1XL2A&Hoi+kAyF+WAZ|B{aOt}Wir?KCQX`n`@k^SND*_e=CO zjk;+=OROHtuBK=2IFZsZ%q>YZw3_^M9}!lgHGaGsUslTm{!!BC$mqze_hXJ6mI;lN zI9Z7LEsD&d4vaGftESuyBcOS#9E2F*{GbnERt?;ht0v@E(j}$`Nyh5WE**I~HI!$t z?Krl`rOVP}V&AlKumlw0n9kAy2|kZq`XyJRyJSi=^B zeRTZ|bK^KF+)ax(Mfsh)8*gDY3`>;mfn-KE#wgdKA3tO6@TVSq4}5G3sL;{wDn#2_ z>Kak;BP(|0cS_5|SC6C+dg4||SUC6WrP7Vn?s$w-QA^78oa;z|U;a)E1!9qa^zYkC zRz**FQaEqi_<4$6!Y%mddBlsAOkW+WRzh;ktC(aE^T3rYoA@%iZ&uI{h%E&gejJr3 z_u4K}u!5^B-1!`=ds3_?Z;#PEcp_i7>jp=XPeiGAHvF&i9_KB{(D50{TOe1s2&BZG z7R=L9WhE0!m>jIWWZ2cc3*cLKT6>b-;Xy^HCeVMiDYbZdJHi|-HlrK=*I6~6KS8dWVK(W_fbfUAXXHk& z5s~1kMXio&gW{y}vF*&0TM$4)4seOiEvft?DQ*@6;d0lg>%wV5u|@Xp+84iyQnr6j zIs5gTK|tmzme_7&lh9e{ERNO1N@B$5RE;On)JHM0IzCR@o^~E_(?cd~)cMa|jR4DW zptCkn{hL_qK`@3E938&rcqUP$lDLFVmi1IIwlWYw9J5u4p-+wCIEh6j>6WtgUw$TX zl;}kUYZTxoWT@Jmu06z^F%PJGdH1k(;OX2FQj>Cb40jVnmZD|kG(TD6MyC@4$#HY( z_qXYl8%9;%Yn@)4wPpIabNu3UZ*p%}yuZ1nDfrR%btr<};MVQt7}Nq{>isn~kKmnC z>gLIu6z+`LCm(mZNxQ&wrM_VVw#!bd8vKbPYW(Red56+1eS=Tv45#7>;y%h${PMrw z5PuvIIDP+kvwo?hqHK84#6^PDMd5x4p~gxdQVG;rx*dRFB?Vgo)6npyLOdqR4LJXQ64Fc(|#66;-TP9e+LDjE>>%(Vj z2q4@vST64M;0xNsFN4OO#kNCkr`Avm?0K8RiR2v_f9<)UyV+b9Q>osU*WD~O0DMETD+C_i{~*~1cOi_S zRxV+hc%$}9h82^`Ar#YB*nUWNE)6Nks_fzJC#(bEs0dD7i|T zZ6a~I?b7Oiq#4SelK+Pd+%2Wu%%Ytlv@`Enk!Ot>b+i(&m<)P3zXyoP2^R9E)hGx_dU7?Mu1z)ER z6AmvYkbxbCJHNHh%p^;NpV^kYqHm13E`5B_!`($G`h4q7Wg+Fo0xVGV*74b2WMWT* zp{9$c)LCJInCp8b*@Hm*(->iDI%t@{}_Gy#La_MLmDoFl}OGR{g zgW!ev_2`iP;*frw)R0(yNw#IRVqAT3r5yaVLqu~hq148OUfj;;F0YtWg+sT2=f-PzC5d#eBQ&vY^G2K&zDS!Ip&xaWMsOS zIPcYxC`CY7($ev%I&E?^qsnR(^t0D~zF}=5NKUMMpcV0DjOA<`Rj^!g)rXV`<8q5r zT#mN2Xsc-+v>AbH$KM(yslfw>(aq(jclj?-^ZTV?oI#MXtC17YM9g$`Wu@I9nx6n? zL^rCddqGMY-RK?5Ga-W<@-{DUZ&R5Ab9n5d7`$13>PK7(3rUP393s;pa!eJjngFi$ zd;us@RX=0icI+*j!9HYH-Mr9qy6ZUhD#3nQzOZtCV6RwKOwXo3Js&eH=2;h>6>~<~ z>SYxH{wGWu;aeI)D1kFjx;|}nXN29%F)ta$oEEvC4jIhSZ<4P_X09P^`s!Op!0t%V zgK;)Oqj#Bj4g}~PDeP8YwMX#WStb3NxJugQ#cbS)Aljv?ZOz@2q3g3Pj=IxrwSw;^ z=M+AurdfN~7Sm4Y3bn@qfi>sT#18Eg)h-(p43!uA{iov^Won$<_hZZt4PN;q)1eOj z1&Dj51yf6z$Nonk0oLGqO2LC}->7p*MTt&cR#wba6c){>ZE~>jl6h&jn})J7Zrr(K zg;-+rKy4A@XwRrc3le;O7`lAsXSrUlXIOL&;OT11>vK)h`a}F`N3VH{vJ%_t9}dp@ zV;l~{Ih}^>u|o$BA2ic-=sMsxeSa-HM*4j)^^xdP51&=->+f_Uix?Z8Ho;+%n2dUS1XQUrXlZV@}I%<2zber?J(ggEYdp}KX5Un zlh3R{yM!}!^!mhBT!xc*7q;r^J&6uT5B}~2v10f zdjik#kzY?-zPpka-PhV*TD}zGm7Iy^r98LZuZ0NCbra}2L^UrsFA6;>jO-n!wlct8 z^$~7;PRq>0!o$pK-!%$yZGWT5?K=8y-py?E2U>;?)bsx7k{drg@s#Pn^)0spmr!It z!9MzA`PTyDoMDU4Sf0e`s#W`aUKw;|h{En6_5X&CRucM%H_xgX?A0=*ZaeKIm>s8E zWmPvtgN+#SU~u(aFZNU3s^<01y{xo(&O)v&Gb;ou2ge4i04kZLx9b>BgP<|GO>mp> zHh^C!H1kQJ{M)6S>Rk|PLC)~NE46>F-R}TvM;*);{g=Lpm3Bj8r)P4b9PtnH;+Phc zPH*byi)fGhI%Q(U<)+`>Es^o11<1a*2R|8x%RSY=C`rw)&-Ii8ClEp>%@In{hGtI! z=70LUt&%IF*F64yllb*Xl!udhFM^81v-Yd;9$^`gwugUI zU;{T`h7>;ep802zn6g?>q~;+ugg*ZT+Jt+AR48E#EcdzT(<;nQ!o_#mL&(B6?3{=J zj{c<987Z@9^7uo#pMRKg^k-Ze0~snqi>M9)-4CH_vK+c3?fg3mB2AlD651;^zU^Y& z&{|NC+%!;R=J3q?LDe5SO_{;{Aob*sJ9+i5SqQrKkfD_-o#89Bb3`lPZz>okyo?i; zS?t5r7_3s8#*t|$Gj2N>vomBGMz$JP0GnYa~9Q5zg~}ZOAA@a#PK*6 zubxv7DNbEz)>%{=qit*_iRWU6T)yHsUp6S7b=Bi;Gm3U#A#NJB7BX4DQMi+tE9fK^ z6#4;TMo)!J6LtiG6EfCWY#&LE#`frED`ytG86ufiAMrKZ&tY*~khtUzCXvqrf4w9nq zf;}=yTDU(fHEi$=Z@B@mY&u1=e0b8MZ9RzI(98Ie}; zBIAowY{JLnjzG1p;sGBuHa42&k9~TJaW-ao_QwKLX1KW>haU0X(d9;1NV}7}?nI`M z%<7>rrA)`Af)h1ZSdX-#%n@~EVAqn9_ZNf$A0|9rg7oND3~yi~rMrMv!T}Y}W#4`L zyp=$s*A~ikb!qJc6Hmm8OL5GRKB|;RkJtI7N)BF3;HPW-4&rtz{#J*8X&T!7FSBL9lENR`Wjz9afkCF*d(xh_q zo41*QG~`+J*`RDijN@ERY^p$AxcQb=cF9v;%m0qML1EBS8tISsf zfG<+3cWB72P^6-|XQlC{S-ACpb;`!+={SH27*@#}?E)%JIY~+SV|_ifsTPwIFcVfh zHBcv2h(e+H$6?lrQ3Ti4>L9Qc@b{rmanl0}l-YB+QKD0_QL5E#$5y!h1QPOq^1R;x zq)l;gab(RBMLl%VRJ)w{2a`|Hlr7HPo8IW!Wy20qCb%s=hiizR`A(G~I zAdSqpf~SU+iNAbI`I!w7F)`IB5gt*_JDQ=7l!=6Rc+pW)7SqQMCXkG94--vR8HOz*cZ!%N;?l1}tX`%K=_FQf{)-1u&9mH>Wo1Gv9sf|e8hmIG!2 zu&(VZ#gi~KVc?yumL3~ zvrBvFH!_S{JxWFVmc3dGG+407`23*5x+GnYG>~lD#yD+wM^c73`90-pOW_IaFRbTb zwzD#}EOvm}ZPV+UvNro0p`KD4jV9l2=LN7Z9i&T9Y!t)6qGZCo6>;>^84k zb+iS|)|C$HxruF~5-m7CeD%l#A^r!?rzf}T4hFoZR^ry=xab6Tx}6M|b@Z8@ z@X0SkpF%38ERdhy6n%~UDG~1P)gt^TaJSZ!!~THbdY{2{@2jRBJ@h7*6i6H04sxAB zox13#cO3>27gz+KESQ@o?IQdZKwjUp-C}@}hW+wLHl2bme%Dm*T`d5YCj6P7*)q-> zeaWvPH5}G&3IYQO2sg!RZ_1Hd_VOKXO@X03K-JMOl*^L~Ptq_og2oa#ST_iqsRdML(_hN0urg5kOyFcH%e}{9_3K@8SYsl36+!ikXqy zm=I&=6edahTnFLBX^hfw;7kC<-OZ% z0u}yIOk;L{2{mK4>&N$(%S~$#iSal(FS?IQ(sRcvyN`CrT>mqRmp?d!glsWgoyS(T zFM7Qj=os;&z-2>TnY73SsBY5czjEWFNKU>f<9{G?ePEdYC3t4Muwo0;>Q`7g0r2cy zdK#a*7GzfKEDlu`pe0BX8^8Vvvci4_l7r$M#}h-!xhR=zpv@?tEl(jms0mM zh=r>*1$A{CZwWYZbH&jMgmH!K;H8Jc9Br07*oC#f3_d*tjsRh=^Gs>N#W!N7p>Ukb z71PVs#H)_PX@||xnac$H$#`s{qKoy#E2H#?*mwc6pJvcToDq1?7W=z*{PJNOIS+?WABE(CXI<<_tJ%ljA!UwN(xwB1sq z!F0`#WEqU0KJMoIu)omjw8Ao~VadkD9Bo0KvH+d7oxQ|y-NU&_m3zktl-Cj#5ut{W z(vo^4rPcG6Rn(Y|i%U%L{HEyq$Mbn0)uKj%G{CI1$#J(fvdMObJ4oM}i-~N6hw>`)|Hf$~;QV1hnzt4E-s~j1rhX((bYm5iSRq<-os#yvfEpqGkU5^B= zR|*C877`u$%Vf^4R(a3YE1b49HTDN0`B#dSnER_$dX#9u>qk1ZDkld5GZzbqPLLq} zP3}%X8L1V}Rr6|0&-(nQTs6A-^9+G31)))Fe6U8QQsHo@BdWKhg$+@)*aNW!C>WHi zw8exTd+1{NRXfy5tucc0ZA{w_eumvS*E_e2dma#JDa9`1`^$v{(Y}W>O^_wjo zzGap{1XKqVB_*flW(AAQFHFvs3 z0WaV5c}1_QA*gP6`(0-F&RJu6LBdwHnYnHT@yo$aEp-U2F_Z^zCYP<~>qzgT9Vk_WNJ zb~|77%wF`|=$y2UuV2499&M4n*Y}!=K!hlKrHuO^vtal|yp5aahEo~pFDy1*mwKf> zFWZx-KaeN{a-_%hfrBqIs%}fTn%)B>uuKmf&((B$yO8X@$x7XGGkDN;zZ&q6L6J*2 zrhb0KQBwxe5PT@9M0lpzEu)Tijr>teO=y=gC@Z9;5^Ve7o6>~}%D&NHdGl;vm}m-I ztcl2!Owv`*maDeNnJ=l%PC%VeC&A9=6v)@bnNhi0V|AO=v!`}6Q^7@&6Kd^mFUb_# zDM_e2q$93eiP{_Z?)+9j_|*Nuh2!wkgBHp=CcANIX@wyNV=BGC`mBYm&lL8OWj4Ap z`ZHvyMZtuUl@D`-B&Tk#6+e0!XuDB=0@N00^}^ApbUr__@XNuX(D*>l z!jfd@W8bqShQU|%A@mY#IA3@QZ%7@#~JR8a01`-ZZ$-p84`d!C-c~0|h4hvnZ_kiXo zi#;5!HV)hztlu05!idBJ&^kZJhNdY_4uB&0_iV4*Yw?j=re<9hh*k1+!0e|hvo38M z_28dbH04)gL3|s4@>foe;b9&$F5`S)=_d}>@GHSj-WR^$*8dK4k^dpxI^GBI;}`Cv zx;0#iGJsN47p?fC2XO2K4cQMp1$vj1Os_sNft(8V5jl120+@Ap?Kf>> z(jOk#ti;F`j@pH%5SSa36Z1PoS=s5?t<5DjrFCpei$)ypOiKiI?|vQ}6rrSS4I+s4 zLuao?1I14atRI~$2Oro2DSX5FFRVhS2Rw(24I)?v;}$R0#KD-8as13I{QRvgzTtj* zOc_FjYx81>-hDQ`J04_Bs@@_w)1YdF9c#yf`k8IT6@AOX({NjxDy$L9bp7gz*Q;T( z+J?;CTci(B0-#a5D8o5TWM#W9=cIHoaURIY0IYPT}~6PrJ$S8tid1ODTg|K%}>`vQ~A)RHJhhnSRI-!Bg05^d;g zQ-F&ZY9?`>sWuf@XF+5}r0rl1k4Gg;0LnO0aRPU?9&&dI=Y&{Ik~4A$%Htyoa>!Og zK72*sGs##k|^m~-|##^LODvx%qK5Bc^%% zA6*tVD;@_0ZjZTKO*P6TD=;Vt?~zy#Gz zv0zXID>rwU>FmMJpXBnbVsSy;w1NK8w6qu^IN%?17ebbLR-pkv`NWePg*NP!$)5Bn zS?4^tY$ASM!nQYu1_VN9Q^=ZB1hhO}%VUJ0Dx`bx|D

-Q?%;sH)!P=l%+J#68iP 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 z You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. -This module provides integration and management functionality for the OpenIddict library; +The OpenIddict Pro module adds application and scope administration to ABP's [open-source OpenIddict module](./openiddict.md). It provides management UI and APIs for MVC, Angular, Blazor and MudBlazor applications. -* Built on the [OpenIddict-core](https://github.com/openiddict/openiddict-core) library. -* Manage **Application** and **API scopes** in the system. -* Set **permissions** for clients. +The two modules have different responsibilities: -See [the module description page](https://abp.io/modules/Volo.OpenIddict) for an overview of the module features. +- The **OpenIddict module** implements the OpenIddict server integration, token validation, stores, persistence, token cleanup and low-level configuration. +- The **OpenIddict Pro module** manages the applications and scopes consumed by that server. -## How to Install - -OpenIddict is pre-installed in [the startup templates](../solution-templates) from version 6.0.0-rc1. So, no need to manually install it. You can also migrate your existing application by following the [Migrating to OpenIddict Step by Step Guide](../release-info/migration-guides/openiddict-step-by-step.md). - -## 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 [Identity module package list page](https://abp.io/packages?moduleName=Volo.Identity.Pro) to see list of packages related with this module. - -## User Interface - -### Menu Items - -The OpenIddict module adds the following items to the "Main" menu, under the "Administration" menu item: - -* **Applications**: Application management page. -* **Scopes**: Scope management page. - -`OpenIddictProMenus` class has the constants for the menu item names. - -### Pages - -#### Application Management - -Applications page is used to manage OpenIddict applications. An `application` represent hosted applications that can request tokens from your authentication server. - -![openiddict-applications-page](../images/openiddict-application-page.png) - -You can create new application or edit existing applications in this page: - -![openiddict-edit-application-modal](../images/openiddict-edit-application-modal.png) - -##### Creating a Client Credentials Application - -Use a client credentials application when a machine-to-machine client, automation, or MCP client needs to call protected backend APIs without an interactive user login. - -1. Open **Administration** > **OpenIddict** > **Applications**. -2. Click **New Application**. -3. Enter a unique **Client Id**, for example `InternalAutomationClient`. -4. Set **Client Type** to `Confidential client`. -5. Enter a strong **Client Secret** and store it securely. The secret is required when the client requests a token. -6. Open the **Authorization** tab and enable **Allow client credentials flow**. -7. Open the **Scopes** tab and select the API scopes the client can request. -8. Save the application. - -The application screen should show the `client_credentials` grant enabled. Use the **Scopes** tab to select the API scope that the client can request. - -![openiddict-client-credentials-application](../images/openiddict-client-credentials-application.png) +See the [OpenIddict module](./openiddict.md) for server endpoints, flows, certificates, validation, token cleanup, aggregates, stores and database configuration. -If the client will call APIs protected by ABP permissions, grant permissions to the application after saving it. Open the application row's **Actions** menu, select **Permissions**, and grant the required permissions for the **Client (OpenIddict Applications)** provider. +## How to Install -Request an access token from the OpenIddict token endpoint with the `client_credentials` grant: +To add OpenIddict Pro to an existing solution, use ABP Suite or run the following command in the solution directory: ```bash -curl -X POST "https://localhost:/connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=client_credentials" \ - -d "client_id=InternalAutomationClient" \ - -d "client_secret=" \ - -d "scope=" +abp add-module Volo.OpenIddict.Pro ``` -Use the returned `access_token` as a bearer token when calling protected APIs: +See the [module page](https://abp.io/modules/Volo.OpenIddict.Pro) for an overview and the [package list](https://abp.io/packages?moduleName=Volo.OpenIddict.Pro) for the current NuGet and NPM packages. -```text -Authorization: Bearer -``` +If you are replacing IdentityServer, follow the [IdentityServer to OpenIddict migration guide](../release-info/migration-guides/identityserver-to-openiddict.md). -In non-tiered applications, the OpenIddict authority is typically the backend host. In tiered applications, use the Auth Server URL for `/connect/token`. +## User Interface -#### API Scope Management +The module provides the same application and scope management workflows for: -OpenIddict module allows to manage API scope. To allow applications to request access tokens for APIs, you need to define API scopes. +- MVC / Razor Pages +- Angular +- Blazor with Blazorise +- Blazor with MudBlazor -![openiddict-api-resources-page](../images/openiddict-scopes-page.png) +Server and WebAssembly wrapper packages are available for both Blazor UI families. -You can create a new API resource or edit an existing API resource in this page: +The module adds **OpenId** under the **Administration** menu, with **Applications** and **Scopes** child items. The menu entries and all module permissions are available on the host side only. -![openiddict-edit-api-scope-modal](../images/openiddict-edit-api-scope-modal.png) +### Application Management -## Data Seed +The Applications page manages clients that request tokens from the OpenIddict server. -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: +The list shows the application type, client ID, display name, client type, consent type and available actions. -* Creates standard identity resources which are role, profile, phone, openid, email and address. -* Creates applications. -* Creates API scopes. +You can create, filter, update and delete applications. -You can delete or edit created applications in the application management page. +The create and edit dialog groups general settings, URIs, authorization flows and scopes into tabs. -## Options +The application form manages: -### OpenIddictBuilder +- **Application type**: Web or Native. +- **Client identity**: Client ID, display name, client URI and logo URI. +- **Client type**: Public or Confidential. +- **Consent type**: Explicit, External, Implicit or Systematic. +- **Credentials**: A client secret or a JSON Web Key Set (JWKS) for `private_key_jwt` authentication. +- **Flows**: Authorization Code, Implicit, Hybrid, Password, Client Credentials, Refresh Token, Token Exchange and Device Authorization. +- **Endpoints and requirements**: End Session, Pushed Authorization, PKCE and enforced Pushed Authorization Requests (PAR). +- **URIs**: Redirect, post-logout redirect and front-channel logout URIs. +- **Permissions**: Allowed scopes and extension grant types. -`OpenIddictBuilder` can be configured in the `PreConfigureServices` method of your OpenIddict [module](../framework/architecture/modularity/basics.md). +The management service derives the OpenIddict grant-type, endpoint and response-type permissions from these selections. For example, enabling Hybrid flow also enables Authorization Code and Implicit flows, and enforcing PAR enables the Pushed Authorization endpoint. -Example: +Redirect, post-logout redirect and front-channel logout values must be absolute URIs. -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` +#### Client Credentials -`OpenIddictBuilder` contains various extension methods to configure the OpenIddict services: +Public clients cannot have a client secret or JWKS. Confidential clients must have at least one credential: -- `AddServer()` registers the OpenIddict token server services in the DI container. Contains `OpenIddictServerBuilder` configurations. -- `AddCore()` registers the OpenIddict core services in the DI container. Contains `OpenIddictCoreBuilder` configurations. -- `AddValidation()` registers the OpenIddict token validation services in the DI container. Contains `OpenIddictValidationBuilder` configurations. +- Use a strong client secret for `client_secret` authentication. +- Use a JWKS containing the client's public signing keys for `private_key_jwt` authentication. -### OpenIddictCoreBuilder +Client secrets are write-only. The management API never returns the stored secret, and leaving the secret empty while editing keeps the existing value. Switching an application to Public removes its stored client secret and JWKS. An empty JWKS value explicitly removes the JWKS only when another confidential-client credential remains. -`OpenIddictCoreBuilder` contains extension methods to configure the OpenIddict core services. +Keep client secrets and private keys outside source control. The JWKS field is intended for public signing keys; never paste a private key into it. -Example: +#### PKCE and Pushed Authorization Requests -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` +Use **Force PKCE** to require Proof Key for Code Exchange for a client. -These services contain: +Use **Allow Pushed Authorization Endpoint** to permit PAR requests. Enabling **Force Pushed Authorization** both permits the endpoint and requires PAR for that client. -- Adding `ApplicationStore`, `AuthorizationStore`, `ScopeStore`, `TokenStore`. -- Replacing `ApplicationManager`, `AuthorizationManager`, `ScopeManager`, `TokenManager`. -- Replacing `ApplicationStoreResolver`, `AuthorizationStoreResolver`, `ScopeStoreResolver`, `TokenStoreResolver`. -- Setting `DefaultApplicationEntity`, `DefaultAuthorizationEntity`, `DefaultScopeEntity`, `DefaultTokenEntity`. +#### Token Lifetime Overrides -### OpenIddictServerBuilder +The **Token Lifetime** action configures per-application overrides for: -`OpenIddictServerBuilder` contains extension methods to configure OpenIddict server services. +- Access token +- Authorization code +- Device code +- Identity token +- Refresh token +- User code +- Request token +- Issued token -Example: +Enter positive values in seconds. Leave a field empty to remove the application override and use the server default. These values change only the selected application; configure server-wide defaults in the [OpenIddict module](./openiddict.md). -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - //Set options here... - }); -} -``` - -These services contain: +#### Generate an Access Token -- Registering claims, scopes. -- Setting the `Issuer` URI that is used as the base address for the endpoint URIs returned from the discovery endpoint. -- Adding development signing keys, encryption/signing keys, credentials, and certificates. -- Adding/removing event handlers. -- Enabling/disabling grant types. -- Setting authentication server endpoint URIs. +The **Generate Access Token** action sends a Client Credentials request to the configured OpenIddict server. The action is available only when all of the following conditions are met: -### OpenIddictValidationBuilder +- The current user has the `OpenIddictPro.Application.GenerateAccessToken` permission. +- The application is Confidential. +- Client Credentials flow is enabled. +- Every requested scope is assigned to the application. -`OpenIddictValidationBuilder` contains extension methods to configure OpenIddict validation services. +The action asks for the client secret at request time because stored secrets cannot be read back. A JWKS-only client must request its token outside this UI by creating its own `private_key_jwt` client assertion. -Example: +The application service reads the token server base URL from `AuthServer:Authority` and sends the request to its `/connect/token` endpoint: -```csharp -public override void PreConfigureServices(ServiceConfigurationContext context) +```json { - PreConfigure(builder => - { - //Set options here... - }); + "AuthServer": { + "Authority": "https://auth.example.com" + } } ``` -These services contain: - -- `AddAudiances()` for resource servers. -- `SetIssuer()` URI that is used to determine the actual location of the OAuth 2.0/OpenID Connect configuration document when using provider discovery. -- `SetConfiguration()` to configure `OpenIdConnectConfiguration`. -- `UseIntrospection()` to use introspection instead of local/direct validation. -- Adding encryption key, credentials, and certificates. -- Adding/removing event handlers. -- `SetClientId() ` to set the client identifier `client_id ` when communicating with the remote authorization server (e.g for introspection). -- `SetClientSecret()` to set the identifier `client_secret` when communicating with the remote authorization server (e.g for introspection). -- `EnableAuthorizationEntryValidation()` to enable authorization validation to ensure the `access token` is still valid by making a database call for each API request. *Note:* This may have a negative impact on performance and can only be used with an OpenIddict-based authorization server. -- `EnableTokenEntryValidation()` to enable authorization validation to ensure the `access token` is still valid by making a database call for each API request. *Note:* This may have a negative impact on performance and it is required when the OpenIddict server is configured to use reference tokens. -- `UseLocalServer()` to register the OpenIddict validation/server integration services. -- `UseAspNetCore()` to register the OpenIddict validation services for ASP.NET Core in the DI container. - -## Internals - -### Domain Layer - -#### Aggregates - -##### OpenIddictApplication - -OpenIddictApplications represent the applications that can request tokens from your OpenIddict Server. - -- `OpenIddictApplications` (aggregate root): Represents an OpenIddict application. - - `ClientId` (string): The client identifier associated with the current application. - - `ClientSecret` (string): The client secret associated with the current application. Maybe hashed or encrypted for security reasons. - - `ConsentType` (string): The consent type associated with the current application. - - `DisplayName` (string): The display name associated with the current application. - - `DisplayNames` (string): The localized display names associated with the current application serialized as a JSON object. - - `Permissions` (string): The permissions associated with the current application, serialized as a JSON array. - - `PostLogoutRedirectUris` (string): The logout callback URLs associated with the current application, serialized as a JSON array. - - `Properties` (string): The additional properties associated with the current application serialized as a JSON object or null. - - `RedirectUris` (string): The callback URLs associated with the current application, serialized as a JSON array. - - `Requirements` (string): The requirements associated with the current application - - `Type` (string): The application type associated with the current application. - - `ClientUri` (string): URI to further information about client. - - `LogoUri` (string): URI to client logo. - -##### OpenIddictAuthorization - -OpenIddictAuthorizations are used to keep the allowed scopes, authorization flow types. - -- `OpenIddictAuthorization` (aggregate root): Represents an OpenIddict authorization. - - - `ApplicationId` (Guid?): The application associated with the current authorization. - - - `Properties` (string): The additional properties associated with the current authorization serialized as a JSON object or null. - - - `Scopes` (string): The scopes associated with the current authorization, serialized as a JSON array. - - - `Status` (string): The status of the current authorization. - - - `Subject` (string): The subject associated with the current authorization. - - - `Type` (string): The type of the current authorization. - -##### OpenIddictScope - -OpenIddictScopes are used to keep the scopes of resources. - -- `OpenIddictScope` (aggregate root): Represents an OpenIddict scope. - - - `Description` (string): The public description associated with the current scope. - - - `Descriptions` (string): The localized public descriptions associated with the current scope, serialized as a JSON object. - - - `DisplayName` (string): The display name associated with the current scope. - - - `DisplayNames` (string): The localized display names associated with the current scope serialized as a JSON object. - - - `Name` (string): The unique name associated with the current scope. - - `Properties` (string): The additional properties associated with the current scope serialized as a JSON object or null. - - `Resources` (string): The resources associated with the current scope, serialized as a JSON array. - -##### OpenIddictToken - -OpenIddictTokens are used to persist the application tokens. - -- `OpenIddictToken` (aggregate root): Represents an OpenIddict token. - - - `ApplicationId` (Guid?): The application associated with the current token. - - `AuthorizationId` (Guid?): The application associated with the current token. - - `CreationDate` (DateTime?): The UTC creation date of the current token. - - `ExpirationDate` (DateTime?): The UTC expiration date of the current token. - - `Payload` (string): The payload of the current token, if applicable. Only used for reference tokens and may be encrypted for security reasons. - - - `Properties` (string): The additional properties associated with the current token serialized as a JSON object or null. - - `RedemptionDate` (DateTime?): The UTC redemption date of the current token. - - `Status` (string): The status of the current authorization. - - - `ReferenceId` (string): The reference identifier associated with the current token, if applicable. Only used for reference tokens and may be hashed or encrypted for security reasons. - - - `Status` (string): The status of the current token. - - - `Subject` (string): The subject associated with the current token. - - - `Type` (string): The type of the current token. - -#### Stores - -This module implements OpenIddict stores: - -- `IAbpOpenIdApplicationStore` -- `IOpenIddictAuthorizationStore` -- `IOpenIddictScopeStore` -- `IOpenIddictTokenStore` - -##### Repositories - -The following custom repositories are defined in this module: - -- `IOpenIddictApplicationRepository` -- `IOpenIddictAuthorizationRepository` -- `IOpenIddictScopeRepository` -- `IOpenIddictTokenRepository` - -##### Domain Services - -This module doesn't contain any domain service but overrides the service below: - -- `AbpApplicationManager` used to populate/get `AbpApplicationDescriptor` information that contains `ClientUri` and `LogoUri`. - -### Settings - -This module doesn't define any settings. - -### Application Layer - -#### Application Services - -- `ApplicationAppService` (implements `IApplicationAppService`): Implements the use cases of the application management UI. -- `ScopeAppService` (implement `IScopeAppService`): Implements the use cases of the API scope management UI. - -### Database Providers - -#### Common - -##### Table/Collection Prefix & Schema - -All tables/collections use the `OpenIddict` prefix by default. Set static properties on the `AbpOpenIddictDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). - -##### Connection String - -This module uses `AbpOpenIddict` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. - -See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details. - -#### Entity Framework Core - -##### Tables - -- **OpenIddictApplications** -- **OpenIddictAuthorizations** -- **OpenIddictScopes** -- **OpenIddictTokens** - -#### MongoDB - -##### Collections - -- **OpenIddictApplications** -- **OpenIddictAuthorizations** -- **OpenIddictScopes** -- **OpenIddictTokens** - -### Permissions - -See the `AbpOpenIddictProPermissions` class members for all permissions defined for this module. - -## ASP.NET Core Module - -This module integrates ASP NET Core, with built-in MVC controllers for four protocols. It uses OpenIddict's [Pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode). - -```cs -AuthorizeController -> connect/authorize -TokenController -> connect/token -LogoutController -> connect/logout -UserInfoController -> connect/userinfo -``` - -### AbpOpenIddictAspNetCoreOptions +The result displays the access token, token type, expiration in seconds and granted scope. Treat both the entered secret and returned access token as sensitive data: do not log them or include them in screenshots, tickets or source files. -`AbpOpenIddictAspNetCoreOptions` can be configured in the `PreConfigureServices` method of your OpenIddict [module](../framework/architecture/modularity/basics.md). +### Creating a Client Credentials Application -Example: - -```csharp -PreConfigure(options => -{ - //Set options here... -}); -``` - -`AbpOpenIddictAspNetCoreOptions` properties: - -- `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the Openiddict claims. -- `AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. - -#### Automatically Removing Orphaned Tokens/Authorizations - -The background task that automatically removes orphaned tokens/authorizations. This can be configured by `TokenCleanupOptions` to manage it. - -`TokenCleanupOptions` can be configured in the `PreConfigureServices` method of your OpenIddict [module](../framework/architecture/modularity/basics.md). - -Example: - -```csharp -PreConfigure(options => -{ - //Set options here... -}); -``` +Use a client credentials application when a machine-to-machine client, automation process or MCP client needs to call protected APIs without an interactive user. -`TokenCleanupOptions` properties: - -- `IsCleanupEnabled` (default: true): Enable/disable token clean up. -- `CleanupPeriod` (default: 3,600,000 ms): Setting clean up period. -- `DisableAuthorizationPruning`: Setting a boolean indicating whether authorizations pruning should be disabled. -- `DisableTokenPruning`: Setting a boolean indicating whether token pruning should be disabled. -- `MinimumAuthorizationLifespan` (default: 14 days): Setting the minimum lifespan authorizations must have to be pruned. Cannot be less than 10 minutes. -- `MinimumTokenLifespan` (default: 14 days): Setting the minimum lifespan tokens must have to be pruned. Cannot be less than 10 minutes. - -#### Updating Claims In Access_token and Id_token +1. Open **Administration** > **OpenId** > **Applications**. +2. Click **New Application**. +3. Enter a unique **Client ID**, such as `InternalAutomationClient`. +4. Select **Confidential client**. +5. Enter a strong **Client Secret** and store it securely. +6. Enable **Allow client credentials flow**. +7. Select the API scopes the client is allowed to request. +8. Save the application. -[Claims Principal Factory](../framework/fundamentals/authorization/index.md#claims-principal-factory) can be used to add/remove claims to the `ClaimsPrincipal`. +![Client Credentials application](../images/openiddict-client-credentials-application.png) -The `AbpDefaultOpenIddictClaimDestinationsProvider` service will add `Name`, `Email,` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`. +If the protected API also uses ABP permissions, open the application's **Actions** menu, select **Permissions**, and grant permissions for the **Client (OpenIddict Applications)** provider. Scope assignment controls OAuth access; ABP permission assignment controls the operations that the client principal can perform. -Create a service that inherits from `IAbpOpenIddictClaimDestinationsProvider` and add it to DI to fully control the destinations of claims. +You can use the **Generate Access Token** action or call the token endpoint directly: -```cs -public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvider, ITransientDependency -{ - public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context) - { - // ... - return Task.CompletedTask; - } -} - -Configure(options => -{ - options.ClaimDestinationsProvider.Add(); -}); +```bash +curl -X POST "https://localhost:/connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -d "client_id=InternalAutomationClient" \ + -d "client_secret=" \ + -d "scope=" ``` -For detailed information, please refer to: [OpenIddict claim destinations](https://documentation.openiddict.com/configuration/claim-destinations.html) - -#### Disable AccessToken Encryption +Use the returned `access_token` as a bearer token: -ABP disables the `access token encryption` by default for compatibility, it can be enabled manually if needed. - -```cs -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - PreConfigure(builder => - { - builder.Configure(options => options.DisableAccessTokenEncryption = false); - }); -} +```text +Authorization: Bearer ``` -### Disable Transport Security Requirement +In a non-tiered solution, the authority is normally the backend host. In a tiered or microservice solution, use the Auth Server URL. -By default, OpenIddict requires the use of HTTPS for all endpoints. You can disable it if it's needed. You just need to configure the `OpenIddictServerAspNetCoreOptions` and set `DisableTransportSecurityRequirement` as **true**: +### Scope Management -```cs -Configure(options => -{ - options.DisableTransportSecurityRequirement = true; -}); -``` - -https://documentation.openiddict.com/configuration/token-formats.html#disabling-jwt-access-token-encryption +Scopes define the API access that applications can request. -### Request/Response Process +![OpenIddict scopes page](../images/openiddict-scopes-page.png) -The `OpenIddict.Server.AspNetCore` adds an authentication scheme(`Name: OpenIddict.Server.AspNetCore, handler: OpenIddictServerAspNetCoreHandler`) and implements the `IAuthenticationRequestHandler` interface. +You can create, filter, update and delete scopes. -It will be executed first in `AuthenticationMiddleware` and can short-circuit the current request. Otherwise, `DefaultAuthenticateScheme` will be called and continue to execute the pipeline. +![Edit an OpenIddict scope](../images/openiddict-edit-api-scope-modal.png) -`OpenIddictServerAspNetCoreHandler` will call various built-in handlers (handling requests and responses), And the handler will process according to the context or skip logic that has nothing to do with it. +A scope has a unique **Name** and optional **Display Name**, **Description** and **Resources**. Resources identify the API audiences associated with the scope. -Example of a token request: - -``` -POST /connect/token HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - - grant_type=password& - client_id=AbpApp& - client_secret=1q2w3e*& - username=admin& - password=1q2w3E*& - scope=AbpAPI offline_access -``` +The application editor also offers the built-in `address`, `email`, `phone`, `profile` and `roles` scopes. These built-in choices are not seeded or managed as Pro scope records, and the scope service rejects attempts to create or rename a managed scope to one of those names. -This request will be processed by various handlers. They will confirm the endpoint type of the request, check `HTTP/HTTPS`, verify that the request parameters (`client. scope, etc`) are valid and exist in the database, etc. Various protocol checks. And build a `OpenIddictRequest` object, If there are any errors, the response content may be set and directly short-circuit the current request. +## Permissions -If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the HTTP request at this time. The rest will be based on this object. +All OpenIddict Pro permissions are host-only. -Check the `username` and `password` in the request. If it is correct create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing. +| Permission | Task | +| --- | --- | +| `OpenIddictPro.Application` | View applications and the Applications menu | +| `OpenIddictPro.Application.Create` | Create applications | +| `OpenIddictPro.Application.Update` | Update applications and token lifetime overrides | +| `OpenIddictPro.Application.Delete` | Delete applications | +| `OpenIddictPro.Application.ManagePermissions` | Manage ABP permissions for a client application | +| `OpenIddictPro.Application.GenerateAccessToken` | Generate a Client Credentials access token | +| `OpenIddictPro.Scope` | View scopes and the Scopes menu | +| `OpenIddictPro.Scope.Create` | Create scopes | +| `OpenIddictPro.Scope.Update` | Update scopes | +| `OpenIddictPro.Scope.Delete` | Delete scopes | -`OpenIddictServerAspNetCoreHandler` do some checks to generate json and replace the http response content. +When the Audit Logging UI module is installed, its typed **View Change History** permissions add a Change History action for applications and scopes. -The `ForbidResult` `ChallengeResult` are all the above types of processing. +## HTTP APIs -If you need to customize OpenIddict, you need to replace/delete/add new handlers and make it execute in the correct order. +The module exposes administration APIs under these route groups: -Please refer to: -https://documentation.openiddict.com/guides/index.html#events-model +| Route group | Operations | +| --- | --- | +| `/api/openiddict/applications` | List, get, create, update and delete applications | +| `/api/openiddict/applications/{id}/token-lifetime` | Get or set per-client token lifetime overrides | +| `/api/openiddict/applications/{id}/generate-access-token` | Generate a Client Credentials access token | +| `/api/openiddict/scopes` | List, get, create, update and delete scopes | +| `/api/openiddict/scopes/all` | Get managed and built-in scopes for application assignment | -### PKCE +The application services enforce the permissions and validation rules described above. The published .NET and Angular proxy packages provide typed clients for these APIs. -https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html +## Angular -### Setting Tokens Lifetime +The Angular package is `@volo/abp.ng.openiddictpro`. Current standalone applications register its configuration provider in `app.config.ts`: -Update `PreConfigureServices` method of AuthServerModule (or HttpApiHostModule if you don't have tiered/separate-authserver) file: +```typescript +import { provideOpeniddictproConfig } from '@volo/abp.ng.openiddictpro/config'; -```csharp -PreConfigure(builder => -{ - builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(30)); - builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(30)); - builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(30)); - builder.SetRefreshTokenLifetime(TimeSpan.FromDays(14)); -}); +export const appConfig = { + providers: [provideOpeniddictproConfig()], +}; ``` -### Refresh Token - -To use refresh token, it must be supported by OpenIddictServer and the `refresh_token` must be requested by the application. - -#### Configuring OpenIddictServer - -There are two ways to allow an application to use the `refresh_token`. +Add its lazy routes in `app.routes.ts`: -* **From OpenIddictDataSeedContributor**, add `OpenIddictConstants.GrantTypes.RefreshToken` to grant types in `CreateApplicationAsync` method: - - ```csharp - await CreateApplicationAsync( - ... - grantTypes: new List //Hybrid flow - { - OpenIddictConstants.GrantTypes.AuthorizationCode, - OpenIddictConstants.GrantTypes.Implicit, - OpenIddictConstants.GrantTypes.RefreshToken, - }, - ... - ``` - - > **Note:** You need to re-create this client if you have generated the database already. - -* **Or from OpenIddict Management UI**, edit your application and `Allow Refresh Token Flow`: - -![openiddict-edit-refresh-token](../images/openid-edit-application.png) - -> **Note:** Angular application is already configured to use `refresh_token`. - -#### Configuring Application: - -You need to request the **offline_access scope** to be able to receive `refresh_token`. - -In **Razor/MVC, Blazor-Server applications**, add `options.Scope.Add("offline_access");` to **OpenIdConnect** options. These application templates are using cookie authentication by default and has default cookie expire options set as: - -```csharp -.AddCookie("Cookies", options => +```typescript { - options.ExpireTimeSpan = TimeSpan.FromDays(365); -}) + path: 'openiddict', + loadChildren: () => + import('@volo/abp.ng.openiddictpro').then(module => module.createRoutes()), +} ``` -[Cookie ExpireTimeSpan will ignore access_token expiration](https://learn.microsoft.com/en-us/dotnet/api/Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions.ExpireTimeSpan?view=aspnetcore-7.0&viewFallbackFrom=net-7.0) and expired access_token will still be valid if it is set to higher value than the `refresh_token lifetime`. It is recommended to keep **Cookie ExpireTimeSpan** and the **Refresh Token lifetime** same, hence the new token will be persisted in the cookie. +`createRoutes` accepts `entityActionContributors`, `toolbarActionContributors`, `entityPropContributors`, `createFormPropContributors` and `editFormPropContributors`. The Applications component is declared for all five contributor types; the Scopes component is declared for entity action contributors. -In **Blazor wasm** applications, add `options.ProviderOptions.DefaultScopes.Add("offline_access");` to **AddOidcAuthentication** options. +The replaceable component keys are: -In **Angular** applications, add `offline_access` to **oAuthConfig** scopes in *environment.ts* file. (Angular applications already have this configuration). +- `eOpenIddictProComponents.Applications` +- `eOpenIddictProComponents.Scopes` +See the Angular guides for [extension points](../framework/ui/angular/extensions-overall.md), [dynamic form extensions](../framework/ui/angular/dynamic-form-extensions.md) and [component replacement](../framework/ui/angular/component-replacement.md). +## Persistence -## Migrating Guide +OpenIddict Pro does not add a separate application or scope schema. Its EF Core and MongoDB provider packages reuse the open-source OpenIddict entities, mappings, `AbpOpenIddict` connection-string name and host-only storage model. -[Migrating from IdentityServer to OpenIddict Step by Step Guide ](../release-info/migration-guides/openiddict-step-by-step.md) +Configure the tables, collections, schema, connection string and database providers through the [OpenIddict module](./openiddict.md). From fe1cea4c87255771f49082a179c17b1ae8880bbd Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 25/41] Expand Text Template Management documentation --- docs/en/modules/text-template-management.md | 125 +++++++++++++------- 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/docs/en/modules/text-template-management.md b/docs/en/modules/text-template-management.md index 32b7cc31a9..dc9479593a 100644 --- a/docs/en/modules/text-template-management.md +++ b/docs/en/modules/text-template-management.md @@ -9,19 +9,21 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. -This module is used to store and edit template contents for [the text templating system](../framework/infrastructure/text-templating/index.md) of the ABP. So, you may need to understand it to better understand the purpose of this module. +This module stores and lets users edit content for ABP's [text templating system](../framework/infrastructure/text-templating/index.md). Read the text templating documentation to understand how applications define and render templates. -There are different use cases of the text templating system. For example, [the Account Module](account.md) is using it to define templates for sending emails when it needs to send emails to users (like sending "password reset link" email). This module provides UI to easily edit these email templates. +Applications use text templates for many purposes. For example, the [Account Module](account.md) defines templates for emails such as password reset messages. This module provides a UI for editing those templates. See [the module description page](https://abp.io/modules/Volo.TextTemplateManagement) for an overview of the module features. +The `TextManagement.Enable` feature is enabled by default. The module's permissions, application services and menu items require this feature. + ## How to Install -Text Template Management module is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it. +The Text Template Management module is pre-installed in [the startup templates](../solution-templates), so you do not need to install it manually. ### Existing Solutions -If you want to add the **Text Template Management** module to your existing solution, you can use the ABP CLI `add-module` command: +To add the **Text Template Management** module to an existing solution, use the ABP CLI `add-module` command: ```bash abp add-module Volo.TextTemplateManagement @@ -31,7 +33,7 @@ abp add-module Volo.TextTemplateManagement 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 [Text Template Management module package list page](https://abp.io/packages?moduleName=Volo.TextTemplateManagement) to see list of packages related with this module. +Visit the [Text Template Management module package list](https://abp.io/packages?moduleName=Volo.TextTemplateManagement) to see the packages related to this module. ## User Interface @@ -47,11 +49,11 @@ Text Template Management module adds the following items to the "Main" menu, und #### Text Templates -Text Templates page is used to view the list of templates defined in the application. +The Text Templates page lists the templates defined in the application. ![text-template-management-text-templates-page](../images/text-template-management-text-templates-page.png) -Click to the `Actions -> Edit Contents` to edit content for a template. There are two types of UI to edit a template content: +Click `Actions > Edit Contents` to edit a template. The module provides two editors: ##### Editing Content for Inline Localized Templates @@ -65,23 +67,45 @@ This kind of templates provides different content for each culture. In this way, ![text-template-management-multiple-culture-edit](../images/text-template-management-multiple-culture-edit.png) +Saved content is an override for the current tenant context. The host and each tenant have separate overrides; a tenant doesn't inherit an override saved in the host context. If no database override exists, the text templating system continues with the configured content contributors, such as the template's virtual-file content. + +For a static template, the framework content provider first tries the requested regional culture and then its parent culture. An inline-localized template then falls back to its culture-independent content, while a culture-specific template falls back to its configured default culture. + +Dynamic definitions use a different lookup order. When `IsDynamicTemplateStoreEnabled` is enabled and a definition exists only in the database, `DatabaseTemplateContentContributor` tries the requested culture, the definition's default culture (or `en` when it has no default), and then culture-independent content. This lookup can return content before the framework tries the requested culture's parent. **Restore to default** deletes the current tenant context's override for the selected template and culture, after which the applicable fallback path is used again. + +### UI Extension Points + +The MVC UI uses `textTemplateManagement.textDefinition` for both [entity action extensions](../framework/ui/mvc-razor-pages/entity-action-extensions.md) and [data table column extensions](../framework/ui/mvc-razor-pages/data-table-column-extensions.md). + +The Blazorise UI exposes entity actions and table columns through the `TextTemplateManagement` page component type. See the Blazor [entity action](../framework/ui/blazor/entity-action-extensions.md) and [data table column](../framework/ui/blazor/data-table-column-extensions.md) extension documents. + ## Configure `TextTemplateManagementOptions` -`TextTemplateManagementOptions` can be used to configure the module. You can use the below code to configure it in the ConfigureServices method of your module (eg: BookStoreApplicationModule). +`TextTemplateManagementOptions` can be used to configure the module. The following example shows the default values: ```csharp Configure(options => { - options.MinimumCacheDuration = TimeSpan.FromHours(1); + options.MinimumCacheDuration = TimeSpan.FromHours(1); + options.SaveStaticTemplatesToDatabase = true; + options.IsDynamicTemplateStoreEnabled = false; }); ``` +| Property | Description | +| --- | --- | +| `MinimumCacheDuration` | Sets the sliding expiration of cached template contents. The default is one hour. | +| `SaveStaticTemplatesToDatabase` | Starts synchronizing static template definitions and their virtual-file contents to the database during application initialization. The default is `true`. | +| `IsDynamicTemplateStoreEnabled` | Enables the database-backed dynamic template-definition store. The default is `false`. Static definitions take precedence when static and dynamic definitions share the same name. | + +Both `SaveStaticTemplatesToDatabase` and `IsDynamicTemplateStoreEnabled` are disabled automatically in a data-migration environment. Disabling static synchronization doesn't disable saved content overrides. + ## Caching -[`DatabaseTemplateContentContributor`](#DatabaseTemplateContentContributor) caches template contents to increase performance. +`DatabaseTemplateContentContributor` caches template contents to increase performance. Cache entries use the `MinimumCacheDuration` sliding expiration. -You can get cache store by injecting `IDistributedCache`. +You can access the cache by injecting `IDistributedCache`. `TemplateContentCacheKey` contains the template definition name and culture; ABP's distributed cache key normalization also isolates entries by the current tenant context. The application service removes the related cache entry when it persists a new override or deletes one through **Restore to default**, so an application restart isn't required. For more information, please check the [Caching](../framework/fundamentals/caching.md) guide. @@ -93,35 +117,35 @@ It has `TemplateDefinitionName` and `Culture` properties. ## Data Seed -This module doesn't seed any data. +This module doesn't define an `IDataSeedContributor`. The optional startup synchronization controlled by `SaveStaticTemplatesToDatabase` is separate from the data seed system. ## Internals ### Domain Layer -#### Aggregates +#### Entities and Aggregate Roots This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide. -##### TextTemplateContent +##### Domain Model Types -* `TextTemplateContent` (aggregate root): Represents a content of text template. +* `TextTemplateContent` (aggregate root): Represents a tenant-aware template content override. +* `TextTemplateDefinitionRecord` (aggregate root): Stores synchronized template definition metadata. +* `TextTemplateDefinitionContentRecord` (entity): Stores a synchronized definition's file content. #### 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: * `ITextTemplateContentRepository` +* `ITextTemplateDefinitionRecordRepository` +* `ITextTemplateDefinitionContentRecordRepository` -#### Domain Services - -This module follows the [Domain Services Best Practices & Conventions](../framework/architecture/best-practices/domain-services.md) guide. +#### Template Content Contributor -##### DatabaseTemplateContentContributor - -`DatabaseTemplateContentContributor` is used by `ITemplateContentProvider` to get template contents that stored in DB and Cache. +`DatabaseTemplateContentContributor` is an `ITemplateContentContributor` used by `ITemplateContentProvider` to read tenant-aware content overrides and, when the dynamic definition store is enabled, synchronized definition content from the database and cache. ### Settings @@ -144,7 +168,7 @@ All tables/collections use the `Abp` prefix by default. Set static properties on ##### Connection String -This module uses `TextTemplateManagement` 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 `TextTemplateManagement` as 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. @@ -153,25 +177,32 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do ##### Tables * **AbpTextTemplateContents** +* **AbpTextTemplateDefinitionRecords** +* **AbpTextTemplateDefinitionContentRecords** #### MongoDB ##### Collections -* **AbpTextTemplateContents** +* **AbpTextTemplates** +* **AbpTextTemplateDefinitionRecords** +* **AbpTextTemplateDefinitionContentRecords** ### Permissions -See the `TextTemplateManagementPermissions` class members for all permissions defined for this module. +All permissions require the `TextManagement.Enable` feature. -The module exposes two edit-time permissions with different risk levels: +The module defines the following permissions: -| Permission | Required to edit | Default grant | -|------------|------------------|---------------| -| `TextTemplateManagement.TextTemplates.EditContents` | Templates rendered by a sandboxed engine (e.g. Scriban). Editing such templates is safe for content editors because the engine cannot execute arbitrary .NET code. | Granted to roles that need to edit template text. | -| `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` | Templates rendered by a **non-sandboxed** engine (e.g. Razor). Editing such templates is functionally equivalent to granting server-side code execution because the engine compiles the content into a .NET assembly that runs with the host process's privileges. | **Not granted to any role by default**, including `admin`. Must be granted explicitly. | +| Permission | Purpose | +| --- | --- | +| `TextTemplateManagement.TextTemplates` | View and filter template definitions and read their contents. | +| `TextTemplateManagement.TextTemplates.EditContents` | Edit and restore template content. | +| `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` | Additionally authorize editing templates rendered by a non-sandboxed engine, such as Razor. | -Whether a template is sandboxed is determined by `ITemplateRenderingEngine.IsSandboxed` on the engine that renders it. Editing a non-sandboxed template requires **both** `EditContents` and `EditNonSandboxedContents`. +Whether a template is sandboxed is determined by `ITemplateRenderingEngine.IsSandboxed` on the engine that renders it. An unknown, unregistered or unresolved default engine is treated as non-sandboxed. Editing a non-sandboxed template requires **both** `EditContents` and `EditNonSandboxedContents`. + +When the standard Permission Management data seeder runs, it grants all role-applicable permissions to the `admin` role. This includes `EditNonSandboxedContents`. If the administrators in your application shouldn't be allowed to edit executable template content, customize the seeding policy and remove any existing grant. The Text Template Management UI surfaces this distinction: @@ -185,39 +216,39 @@ The Text Template Management UI surfaces this distinction: #### Installation -In order to configure the application to use the text template management module, you first need to import `provideTextTemplateManagementConfig` from `@volo/abp.ng.text-template-management/config` to root configuration. Then, you will need to append it to the `appConfig` array. +To configure the application to use the text template management module, import `provideTextTemplateManagementConfig` from `@volo/abp.ng.text-template-management/config` and add it to the root `providers` array. -```js +```ts // app.config.ts import { provideTextTemplateManagementConfig } from '@volo/abp.ng.text-template-management/config'; export const appConfig: ApplicationConfig = { providers: [ // ... - provideTextTemplateManagementConfig() + provideTextTemplateManagementConfig(), ], }; ``` -The text template management module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. Available options are listed below. It is available for import from `@volo/abp.ng.text-template-management`. +The text template management module should be imported and lazy-loaded in your routing array. It exports a `createRoutes` function from `@volo/abp.ng.text-template-management`. Available options are listed below. -```js +```ts // app.routes.ts const APP_ROUTES: Routes = [ // ... { path: 'text-template-management', loadChildren: () => - import('@volo/abp.ng.text-template-management').then(c => c.createRoutes(/* options here */)), + import('@volo/abp.ng.text-template-management').then(c => c.createRoutes()), }, ]; ``` -> If you have generated your project via the startup template, you do not have to do anything, because it already has both configurations implemented. +> Startup templates already include both settings, so no additional configuration is needed.

Options

-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 the `createRoutes` function: - **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. @@ -226,7 +257,7 @@ You can modify the look and behavior of the module pages by passing the followin #### Services / Models -Text Template Management 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: +The Text Template Management module's services and models are generated by the [ABP CLI](../cli) `generate-proxy` command. To generate the module's proxies, run the following command in the Angular project directory: ```bash abp generate-proxy --module textTemplateManagement @@ -238,6 +269,12 @@ abp generate-proxy --module textTemplateManagement `eTextTemplateManagementComponents` enum provides all replaceable component keys. It is available for import from `@volo/abp.ng.text-template-management`. +The available keys are: + +* `eTextTemplateManagementComponents.TextTemplates` +* `eTextTemplateManagementComponents.TemplateContents` +* `eTextTemplateManagementComponents.InlineTemplateContent` + Please check [Component Replacement document](../framework/ui/angular/component-replacement.md) for details. @@ -245,7 +282,7 @@ Please check [Component Replacement document](../framework/ui/angular/component- The Text Template Management module remote endpoint URL can be configured in the environment files. -```js +```ts export const environment = { // other configurations apis: { @@ -253,14 +290,14 @@ export const environment = { url: 'default url here', }, TextTemplateManagement: { - url: 'Text Template Management remote url here' - } + url: 'Text Template Management remote url here', + }, // other api configurations }, }; ``` -The Text Template Management module remote URL configuration shown above is optional. If you don't set a URL, the `default.url` will be used as fallback. +The Text Template Management module's remote URL configuration is optional. If you don't set a URL, `default.url` is used as a fallback. ## Distributed Events From 4221423c3b4af5afd3f625faaaabbd3ebcb103bd Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 26/41] Expand Identity Pro and Account Pro documentation --- docs/en/modules/account-pro.md | 50 ++++++++++++++++--- docs/en/modules/account/passkey.md | 2 + .../modules/account/shared-user-accounts.md | 21 +++++--- .../modules/identity/import-external-users.md | 6 +++ docs/en/modules/identity/oauth-login.md | 21 +++++++- docs/en/modules/identity/password-history.md | 30 +++++++++-- .../en/modules/identity/session-management.md | 4 +- 7 files changed, 116 insertions(+), 18 deletions(-) diff --git a/docs/en/modules/account-pro.md b/docs/en/modules/account-pro.md index a79c3d4992..0084a7f10c 100644 --- a/docs/en/modules/account-pro.md +++ b/docs/en/modules/account-pro.md @@ -86,7 +86,11 @@ Configure(options => * `TenantAdminUserName` (default: admin): The tenant admin user name. * `ImpersonationTenantPermission`: The permission name for tenant impersonation. * `ImpersonationUserPermission`: The permission name for user impersonation. -* `ExternalProviderIconMap`: A dictionary of external provider names and their corresponding font-awesome icon classes. You can add new mapping to this dictionary to change the icon of an external provider.(Popular external provider icons are already defined, such as `Facebook`, `Google`, `Microsoft`, `Twitter`, etc.) +* `SwitchUserDuringImpersonate` (default: `false`): Signs the target user in with the application cookie while an impersonation flow is in progress. +* `ExternalProviderIconMap`: A dictionary of external provider names and their icon asset paths or CSS classes. Common providers such as GitHub, Google, X, Apple, LinkedIn, Facebook and Microsoft are already mapped. +* `IsTenantMultiDomain` (default: `false`): Enables tenant-domain redirects for linked-account and tenant-switching flows. +* `GetTenantDomain`: Resolves the target tenant's origin for impersonation redirects and, when `IsTenantMultiDomain` is enabled, linked-account and tenant-switching redirects. By default, it returns the current request's scheme and host. +* `ExternalProfilePictureDownloadTimeout` (default: 5 seconds): Limits how long external-login registration waits while downloading a profile picture. ### AbpProfilePictureOptions @@ -101,7 +105,26 @@ Configure(options => `AbpProfilePictureOptions` properties: -* `EnableImageCompression` (default: false): Enables the image compression for the profile picture. When enabled, the selected compression library will compress the profile picture to decrease the image size. For more information see [image manipulation](../framework/infrastructure/image-manipulation.md) +* `EnableImageCompression` (default: `false`): Enables image compression for the profile picture. When enabled, the selected compression library compresses the profile picture to decrease its size. For more information, see [image manipulation](../framework/infrastructure/image-manipulation.md). +* `AllowedFileExtensions` (default: `.jpg`, `.jpeg` and `.png`): Defines the accepted file-name extensions. The extension check runs when the upload includes a file name. +* `MaxFileSizeInBytes` (default: 5 MiB): Rejects larger uploads. Set it to `0` to disable the size limit. +* `MagicBytesVerifiers`: Verifies the file content independently of the file name. The default verifiers accept JPEG and PNG signatures. If you add an allowed extension, add a matching content verifier as well; at least one verifier must accept every uploaded image. + +### Registration Email Confirmation Codes + +The registration email confirmation code is stored with a 10-minute absolute expiration by default. Configure a different duration with `Account:EmailConfirmation:CodeExpirationTime`: + +```json +{ + "Account": { + "EmailConfirmation": { + "CodeExpirationTime": "00:15:00" + } + } +} +``` + +Sending and checking these codes use separate built-in operation rate-limit policies. Sending a new code resets the check rate-limit state for that email address. ## Local login @@ -111,6 +134,20 @@ If you use `Social / External Logins`, It is automatically called for authentica ![account-pro-module-local-login-setting](../images/account-pro-module-local-login-setting.png) +## Email Login + +Email login lets users sign in with a one-time code, a magic link or both. It is disabled by default and also requires **Local login** to remain enabled. Configure it in `Settings > Account > Email Login`. + +The available login types are: + +* `OtpAndMagicLink` (default): The email contains both a six-digit code and a magic link. +* `MagicLinkOnly`: The email contains only a magic link and the code-verification endpoint is disabled. +* `OtpOnly`: The email contains only a code and direct magic-link verification is disabled. + +The token lifespan defaults to 90 seconds and accepts values from 30 to 86,400 seconds. Codes and link tokens are single-use. Completing either path invalidates the outstanding credential for the other path, and sending a new email invalidates the previous credentials. + +Email login uses built-in send and verification rate limits. When `AccountSettingNames.PreventEmailEnumeration` is enabled, requests for an unknown or locked-out account return the same expiry-shaped response as a valid request without sending an email. This prevents callers from using the send response to distinguish those accounts. + ### Switching users during OAuth login If you have an OAuth/Auth Server application using the Account Pro module, you can pass the `prompt=select_account` parameter to force the user to select an account. @@ -345,9 +382,11 @@ export const APP_ROUTES: Routes = [ You can modify the look and behavior of the module pages by passing the following options to `createRoutes` static method: - **redirectUrl**: Default redirect URL after logging in. -- **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. +- **entityActionContributors:** Changes actions on `eAccountComponents.MySecurityLogs`. See [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md). +- **toolbarActionContributors:** Changes the toolbar on `eAccountComponents.MySecurityLogs`. See [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md). +- **entityPropContributors:** Changes columns on `eAccountComponents.MySecurityLogs`. See [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md). +- **personelInfoEntityPropContributors:** Changes the edit-form properties on `eAccountComponents.PersonalSettings`. The public API uses this spelling. See [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md). +- **isPersonalSettingsChangedConfirmationActive:** Deprecated. Personal settings refresh the current user's state without requiring a new login. #### Services / Models @@ -426,4 +465,3 @@ This module doesn't define any additional distributed event. See the [standard d * [Idle Session Timeout](./account/idle-session-timeout.md) * [Web Authentication API (WebAuthn) passkeys](./account/passkey.md) * [Shared user accounts](./account/shared-user-accounts.md) -``` \ No newline at end of file diff --git a/docs/en/modules/account/passkey.md b/docs/en/modules/account/passkey.md index b8f00ee576..a868977ece 100644 --- a/docs/en/modules/account/passkey.md +++ b/docs/en/modules/account/passkey.md @@ -8,6 +8,8 @@ You can enable/disable the `Web Authentication API (WebAuthn) passkeys` feature ![passkey-setting](../../images/passkey-setting.png) +Passkeys are disabled by default. The default limit is 10 passkeys per user, and the configured limit must be greater than zero. Registration is rejected when passkeys are disabled or the user has reached the limit. + ## Manage Passkeys You can add/rename/delete your passkeys in the `Account/Manage` page: diff --git a/docs/en/modules/account/shared-user-accounts.md b/docs/en/modules/account/shared-user-accounts.md index 908b342de2..5205ef10f8 100644 --- a/docs/en/modules/account/shared-user-accounts.md +++ b/docs/en/modules/account/shared-user-accounts.md @@ -74,6 +74,17 @@ From the invitation modal, you can view and manage sent invitations, including r ![Manage Invitations](../../images/manage-invitations.png) +Invitation links contain a protected, URL-safe token and expire after 7 days by default. Invalid, modified or expired tokens are rejected. Configure the lifespan with `UserInvitationTokenProviderOptions`: + +```csharp +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromDays(3); +}); +``` + +Inviting the same email address again while an invitation is still pending reuses that invitation, replaces its assigned roles and refreshes its invitation date. Resending is allowed only for a pending invitation; it refreshes the invitation date and sends a newly generated token. + ## Accepting an Invitation If the invited person already has an account, clicking the email link shows a confirmation screen to join the tenant: @@ -150,13 +161,13 @@ When the Shared strategy is enabled, a user is a **global resource** across host ### Host-only operations -The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce this — a direct API call from a tenant context will be rejected with a `UserFriendlyException`: +The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce these restrictions. - Delete a user -- Activate / deactivate a user (`IsActive`) - Lock / unlock a user - Enable or disable two-factor authentication -- Change `LockoutEnabled` or `ShouldChangePasswordOnNextLogin` + +Direct tenant API calls for these operations are rejected with a `UserFriendlyException`. When a tenant update request changes `IsActive`, `LockoutEnabled` or `ShouldChangePasswordOnNextLogin`, the application service restores the current host-managed values and continues processing the remaining editable fields. > `Delete` here means deleting the **global user account**, not removing a user from a single tenant. Removing a member from one tenant is a tenant-level soft operation and is available to tenant administrators — see **Remove from tenant** below. @@ -176,6 +187,4 @@ Users can leave a tenant from their own account menu (`Switch Tenant` → `Leave If you plan to migrate an existing multi-tenant application from an isolated strategy to Shared User Accounts, keep the following in mind: 1. **Uniqueness check**: Before enabling Shared, ensure all existing usernames and emails are unique globally. ABP performs this check when you switch the strategy and reports conflicts. -2. **Tenants with separate databases**: If some tenants use separate databases, you must ensure the Host database contains matching user records in the `AbpUsers` table (and, if you use social login / passkeys, also sync `AbpUserLogins` and `AbpUserPasskeys`) so the Host-side records match the tenant-side data. After that, the framework can create/manage the user-to-tenant associations. - - **Important — each host-side shadow row must have a new primary key (`Id`) different from the tenant user's `Id`.** Generate a fresh `Guid` for every shadow row instead of reusing the tenant user's primary key. The framework relies on this to distinguish a separate-database tenant from a shared-database one; reusing the Id can mask "Leave Tenant" and external login / passkey synchronization on legacy data. The other identifying fields (`UserName`, `Email`, `PasswordHash`, `TenantId`, etc.) should still match the tenant-side row. - +2. **Tenants with separate databases**: The module detects a separate Identity database by comparing the resolved Identity connection string for the tenant with the host connection string. If some tenants use separate databases, ensure that the host database contains the corresponding shadow users in the `AbpUsers` table. Host-side shadow users are located by the tenant identifier and email address. If you use social login or passkeys, also synchronize `AbpUserLogins` and `AbpUserPasskeys`. Existing shadow rows that reuse the tenant user's primary key remain compatible with leaving a tenant. diff --git a/docs/en/modules/identity/import-external-users.md b/docs/en/modules/identity/import-external-users.md index 00c8e4b824..5a446894b0 100644 --- a/docs/en/modules/identity/import-external-users.md +++ b/docs/en/modules/identity/import-external-users.md @@ -26,3 +26,9 @@ The Identity PRO module has built-in `LdapExternalLoginProvider` and `OAuthExter 3. Enter the correct `username` and `password` to complete the import. ![complete-import](../../images/complete-import.png) + +## Existing User Matching + +Before creating an account, the import workflow searches by username and email. If it finds an external user, the selected provider updates that user. If it finds a local user, the import is rejected instead of converting or overwriting the local account. + +With [Shared User Accounts](../account/shared-user-accounts.md), this lookup covers the Host and all tenants. A Host-context import therefore cannot create a second identity for a username or email that already belongs to another tenant. diff --git a/docs/en/modules/identity/oauth-login.md b/docs/en/modules/identity/oauth-login.md index b36e35cfc1..3cb6f47a91 100644 --- a/docs/en/modules/identity/oauth-login.md +++ b/docs/en/modules/identity/oauth-login.md @@ -25,6 +25,25 @@ Then you can enter the user name and password on the login page for oauth extern ![oauth-login](../../images/ldap-login.png) +## Mapping User Claims + +`AbpOAuthExternalLoginProviderOptions` maps claims returned by the user-info endpoint to the imported or updated Identity user. The defaults use ABP claim types for name, surname, email, email verification, phone number, phone verification and user id. Configure the mappings when the provider returns different claim names: + +```csharp +Configure(options => +{ + options.NameClaimType = "given_name"; + options.SurnameClaimType = "family_name"; + options.EmailClaimType = "email"; + options.EmailConfirmedClaimType = "email_verified"; + options.PhoneNumberClaimType = "phone_number"; + options.PhoneNumberConfirmedClaimType = "phone_number_verified"; + options.ProviderKeyClaimType = "sub"; +}); +``` + +The email claim is required. Other mapped claims are optional. `CanObtainUserInfoWithoutPassword` defaults to `false`. + ## Resources -https://oauth.net/2/grant-types/password/ +[OAuth 2.0 Resource Owner Password Credentials](https://oauth.net/2/grant-types/password/) diff --git a/docs/en/modules/identity/password-history.md b/docs/en/modules/identity/password-history.md index e28d78fe0e..090e427377 100644 --- a/docs/en/modules/identity/password-history.md +++ b/docs/en/modules/identity/password-history.md @@ -1,10 +1,17 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to configure password reuse prevention and password history retention in the ABP Identity Pro module." +} +``` + # Password History ## Introduction > You must have an ABP Team or a higher license to use this module & its features. -The Identity PRO module has a built-in password history function that allows you to enforce password reuse policies for users within your application. It keeps track of users’ previously used passwords and checks this history whenever a user attempts to change their password. This prevents users from setting a password that they have already used in the past, ensuring that each new password is unique and not a repetition of an older one. +The Identity Pro module has a built-in password history function that allows you to enforce password reuse policies for users within your application. It keeps hashes of previously used passwords and checks the configured history window whenever a user or administrator changes or resets a password. ## Password History Settings @@ -13,8 +20,25 @@ You need to enable the password history and configure related settings: ![identity-pro-module-password-history-settings](../../images/identity-pro-module-password-history-settings.png) * **Enable prevent password reuse**: Whether to prevent users from reusing their previous passwords. -* **Password change period**: The number of previous passwords that cannot be reused. +* **Prevent password reuse count**: The number of previous passwords that cannot be reused. The settings API accepts values from `1` through `128`. -When you enable the password history, users and administrators will not be able to reuse their previous passwords when changing/resetting their passwords. +When you enable password history with a positive reuse count, users and administrators cannot reuse a password in that window when changing or resetting a password. ![identity-pro-module-change-password](../../images/identity-pro-module-change-password.png) + +## History Record Retention + +By default, the module keeps only the number of records configured by **Prevent password reuse count**. Set `AbpIdentityPasswordHistoryOptions.KeepAllRecords` to `true` if your retention policy requires every recorded password hash to remain in the database: + +```csharp +Configure(options => +{ + options.KeepAllRecords = true; +}); +``` + +`KeepAllRecords` changes database retention only. Password validation still checks only the configured reuse-count window. + +History pruning is evaluated after a successful password add, change or reset records a new hash. Lowering **Prevent password reuse count** does not immediately delete existing history records. + +> The settings UI and API do not accept `0`. If the underlying setting is bypassed and written as `0`, reuse validation is disabled, but new history records continue to be stored and are not pruned by the reuse count. diff --git a/docs/en/modules/identity/session-management.md b/docs/en/modules/identity/session-management.md index 4a323553e2..08ae3a5fda 100644 --- a/docs/en/modules/identity/session-management.md +++ b/docs/en/modules/identity/session-management.md @@ -50,7 +50,7 @@ The `IdentitySessionCleanupBackgroundWorker` is a background worker that will re ## How it works -This feature depends on the [Dynamic Claims](https://docs.abp.io/en/abp/latest/Dynamic-Claims) feature of the ABP framework. Here is how it works: +This feature depends on the [Dynamic Claims](../../framework/fundamentals/dynamic-claims.md) feature of the ABP framework. Here is how it works: * The `IdentitySessionClaimsPrincipalContributor` will generate a random GUID as a `sessionid` to add the `ClaimsPrincipal`, This usually happens when logging in to get the user's claims. * The `OnSignedIn` event of `Identity` and `ProcessSignIn` event of `OpenIddict` will get this `sessionid` and store it in the database (`IdentitySession` table). @@ -61,4 +61,4 @@ This feature depends on the [Dynamic Claims](https://docs.abp.io/en/abp/latest/D * The `IdentitySessionCleanupBackgroundWorker` will remove the inactive sessions. * Once a new session has been created, we will remove the other sessions based on the `PreventConcurrentLogin` setting. * The `IdentitySessionManager` is used to manage/maintain the sessions. Please use this class instead of directly using the repository. -* The `UpdateSessionAfterCacheHit(10 times by default)` property of `IdentitySessionCheckerOptions` is used to configure the `IdentitySessionChecker` that will update session in the database when cache hits reach this value. This is to reduce the database update frequency and improve performance. +* The `UpdateSessionAfterCacheHit` property of `IdentitySessionCheckerOptions` controls periodic database updates after cache hits and defaults to `10`. The checker writes on the first successful check, then writes again when the hit count becomes greater than the configured value and resets the counter. This reduces the database update frequency. From f60da008189c710d27e47b82452c8621256c3c34 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 27/41] Expand AI Management module documentation --- docs/en/modules/ai-management/index.md | 467 ++++++++++++------------- 1 file changed, 230 insertions(+), 237 deletions(-) diff --git a/docs/en/modules/ai-management/index.md b/docs/en/modules/ai-management/index.md index 1cef520472..5b01227437 100644 --- a/docs/en/modules/ai-management/index.md +++ b/docs/en/modules/ai-management/index.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Discover how to implement AI management in your ABP Framework application, enhancing workspace dynamics with easy installation options." + "Description": "Manage persisted AI workspaces, providers, RAG data sources, MCP tools and remote AI clients with the ABP AI Management module." } ``` @@ -13,7 +13,7 @@ This module implements AI (Artificial Intelligence) management capabilities on t ## How to Install -The **AI Management Module** is not included in [the startup templates](../solution-templates/layered-web-application) by default. However, when creating a new application with [ABP Studio](../../tools/abp-studio/index.md), you can easily enable it during setup via the *AI Integration* step in the project creation wizard. Alternatively, you can install it using the ABP CLI or ABP Studio: +The **AI Management Module** is not included in [the startup templates](../../solution-templates/layered-web-application/index.md) by default. However, when creating a new application with [ABP Studio](../../studio/overview.md), you can easily enable it during setup via the *AI Integration* step in the project creation wizard. Alternatively, you can install it using the ABP CLI or ABP Studio: **Using ABP CLI:** @@ -97,11 +97,13 @@ AI Management module packages are designed for various usage scenarios. Packages ## User Interface -This module provides UI integration for all three officially supported UI frameworks by ABP: +This module provides administration and client UI for the following UI stacks: * **MVC / Razor Pages** UI -* **Angular** UI -* **Blazor** UI (Server & WebAssembly) +* **Angular** UI +* **Blazorise** UI (Server & WebAssembly) +* **MudBlazor** UI (Server & WebAssembly) +* **React Admin Console** management UI ### Menu Items @@ -129,12 +131,12 @@ You can create a new workspace or edit an existing workspace in this page. The w * **API Key**: Authentication key (if required by provider) * **API Base URL**: Custom endpoint URL (optional) * **System Prompt**: Default system instructions -* **Temperature**: Response randomness (0.0-1.0) -* **Application Name**: Associate with specific application -* **Required Permission**: Permission needed to use this workspace +* **Temperature**: Provider-specific response randomness * **Embedder Provider / Model**: Embedding generator used for RAG * **Vector Store Provider / Settings**: Storage backend and connection settings for document vectors +The **Application Name** is assigned by the application that creates or synchronizes the workspace. Workspace access is managed from the resource permission action; it is not a field in the create/edit form. + #### Chat Interface The AI Management module includes a built-in chat interface for testing workspaces. You can: @@ -177,6 +179,21 @@ When a workspace has MCP servers associated, the AI model can invoke tools from ![ai-management-chat-mcp-tools](../../images/ai-management-chat-mcp-tools.png) +##### MCP Timeouts + +Use `McpClientFactoryOptions` when an MCP server needs more time to initialize or when a stdio connection test starts a slow process: + +```csharp +Configure(options => +{ + options.DefaultTimeoutMs = 180_000; + options.StdioInitializationTimeoutMs = 240_000; + options.StdioConnectionTestTimeoutSeconds = 300; +}); +``` + +`DefaultTimeoutMs` defaults to 120 seconds and applies to HTTP transports and, unless overridden, stdio initialization. `StdioConnectionTestTimeoutSeconds` defaults to 180 seconds and is constrained to 30-600 seconds by the connection-test service. HTTP connection tests use a fixed one-minute timeout. + #### Workspace Data Sources Workspace Data Sources page is used to upload and manage RAG documents per workspace. Uploaded files are processed and indexed in the background. @@ -207,12 +224,12 @@ When creating or managing a workspace, you can configure the following propertie | `ApiKey` | No | API authentication key (required by some providers) | | `ApiBaseUrl` | No | Custom endpoint URL (defaults to provider's default) | | `SystemPrompt` | No | Default system prompt for all conversations | -| `Temperature` | No | Response randomness (0.0-1.0, defaults to provider default) | +| `Temperature` | No | Provider-specific response randomness | | `Description` | No | Workspace description | | `IsActive` | No | Enable/disable the workspace (default: true) | -| `ApplicationName` | No | Associate workspace with specific application | -| `RequiredPermissionName` | No | Permission required to use this workspace | -| `IsSystem` | No | Whether it's a system workspace (read-only) | +| `ApplicationName` | No | Application identity recorded during creation or synchronization | +| `RequiredPermissionName` | No | Optional policy checked by client consumption services | +| `IsSystem` | No | Whether the workspace was synchronized from code | | `OverrideSystemConfiguration` | No | Allow database configuration to override code-defined settings | | `EmbedderProvider` | No | Embedding provider name (e.g., "OpenAI", "Ollama") | | `EmbedderModelName` | No | Embedding model identifier (e.g., "text-embedding-3-small") | @@ -231,27 +248,38 @@ The AI Management module supports two types of workspaces: * **Defined in code** using `PreConfigure` * **Cannot be deleted** through the UI -* **Read-only by default**, but can be overridden when `OverrideSystemConfiguration` is enabled +* **Use the code-defined chat client by default**; persisted provider settings take effect only when `OverrideSystemConfiguration` is enabled * **Useful for** application-critical AI features that must always be available -* **Created automatically** when the application starts +* **Synchronized automatically** when the application starts Example: ```csharp -PreConfigure(options => +public override void PreConfigureServices(ServiceConfigurationContext context) { - options.Workspaces.Configure(configuration => + PreConfigure(options => { - configuration.ConfigureChatClient(chatClientConfiguration => - { - chatClientConfiguration.Builder = new ChatClientBuilder( - sp => new OpenAIClient(apiKey).GetChatClient("gpt-4") - ); - }); + options.Workspaces.Configure(); }); +} +``` + +Configure the code-defined keyed client with the [Framework AI provider integration](../../framework/infrastructure/artificial-intelligence/index.md) used by your application. + +At startup, AI Management records the registered chat, embedding and vector-store providers and synchronizes the code-defined workspace names for the current application. When at least one code-defined workspace remains, names removed from that application's configuration are also removed from its synchronized set. An empty workspace collection skips the update, so removing the last code-defined workspace doesn't delete its persisted record automatically. Synchronization errors are logged without stopping application startup. + +You can disable startup synchronization or override the recorded application name: + +```csharp +Configure(options => +{ + options.AutoUpdateAtStartup = false; + options.ApplicationName = "MyAIGateway"; }); ``` +Disabling synchronization means code-defined workspaces are not added to or updated in the management database automatically. Their code-defined keyed clients can still be resolved by the Framework AI infrastructure. + #### Dynamic Workspaces * **Created through the UI** or programmatically via `ApplicationWorkspaceManager` and `IWorkspaceRepository` @@ -264,12 +292,16 @@ Example (data seeding): ```csharp public class WorkspaceDataSeederContributor : IDataSeedContributor, ITransientDependency { + private readonly IConfiguration _configuration; private readonly IWorkspaceRepository _workspaceRepository; private readonly ApplicationWorkspaceManager _applicationWorkspaceManager; + public WorkspaceDataSeederContributor( + IConfiguration configuration, IWorkspaceRepository workspaceRepository, ApplicationWorkspaceManager applicationWorkspaceManager) { + _configuration = configuration; _workspaceRepository = workspaceRepository; _applicationWorkspaceManager = applicationWorkspaceManager; } @@ -281,18 +313,32 @@ public class WorkspaceDataSeederContributor : IDataSeedContributor, ITransientDe provider: "OpenAI", modelName: "gpt-4"); - workspace.ApiKey = "your-api-key"; + workspace.ApiKey = _configuration["AI:OpenAI:ApiKey"]; workspace.SystemPrompt = "You are a helpful customer support assistant."; await _workspaceRepository.InsertAsync(workspace); } +} ``` +> [!WARNING] +> Provider API keys, embedding keys, vector-store connection settings, MCP headers and MCP credentials are sensitive persisted configuration. Do not hard-code them in source control. Restrict workspace and MCP administration permissions, protect the application database and configuration backups, and supply secrets from a secure configuration provider. Duplicating a workspace also duplicates its provider and RAG configuration, including credentials. + +### Resolution Precedence + +When an application requests a workspace chat client, AI Management resolves it in this order: + +1. If no persisted configuration exists, the workspace is inactive, or it is a system workspace with `OverrideSystemConfiguration = false`, resolution first tries the code-defined keyed `IChatClient` and then the default `IChatClient`. +2. An active dynamic workspace, or an overridden system workspace, uses the factory registered for its persisted `Provider` value. + +If neither fallback exists, an inactive workspace produces an inactive-workspace error; the other fallback cases produce a provider-not-found error. A configured provider without a registered factory also produces a provider-not-found error. + ### Workspace Naming Rules * Workspace names **must be unique** * Workspace names **cannot contain spaces** (use underscores or camelCase) -* Workspace names are **case-sensitive** + +Workspace names are global identifiers in an AI Management database. Workspaces, MCP server configurations, data sources and the data-source blob container are not tenant-scoped entities. In a multi-tenant application, use workspace resource permissions and application-level design to control access; do not treat these records or blobs as tenant-isolated storage. ## RAG with File Upload @@ -367,7 +413,12 @@ RAG is enabled per workspace when both embedding and vector store settings are c * `MongoDb`: Standard MongoDB connection string including database name. * `Pgvector`: Standard PostgreSQL/Npgsql connection string. -* `Qdrant`: Qdrant endpoint string (`http://host:port`, `https://host:port`, or `host:port`). +* `Qdrant`: Qdrant endpoint string (`http://host:port`, `https://host:port`, or `host:port`). The current provider discards the URL scheme and creates a non-TLS client from only the host and port. Do not rely on an `https://` value to enable TLS. + +> [!IMPORTANT] +> The `MongoDb` vector-store provider requires MongoDB Atlas or an Atlas CLI local deployment because it uses `$vectorSearch`. Standard MongoDB Docker images do not provide this feature. Create an Atlas Vector Search index named `vector_index` on the `AIVectorEmbeddings` collection, with `Embedding` as the vector field, cosine similarity and dimensions matching the configured embedding model. The provider creates the collection and its regular workspace index, but it does not create the Atlas vector index. + +The Pgvector provider creates the target database when its credentials permit it, enables the `vector` extension and creates workspace tables lazily. Qdrant creates a workspace-specific collection when the first vector is stored. The service account therefore needs the corresponding database, extension, table or collection privileges. #### Document Processing Pipeline @@ -376,9 +427,11 @@ When a file is uploaded as a workspace data source: 1. File is stored in blob storage. 2. `IndexDocumentJob` is queued. 3. `DocumentProcessingManager` extracts text using content-type-specific extractors. -4. Text is chunked (default chunk size: `1000`, overlap: `200`). -5. Embeddings are generated in batches and stored through the configured vector store. -6. Data source is marked as processed (`IsProcessed = true`). +4. Text is chunked with `1000` characters as a target size. The normal paragraph path carries up to `200` characters into the next chunk; oversized paragraphs are split by line with a `50`-character carry. An indivisible line can produce a chunk larger than the target. +5. Embeddings are generated in ordered batches and stored through the configured vector store. +6. The data source is marked as processed (`IsProcessed = true`) after the final batch succeeds. + +Each indexing run has an identifier and a per-data-source distributed lock. Stale and duplicate batches are ignored, while an out-of-order batch is retried by the background job system. A failed run can therefore leave partial vector data until a retry succeeds or the data source is re-indexed again. #### Workspace Data Source HTTP API @@ -395,20 +448,45 @@ The module exposes workspace data source endpoints under `/api/ai-management/wor #### Chat Integration Behavior -When a workspace has embedder configuration, AI Management wraps the chat client with a document search tool function named `search_workspace_documents`. +When an active dynamic workspace, or a system workspace with `OverrideSystemConfiguration = true`, is resolved through its persisted provider factory and has embedder configuration, AI Management wraps the factory-created client with a document search tool function named `search_documents`. The keyed/default fallback branches described in [Resolution Precedence](#resolution-precedence) return their code-defined client directly and don't add persisted RAG or MCP tools. * The tool delegates to `IDocumentSearchService` (`DocumentSearchService` by default). * The search currently uses `TopK = 5` chunks. * If RAG retrieval fails, chat continues without injected context. +The underlying chat client must be a `FunctionInvokingChatClient`. The built-in OpenAI and Ollama factories add function invocation automatically. A custom chat factory must build its client with `ChatClientBuilder.UseFunctionInvocation()` before RAG or MCP tools can execute. + #### Automatic Reindexing on Configuration Changes When workspace embedder or vector store configuration changes, AI Management automatically: * Initializes the new vector store configuration (if needed). -* Deletes existing embeddings when embedder provider/model changes. +* Attempts to delete existing embeddings when the embedder provider or model changes. * Re-queues all workspace data sources for re-indexing. +Embedding cleanup and automatic re-index queueing are best-effort follow-up operations. Their failures are logged without rolling back the workspace update. Use the data-source page's **Re-index All** action after correcting the configuration if automatic re-indexing could not be queued. + +### Configuring Indexing Options + +`AIManagementIndexingOptions` controls the work performed by background indexing jobs: + +```csharp +Configure(options => +{ + options.EmbeddingBatchSize = 100; + options.MaxConcurrentIndexingJobs = 2; + options.DistributedLockTimeoutSeconds = 15; +}); +``` + +| Property | Default | Description | +| -------- | ------- | ----------- | +| `EmbeddingBatchSize` | `50` | Maximum chunks embedded and stored by one batch job | +| `MaxConcurrentIndexingJobs` | `1` | Maximum indexing batches running concurrently across the application | +| `DistributedLockTimeoutSeconds` | `0` | Time to wait for an indexing lock; `0` performs an immediate attempt | + +Increase concurrency only after checking the embedding provider's rate limits and vector-store capacity. The concurrency limiter is application-wide; the per-data-source lock still prevents two batches from mutating the same data source concurrently. + ### Configuring Data Source Upload Options The `WorkspaceDataSourceOptions` class allows you to customize the file upload constraints for workspace data sources. You can configure the allowed file extensions, maximum file size, and content type mappings. @@ -418,15 +496,13 @@ public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { - // Add support for additional file types - options.AllowedFileExtensions = new[] { ".txt", ".md", ".pdf", ".docx", ".csv" }; + // Allow log files to use the built-in plain-text extractor. + options.AllowedFileExtensions = [".txt", ".md", ".pdf", ".log"]; // Increase the maximum file size to 50 MB options.MaxFileSize = 50 * 1024 * 1024; - // Add content type mappings for new extensions - options.ContentTypeMap[".docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; - options.ContentTypeMap[".csv"] = "text/csv"; + options.ContentTypeMap[".log"] = "text/plain"; }); } ``` @@ -439,6 +515,8 @@ public override void ConfigureServices(ServiceConfigurationContext context) | `MaxFileSize` | `long` | `10485760` (10 MB) | Maximum file size in bytes | | `ContentTypeMap` | `Dictionary` | `.txt`, `.md`, `.pdf` with their MIME types | Maps file extensions to MIME content types | +Adding an extension and MIME mapping only allows the upload. The mapped content type must also be supported by an `IDocumentTextExtractor`. Implement and register an extractor when adding a format that cannot use the built-in plain-text, Markdown or PDF extractors. + The options class also provides helper methods: | Method | Description | @@ -447,9 +525,6 @@ The options class also provides helper methods: | `GetAllowedExtensionsDisplay()` | Returns a comma-separated display string (e.g., ".txt, .md, .pdf") | | `GetAcceptAttribute()` | Returns a string for the HTML `accept` attribute (e.g., ".txt,.md,.pdf") | -> [!NOTE] -> Adding new file extensions also requires a matching content extractor to be registered for document processing. The built-in extractors support `.txt`, `.md`, and `.pdf` files. - #### Hosting-Level Upload Limits `WorkspaceDataSourceOptions.MaxFileSize` controls the module-level validation, but your hosting stack may reject large uploads before the request reaches AI Management. If you increase `MaxFileSize`, make sure the underlying server and proxy limits are also updated. @@ -523,6 +598,7 @@ The AI Management module defines the following permissions: | `AIManagement.Workspaces.Delete` | Delete workspaces | | `AIManagement.Workspaces.Playground` | Access workspace chat playground | | `AIManagement.Workspaces.ManagePermissions` | Manage workspace resource permissions | +| `Volo.AIManagement.Workspaces.Workspace.Consume` | Consume a specific workspace; granted as a resource permission | | `AIManagement.McpServers` | View MCP servers | | `AIManagement.McpServers.Create` | Create new MCP servers | | `AIManagement.McpServers.Update` | Edit existing MCP servers| @@ -539,9 +615,17 @@ The module also defines workspace data source permissions for RAG document opera | `AIManagement.WorkspaceDataSources.Download` | Download original uploaded file | | `AIManagement.WorkspaceDataSources.ReIndex` | Re-index one or all workspace files | -### Workspace-Level Permissions +### Workspace Consumption Authorization + +The `IChatCompletionClientAppService` client API, OpenAI-compatible API and MCP tool catalog authorize a workspace when the current user satisfies any one of these conditions: + +1. The user has `AIManagement.Workspaces.Playground`. +2. The workspace has a `RequiredPermissionName` and the user has that policy. +3. The user has the `Volo.AIManagement.Workspaces.Workspace.Consume` resource permission for that workspace ID. -In addition to module-level permissions, you can restrict access to individual workspaces by setting the `RequiredPermissionName` property: +Use the **Manage Permissions** action on the workspace page to grant the per-workspace resource permission. `AIManagement.Workspaces.ManagePermissions` controls access to that action. + +`RequiredPermissionName` is a programmatic alternative for workspaces tied to an application permission: ```csharp var workspace = await _applicationWorkspaceManager.CreateAsync( @@ -549,14 +633,11 @@ var workspace = await _applicationWorkspaceManager.CreateAsync( provider: "OpenAI", modelName: "gpt-4" ); -// Set a specific permission for the workspace workspace.RequiredPermissionName = MyAppPermissions.AccessPremiumWorkspaces; +await _workspaceRepository.InsertAsync(workspace); ``` -When a workspace has a required permission: - -* Only authorized users with that permission can access the workspace endpoints -* Users without the permission will receive an authorization error +Administrative workspace and data-source endpoints still use the module permissions in the tables above. A workspace consumption grant does not grant permission to edit the workspace, manage its credentials or upload RAG documents. ## Usage Scenarios @@ -569,57 +650,7 @@ The AI Management module is designed to support various usage patterns, from sim **Use this when:** You want to use AI in your application without any dependency on the AI Management module. -In this scenario, you only use the ABP Framework's AI features directly. You configure AI providers (like OpenAI) in your code and don't need any database or management UI. - -**Required Packages:** - -- `Volo.Abp.AI` -- Any Microsoft AI extensions (e.g., `Microsoft.Extensions.AI.OpenAI`) - -**Configuration:** - -```csharp -public class YourModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(options => - { - options.Workspaces.ConfigureDefault(configuration => - { - configuration.ConfigureChatClient(chatClientConfiguration => - { - chatClientConfiguration.Builder = new ChatClientBuilder( - sp => new OpenAIClient(apiKey).GetChatClient("gpt-4") - ); - }); - }); - }); - } -} -``` - -**Usage:** - -```csharp -public class MyService -{ - private readonly IChatClient _chatClient; - - public MyService(IChatClient chatClient) - { - _chatClient = chatClient; - } - - public async Task GetResponseAsync(string prompt) - { - var response = await _chatClient.CompleteAsync(prompt); - return response.Message.Text; - } -} -``` - -> See [Artificial Intelligence](../../framework/infrastructure/artificial-intelligence/index.md) documentation for more details about workspace configuration. +In this scenario, use the ABP Framework AI infrastructure and configure provider clients in code. No AI Management database, administration UI or commercial client packages are involved. See [Artificial Intelligence](../../framework/infrastructure/artificial-intelligence/index.md) for the current static workspace registration and consumption APIs. ### Scenario 2: AI Management with Domain Layer Dependency (Local Execution) @@ -646,49 +677,12 @@ In this scenario, you install the AI Management module with its database layer, > Note: `Volo.AIManagement.EntityFrameworkCore` transitively includes `Volo.AIManagement.Domain` and `Volo.Abp.AI.AIManagement` packages. -**Workspace Definition Options:** - -**Option 1 - System Workspace (Code-based):** - -```csharp -public class YourModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(options => - { - options.Workspaces.Configure(configuration => - { - configuration.ConfigureChatClient(chatClientConfiguration => - { - // Configuration will be populated from database - }); - }); - }); - } -} -``` - -**Option 2 - Dynamic Workspace (UI-based):** - -No code configuration needed. Define workspaces through: +Define dynamic workspaces through: - The AI Management UI (navigate to AI Management > Workspaces) - Data seeding in your `DataSeeder` class -**Using Chat Client:** - -```csharp -public class MyService -{ - private readonly IChatClient _chatClient; - - public MyService(IChatClient chatClient) - { - _chatClient = chatClient; - } -} -``` +For a system workspace, define the typed workspace and its code client as described in [System Workspaces](#system-workspaces). Enable `OverrideSystemConfiguration` on that workspace only when persisted provider settings should replace the code client. ### Scenario 3: AI Management Client with Remote Execution @@ -714,25 +708,6 @@ Add the remote service endpoint in your `appsettings.json`: } ``` -Optionally define workspace in your module: - -```csharp -public class YourModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - PreConfigure(options => - { - // Optional: Pre-define workspace type for type safety - options.Workspaces.Configure(configuration => - { - // Configuration will be fetched from remote service - }); - }); - } -} -``` - **Usage:** ```csharp @@ -749,14 +724,14 @@ public class MyService { var request = new ChatClientCompletionRequestDto { - Messages = new List - { - new ChatMessageDto { Role = "user", Content = prompt } - } + Messages = + [ + new ChatMessageDto { Role = ChatRole.User, Content = prompt } + ] }; var response = await _chatService.ChatCompletionsAsync(workspaceName, request); - return response.Content; + return response.Text; } // For streaming responses @@ -764,15 +739,15 @@ public class MyService { var request = new ChatClientCompletionRequestDto { - Messages = new List - { - new ChatMessageDto { Role = "user", Content = prompt } - } + Messages = + [ + new ChatMessageDto { Role = ChatRole.User, Content = prompt } + ] }; await foreach (var update in _chatService.StreamChatCompletionsAsync(workspaceName, request)) { - yield return update.Content; + yield return update.Text; } } } @@ -799,8 +774,8 @@ Same as Scenario 3, configure the remote AI Management service in `appsettings.j Once configured, other applications can call your application's endpoints: -- `POST /api/ai-management-client/chat-completion` for chat completions -- `POST /api/ai-management-client/stream-chat-completion` for streaming responses +- `POST /api/chat-completion/{workspaceName}` for chat completions +- `POST /api/chat-completion/{workspaceName}/stream` for streaming responses Your application acts as a proxy, forwarding these requests to the AI Management microservice. @@ -823,7 +798,7 @@ Each AI Management **workspace** appears as a selectable model in the client app ![ai-management-openai-anythingllm2](../../images/ai-management-openai-anythingllm2.png) -#### Available Endpoints +### Available Endpoints | Endpoint | Method | Description | | ---------------------------- | ------ | ----------------------------------------------- | @@ -832,15 +807,14 @@ Each AI Management **workspace** appears as a selectable model in the client app | `/v1/models` | GET | List available models (workspaces) | | `/v1/models/{modelId}` | GET | Retrieve a single model (workspace) | | `/v1/embeddings` | POST | Generate embeddings | -| `/v1/files` | GET | List uploaded files | -| `/v1/files` | POST | Upload a file | -| `/v1/files/{fileId}` | GET | Get file info | -| `/v1/files/{fileId}` | DELETE | Delete a file | -| `/v1/files/{fileId}/content` | GET | Download file content | -All endpoints require authentication via a **Bearer token** in the `Authorization` header. +All endpoints require an authenticated user. Non-browser API clients normally send an access token as a **Bearer token** in the `Authorization` header. -#### Usage +The value of `model` is an AI Management workspace name, not the provider's model name. `GET /v1/models` returns only workspaces the current user is authorized to consume. The same workspace authorization is applied before chat, completion, model and embedding operations. + +The module also exposes file-management routes backed by workspace data sources. They require workspace-specific parameters and are not a drop-in implementation of the OpenAI Files API. Use the [Workspace Data Source HTTP API](#workspace-data-source-http-api) for portable upload, list, download, delete and re-index operations. + +### Usage The general pattern for connecting any OpenAI-compatible client: @@ -877,13 +851,13 @@ curl -X POST https://localhost:44336/v1/chat/completions \ }' ``` -> The OpenAI-compatible endpoints are available from both the `Volo.AIManagement.Client.HttpApi` and `Volo.AIManagement.HttpApi` packages, depending on your deployment scenario. +> The OpenAI-compatible `/v1` endpoints are provided by `Volo.AIManagement.Client.HttpApi`. `Volo.AIManagement.HttpApi` instead exposes the management and integration APIs. ## Client Usage AI Management uses different packages depending on the usage scenario: -- **`Volo.AIManagement.*` packages**: These contain the core AI functionality and are used when your application hosts and manages its own AI operations. These packages don't expose any application service and endpoints to be consumed by default. +- **`Volo.AIManagement.*` packages**: These contain the core AI functionality for applications that host and manage AI operations. The application and HTTP API packages expose management and integration services. - **`Volo.AIManagement.Client.*` packages**: These are designed for applications that need to consume AI services from a remote application. They provide both server and client side of remote access to the AI services. @@ -923,6 +897,9 @@ You can customize the chat widget with the following properties: - `Title`: The title of the chat widget. - `ShowStreamCheckbox`: Whether to show the stream checkbox. Allows user to toggle streaming on and off. Default is `false`. - `UseStreaming`: Default streaming behavior. Can be overridden by user when `ShowStreamCheckbox` is true. +- `DisableWhenNoConversation`: Disables input until a `ConversationId` is selected. Default is `false`. +- `ShowUsageDetails`: Shows token usage and tool calls for assistant messages. Default is `true`. +- `ShowDetailedSourceInformation`: Shows chunk-level RAG source details instead of grouping sources by file. Default is `false`. ```csharp @await Component.InvokeAsync(typeof(ChatClientChatViewComponent), new ChatClientChatViewModel @@ -1048,7 +1025,7 @@ export const appConfig: ApplicationConfig = { }; ``` -The AI Management module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. It is available for import from `@volo/abp.ng.ai-management`. +The AI Management module should be imported and lazy-loaded in your routing array. It has a `createRoutes` function for configuration and is available from `@volo/abp.ng.ai-management`. ```js // app.routes.ts @@ -1057,11 +1034,13 @@ const APP_ROUTES: Routes = [ { path: 'ai-management', loadChildren: () => - import('@volo/abp.ng.ai-management').then(m => m.createRoutes(/* options here */)), + import('@volo/abp.ng.ai-management').then(m => m.createRoutes()), }, ]; ``` +`createRoutes` optionally accepts `AIManagementConfigOptions`. It provides entity-action, toolbar-action and entity-property contributors for workspaces and MCP servers, plus create/edit form contributors for workspaces. The Workspaces, Chat Playground, MCP Servers and Workspace Data Sources routes also use keys from `eAIManagementComponents`, so they can be replaced through ABP's replaceable component system. + #### Services / Models AI Management 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: @@ -1084,14 +1063,19 @@ export const environment = { AIManagement: { url: 'AI Management remote url here', }, + AIManagementClient: { + url: 'AI Management client remote url here', + }, // other api configurations }, }; ``` -The AI Management module remote URL configurations shown above are optional. +Use `AIManagement` for management and integration proxies. Non-streaming requests from the Angular chat widget use the generated `ChatCompletionClientService` proxy with `AIManagementClient`. When streaming is enabled, both the stream-start POST request and the subsequent EventSource connection use `default.url` instead of `AIManagementClient`. -> If you don't set the `AIManagement` property, the `default.url` will be used as fallback. +Both named entries are optional and generated proxy requests fall back independently to `default.url`. `default.url` is also the global fallback for other ABP and application requests that don't specify a configured named API, so changing it redirects those requests too. + +For split hosting, configure `AIManagement` and `AIManagementClient` for their respective hosts. Before pointing `default.url` to the client API and enabling streaming, make sure every other request uses an appropriate named API or that the client host provides or forwards all endpoints that still rely on the global fallback. #### The Chat Widget @@ -1103,12 +1087,19 @@ The `@volo/abp.ng.ai-management` package provides a `ChatInterfaceComponent` (`a ``` - `workspaceName` (required): The name of the workspace to use. -- `conversationId`: The unique identifier for persisting and retrieving chat history from client-side storage. When provided, the chat history is stored in the browser and restored when the user revisits the page. If `null`, the chat is ephemeral and will be lost when the component is destroyed. +- `conversationId`: The unique identifier for persisting and retrieving chat history from client-side storage. When provided, the history is restored for the current user and workspace. - `providerName`: The name of the AI provider. Used for displaying contextual error messages. +- `allowEphemeral`: Allows sending without a `conversationId`. The default is `false`; set it to `true` for an in-memory conversation that is discarded with the component. +- `showUsageDetails`: Shows usage and tool-call details. Default is `true`. +- `showStreamCheckbox`: Shows the streaming toggle. Default is `true`. + +The component emits `messageSent` and `messageReceived` events for application-level conversation orchestration. ### Blazor UI @@ -1117,26 +1108,22 @@ The `@volo/abp.ng.ai-management` package provides a `ChatInterfaceComponent` (`a The AI Management module remote endpoint URLs can be configured in your `appsettings.json`: ```json -"RemoteServices": { - "Default": { - "BaseUrl": "Default url here" - }, - "AIManagement": { - "BaseUrl": "AI Management remote url here" +{ + "RemoteServices": { + "Default": { + "BaseUrl": "Default url here" + }, + "AIManagement": { + "BaseUrl": "AI Management remote url here" + }, + "AIManagementClient": { + "BaseUrl": "AI Management client remote url here" + } } } ``` -For **Blazor WebAssembly**, you can also configure the remote endpoint URL via `AIManagementClientBlazorWebAssemblyOptions`: - -```csharp -Configure(options => -{ - options.RemoteServiceUrl = builder.Configuration["RemoteServices:AIManagement:BaseUrl"]; -}); -``` - -> If you don't set the `BaseUrl` for AIManagement, the `Default.BaseUrl` will be used as fallback. +Use the `AIManagement` remote service for management and integration clients. Use `AIManagementClient` for the remote chat and OpenAI-compatible client APIs. Blazor WebAssembly uses the standard ABP remote-service configuration shown above; it doesn't require a module-specific options class. If a named `BaseUrl` isn't set, ABP uses `Default.BaseUrl` as the fallback. #### The Chat Widget @@ -1152,10 +1139,15 @@ The `Volo.AIManagement.Client.Blazor` package provides a `ChatClientChat` Blazor ``` - `WorkspaceName` (required): The name of the workspace to use. -- `ConversationId`: The unique identifier for persisting and retrieving chat history from client-side storage. When provided, the chat history is stored in the browser's local storage and restored when the user revisits the page. If not provided or `null`, the chat is ephemeral and will be lost when the component is disposed. +- `ConversationId` (required to send messages): The unique identifier for persisting and retrieving chat history from client-side storage. The component disables message input while this value is null or empty. - `Title`: The title displayed in the chat widget header. - `ShowStreamCheckbox`: Whether to show a checkbox that allows the user to toggle streaming on and off. Default is `false`. -- `OnFirstMessage`: An `EventCallback` that is triggered when the first message is sent in a conversation. It can be used to determine the chat title after the first prompt like applied in the chat playground. The event args contain `ConversationId` and `Message` properties. +- `UseStreaming`: Initial streaming mode. Default is `true`. +- `ShowUsageDetails`: Shows token usage and tool calls. Default is `true` in the Blazorise component. +- `ShowDetailedSourceInformation`: Shows chunk-level RAG source information. Default is `true` in the Blazorise component. +- `OnFirstMessage`: An `EventCallback` that is triggered when the first message is sent in a conversation. It can be used to determine the chat title after the first prompt like applied in the chat playground. The event args contain `ConversationId` and `Message` properties. + +The MudBlazor package exposes the same core `WorkspaceName`, `ConversationId`, `Title`, `ShowStreamCheckbox` and `OnFirstMessage` parameters. The detailed usage/source display parameters above are specific to the Blazorise component. ```xml ``` -## Using Dynamic Workspace Configurations for custom requirements +## Reading Dynamic Workspace Configuration -The AI Management module allows you to access only configuration of a workspace without resolving pre-constructed chat client. This is useful when you want to use a workspace for your own purposes and you don't need to use the chat client. -The `IWorkspaceConfigurationStore` service is used to access the configuration of a workspace. It has multiple implementations according to the usage scenario. +Use `IWorkspaceConfigurationStore` when trusted server-side code needs the persisted workspace configuration without resolving an `IChatClient`. The store has local and remote implementations for the corresponding deployment scenarios. ```csharp public class MyService @@ -1179,34 +1170,29 @@ public class MyService _workspaceConfigurationStore = workspaceConfigurationStore; } - public async Task DoSomethingAsync() + public async Task GetModelNameAsync() { - // Get the configuration of the workspace that can be managed dynamically. - var configuration = await _workspaceConfigurationStore.GetAsync("MyWorkspace"); - - // Do something with the configuration - var kernel = Kernel.CreateBuilder() - .AddAzureOpenAIChatClient( - config.ModelName!, - new Uri(config.ApiBaseUrl), - config.ApiKey - ) - .Build(); + var configuration = await _workspaceConfigurationStore + .GetOrNullAsync("MyWorkspace"); + + return configuration?.ModelName; } } ``` +The returned configuration can include provider credentials. Keep it inside trusted server-side services and do not serialize it into an application response. Use `IChatClient` or `IChatCompletionClientAppService` when you only need to execute a chat request. + ## Implementing Custom AI Provider Factories -While the AI Management module provides built-in support for OpenAI through the `Volo.AIManagement.OpenAI` package, you can easily add support for other AI providers by implementing a custom `IChatClientFactory`. +AI Management provides built-in OpenAI and Ollama factories through the `Volo.AIManagement.OpenAI` and `Volo.AIManagement.Ollama` packages. You can add another provider by implementing a custom `IChatClientFactory`. ### Understanding the Factory Pattern -The AI Management module uses a factory pattern to create `IChatClient` instances based on the provider configuration stored in the database. Each provider (OpenAI, Ollama, Azure OpenAI, etc.) needs its own factory implementation. +The AI Management module uses a factory pattern to create `IChatClient` instances based on the provider configuration stored in the database. Each provider name needs one registered factory implementation. ### Creating a Custom Factory -Here's how to implement a factory for Ollama as an example: +The following example registers a separate `CustomOllama` provider to demonstrate the factory contract. Use the built-in `Volo.AIManagement.Ollama` package when you only need the standard Ollama integration. #### Step 1: Install the Provider's NuGet Package @@ -1228,20 +1214,18 @@ using Volo.Abp.DependencyInjection; namespace YourNamespace; -public class OllamaChatClientFactory : IChatClientFactory, ITransientDependency +public class CustomOllamaChatClientFactory : IChatClientFactory, ITransientDependency { - public string Provider => "Ollama"; + public string Provider => "CustomOllama"; public Task CreateAsync(ChatClientCreationConfiguration configuration) { - // Create the Ollama client with configuration from database - var client = new OllamaApiClient( + var builder = new ChatClientBuilder(sp => new OllamaApiClient( configuration.ApiBaseUrl ?? "http://localhost:11434", - configuration.ModelName - ); + configuration.ModelName ?? throw new InvalidOperationException("A model is required."))); - // Return as IChatClient - return Task.FromResult(client); + builder.UseFunctionInvocation(); + return Task.FromResult(builder.Build()); } } ``` @@ -1255,13 +1239,13 @@ public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { - options.AddFactory("Ollama"); + options.AddFactory("CustomOllama"); }); } ``` -> [!TIP] -> For production scenarios, you may want to add validation for the factory configuration. +> [!IMPORTANT] +> Validate every required configuration value in the factory. Build a `FunctionInvokingChatClient` with `UseFunctionInvocation()` when the workspace can use RAG or MCP tools. ### Available Configuration Properties @@ -1270,6 +1254,7 @@ The `ChatClientCreationConfiguration` object provides the following properties f | Property | Type | Description | | ------------------------ | ------- | ------------------------------------------- | | `Name` | string | Workspace name | +| `WorkspaceId` | Guid? | Persisted workspace ID, when available | | `Provider` | string | Provider name (e.g., "OpenAI", "Ollama") | | `ApiKey` | string? | API key for authentication | | `ModelName` | string | Model identifier (e.g., "gpt-4", "mistral") | @@ -1279,6 +1264,7 @@ The `ChatClientCreationConfiguration` object provides the following properties f | `Description` | string? | Workspace description | | `IsActive` | bool | Whether the workspace is active | | `IsSystem` | bool | Whether it's a system workspace | +| `OverrideSystemConfiguration` | bool | Whether persisted settings override a system workspace | | `RequiredPermissionName` | string? | Permission required to use this workspace | | `HasEmbedderConfiguration` | bool | Whether the workspace has embedder/RAG configuration | @@ -1286,6 +1272,8 @@ The `ChatClientCreationConfiguration` object provides the following properties f Here's an example of implementing a factory for Azure OpenAI: +Install the `Azure.AI.OpenAI` NuGet package before adding this factory. + ```csharp using Azure.AI.OpenAI; using Azure; @@ -1306,8 +1294,13 @@ public class AzureOpenAIChatClientFactory : IChatClientFactory, ITransientDepend new AzureKeyCredential(configuration.ApiKey ?? throw new ArgumentNullException(nameof(configuration.ApiKey))) ); - var chatClient = client.GetChatClient(configuration.ModelName); - return Task.FromResult(chatClient.AsIChatClient()); + var modelName = configuration.ModelName ?? + throw new ArgumentNullException(nameof(configuration.ModelName)); + var builder = new ChatClientBuilder(sp => + client.GetChatClient(modelName).AsIChatClient()); + + builder.UseFunctionInvocation(); + return Task.FromResult(builder.Build()); } } ``` @@ -1318,7 +1311,7 @@ After implementing and registering your factory: 1. **Through UI**: Navigate to the AI Management workspaces page and create a new workspace: - - Select your provider name (e.g., "Ollama", "AzureOpenAI") + - Select your provider name (for this example, `CustomOllama`) - Configure the API settings - Set the model name @@ -1326,8 +1319,8 @@ After implementing and registering your factory: ```csharp var workspace = await _applicationWorkspaceManager.CreateAsync( - name: "MyOllamaWorkspace", - provider: "Ollama", + name: "MyCustomOllamaWorkspace", + provider: "CustomOllama", modelName: "mistral" ); workspace.ApiBaseUrl = "http://localhost:11434"; @@ -1396,16 +1389,16 @@ The module exposes the following integration services for inter-service communic Workspace configurations are cached for performance. The cache key format: -``` -WorkspaceConfiguration:{ApplicationName}:{WorkspaceName} +```text +WorkspaceConfiguration:{WorkspaceName} ``` +The cache is invalidated when workspaces are created, updated or deleted. A rename invalidates both the old and current workspace names, and invalidation participates in the current unit of work. + ### HttpApi Client Layer - `IntegrationWorkspaceConfigurationStore`: Integration service for remote workspace configuration retrieval. Implements `IWorkspaceConfigurationStore` interface. -The cache is automatically invalidated when workspaces are created, updated, or deleted. - ## See Also - [Artificial Intelligence Infrastructure](../../framework/infrastructure/artificial-intelligence/index.md): Learn about the underlying AI workspace infrastructure From 9ed7c0e5570d1281e0e4a2171327f748341059bf Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 28/41] Expand SaaS module documentation --- docs/en/modules/saas.md | 335 ++++++++++++++++++++++++++-------------- 1 file changed, 215 insertions(+), 120 deletions(-) diff --git a/docs/en/modules/saas.md b/docs/en/modules/saas.md index 7d78a4e9df..a1fb887253 100644 --- a/docs/en/modules/saas.md +++ b/docs/en/modules/saas.md @@ -9,41 +9,43 @@ > 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(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: +```csharp +[Authorize] +public class IndexModel : PageModel +{ + protected ISubscriptionAppService SubscriptionAppService { get; } + protected ICurrentTenant CurrentTenant { get; } - ```csharp - public class IndexModel : PageModel - { - protected ISubscriptionAppService SubscriptionAppService { get; } - - protected ICurrentTenant CurrentTenant { get; } - - public IndexModel( - ISubscriptionAppService subscriptionAppService, - ICurrentTenant currentTenant) - { - SubscriptionAppService = subscriptionAppService; - CurrentTenant = currentTenant; - } - - public async Task OnPostAsync(Guid editionId) - { - var paymentRequest = await SubscriptionAppService.CreateSubscriptionAsync(editionId, CurrentTenant.GetId()); - - return LocalRedirectPreserveMethod("/Payment/GatewaySelection?paymentRequestId=" + paymentRequest.Id); - } - } - -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 OnPostAsync(Guid editionId) + { + var paymentRequest = await SubscriptionAppService.CreateSubscriptionAsync( + editionId, + CurrentTenant.GetId() + ); + + return LocalRedirectPreserveMethod( + "/Payment/GatewaySelection?paymentRequestId=" + paymentRequest.Id + ); + } +} +``` + +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. ![saas-module-tenants-page](../images/saas-module-tenants-page.png) @@ -113,41 +121,55 @@ You can create a new tenant or edit a tenant in this page: ![saas-module-tenant-edit-modal](../images/saas-module-tenant-edit-modal.png) +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. ![saas-module-tenant-connection-strings-modal](../images/saas-module-tenant-connection-strings-modal.png) -##### Module Specific Connection Strings +##### Module-Specific Connection Strings 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(options => +Configure(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. ![saas-module-tenant-module-specific-connection-strings-modal](../images/saas-module-tenant-module-specific-connection-strings-modal.png) ##### Tenant Features -You can set features of tenants. +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. ![saas-module-features-modal](../images/saas-module-features-modal.png) -#### Edition management +#### Edition Management -Editions page is used to manage the editions in your system. +The Editions page is used to manage the editions in your system. ![saas-module-editions-page](../images/saas-module-editions-page.png) @@ -155,21 +177,25 @@ You can create a new edition or edit an existing edition in this page: ![saas-module-edition-edit-modal](../images/saas-module-edition-edit-modal.png) +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. + ##### Edition Features -You can set features of an edition in this page: +You can set features of an edition on this page: ![saas-module-features-modal](../images/saas-module-features-modal.png) -## Data seed +## Data Seed + +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: + +```csharp +ObjectExtensionManager.Instance.Modules() + .ConfigureSaas(saas => + { + saas.ConfigureTenant(tenant => + { + tenant.AddOrUpdateProperty("ExternalId"); + }); + + saas.ConfigureEdition(edition => + { + edition.AddOrUpdateProperty("CatalogCode"); + }); + }); +``` + +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. +- `SubscriptionAppService` (implements `ISubscriptionAppService`): Creates Payment subscription requests for tenant-edition subscriptions. -### Database providers +### Database Providers #### Common -##### Table / collection prefix & schema +##### Table/Collection Prefix & Schema 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. +- `Saas.Tenants`: Views tenant management. + - `Saas.Tenants.Create` + - `Saas.Tenants.Update` + - `Saas.Tenants.Delete` + - `Saas.Tenants.ManageFeatures` + - `Saas.Tenants.ManageConnectionStrings` + - `Saas.Tenants.SetPassword` + - `Saas.Tenants.Impersonation` + - `AuditLogging.ViewChangeHistory:Volo.Saas.Tenant` +- `Saas.Editions`: Views edition management. + - `Saas.Editions.Create` + - `Saas.Editions.Update` + - `Saas.Editions.Delete` + - `Saas.Editions.ManageFeatures` + - `AuditLogging.ViewChangeHistory:Volo.Saas.Edition` + +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'; export const appConfig: ApplicationConfig = { @@ -265,11 +336,13 @@ export const appConfig: ApplicationConfig = { }; ``` -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 = [

Options

-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: ```bash abp generate-proxy --module saas @@ -302,47 +376,71 @@ abp generate-proxy --module saas #### Replaceable Components -`eSaasComponents` enum provides all replaceable component keys. It is available for import from `@volo/abp.ng.saas`. +`eSaasComponents` is available for import from `@volo/abp.ng.saas`. The following keys are wired to replaceable components: -Please check [Component Replacement document](../framework/ui/angular/component-replacement.md) for details. +- `eSaasComponents.Editions`: Editions page. +- `eSaasComponents.Tenants`: Tenants page. +- `eSaasComponents.ConnectionStrings` is also exported, but the current connection-strings template isn't wired to this replacement key. +- `eSaasComponents.SetTenantPassword`: Set-tenant-password modal content. +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: + +```csharp +Configure(options => +{ + options.AutoEventSelectors.Add(); + options.AutoEventSelectors.Add(); +}); ``` + +**Example: Subscribe to the opt-in tenant-created entity event** + +```csharp public class MyHandler : IDistributedEventHandler>, ITransientDependency @@ -355,9 +453,6 @@ public class MyHandler : } ``` +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. From b911c13fe03c35ce4c55670fe9eb86f0c7bcacd4 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:10:39 +0800 Subject: [PATCH 29/41] Expand Payment module documentation --- docs/en/modules/payment-custom-gateway.md | 292 +++++++++++++++++----- docs/en/modules/payment.md | 287 +++++++++++---------- 2 files changed, 385 insertions(+), 194 deletions(-) diff --git a/docs/en/modules/payment-custom-gateway.md b/docs/en/modules/payment-custom-gateway.md index df142f7a8c..c648703efe 100644 --- a/docs/en/modules/payment-custom-gateway.md +++ b/docs/en/modules/payment-custom-gateway.md @@ -9,86 +9,179 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. -This document explains creating custom a payment gateway that's different than the existing ones in the [Payment Module](payment#packages). +This document explains how to create a custom payment gateway that is different from the built-in gateways in the [Payment Module](payment#packages). ## Creating Core Operations -- Create **MyPaymentGateway.cs** in the **Domain** layer of your project and implement `IPaymentGateway`. +- Create **MyPaymentGateway.cs** in the **Domain** layer of your project and implement `IPaymentGateway`. The gateway client in this example represents your provider-specific SDK adapter. ```csharp - public class MyPaymentGateway : IPaymentGateway, ITransientDependency - { - private readonly IPaymentRequestRepository paymentRequestRepository; - - public MyPaymentGateway(IPaymentRequestRepository paymentRequestRepository) - { - this.paymentRequestRepository = paymentRequestRepository; - } + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Volo.Abp.DependencyInjection; + using Volo.Payment.Gateways; + using Volo.Payment.Requests; + + public interface IMyGatewayClient + { + Task CreateCheckoutAsync( + Guid paymentRequestId, + float totalPrice, + string currency, + string returnUrl, + string cancelUrl); + + Task VerifyAsync( + IReadOnlyDictionary parameters); + + Task ValidateAndHandleWebhookAsync( + string payload, + IReadOnlyDictionary headers); + } - public async Task StartAsync(PaymentRequest paymentRequest, PaymentRequestStartInput input) - { - var totalPrice = paymentRequest.Products.Sum(x => x.TotalPrice); + public interface IMyGatewayTransactionRepository + { + Task TryBindAsync( + string providerTransactionId, + Guid paymentRequestId); + } - var checkoutLink = // Some operations here + public record MyGatewayPaymentResult( + Guid PaymentRequestId, + bool IsPaid, + float Amount, + string Currency, + string ProviderTransactionId, + string FailureReason); - return new PaymentRequestStartResult - { - CheckoutLink = checkoutLink + "returnUrl=" + input.ReturnUrl - }; - } + public class MyPaymentGateway : IPaymentGateway, ITransientDependency + { + private readonly IPaymentRequestRepository _paymentRequestRepository; + private readonly IMyGatewayClient _gatewayClient; + private readonly IMyGatewayTransactionRepository _gatewayTransactionRepository; + + public MyPaymentGateway( + IPaymentRequestRepository paymentRequestRepository, + IMyGatewayClient gatewayClient, + IMyGatewayTransactionRepository gatewayTransactionRepository) + { + _paymentRequestRepository = paymentRequestRepository; + _gatewayClient = gatewayClient; + _gatewayTransactionRepository = gatewayTransactionRepository; + } - public async Task CompleteAsync(Dictionary parameters) - { - var token = parameters["token"]; // You can get any parameter from your gateway provides. Example: token, id, hash etc. + public bool IsValid( + PaymentRequest paymentRequest, + Dictionary properties) + { + return paymentRequest.Products.Count > 0; + } - var result = ;// provider.Validate(token); Validate the payment here + public async Task StartAsync( + PaymentRequest paymentRequest, + PaymentRequestStartInput input) + { + var checkoutLink = await _gatewayClient.CreateCheckoutAsync( + paymentRequest.Id, + paymentRequest.Products.Sum(product => product.TotalPrice), + paymentRequest.Currency, + input.ReturnUrl, + input.CancelUrl); + + return new PaymentRequestStartResult + { + CheckoutLink = checkoutLink + }; + } - var paymentRequest = await paymentRequestRepository.FindAsync(result.Id); + public async Task CompleteAsync( + Dictionary parameters) + { + var result = await _gatewayClient.VerifyAsync(parameters); + var paymentRequest = await _paymentRequestRepository.GetAsync( + result.PaymentRequestId); + + if (result.IsPaid) + { + var expectedAmount = paymentRequest.Products.Sum( + product => product.TotalPrice); + + if (result.PaymentRequestId != paymentRequest.Id || + result.Amount != expectedAmount || + !string.Equals( + result.Currency, + paymentRequest.Currency, + StringComparison.OrdinalIgnoreCase) || + string.IsNullOrWhiteSpace(result.ProviderTransactionId)) + { + throw new InvalidOperationException( + "The provider payment does not match the payment request."); + } + + if (!await _gatewayTransactionRepository.TryBindAsync( + result.ProviderTransactionId, + paymentRequest.Id)) + { + throw new InvalidOperationException( + "The provider transaction is already bound to another payment request."); + } + + paymentRequest.Complete(); + } + else + { + paymentRequest.Failed(result.FailureReason); + } + + return await _paymentRequestRepository.UpdateAsync(paymentRequest); + } - paymentRequest.SetState(PaymentRequestState.Completed); // completed or anything else according to your result. + public Task HandleWebhookAsync( + string payload, + Dictionary headers) + { + return _gatewayClient.ValidateAndHandleWebhookAsync(payload, headers); + } + } + ``` - return paymentRequest; - } + `IsValid` controls whether the gateway is offered for a specific payment request. `StartAsync` passes the request currency to the provider adapter together with the amount. `CompleteAsync` verifies the provider response and reconciles the request identifier, amount, currency, and provider transaction identifier before changing the request state. `HandleWebhookAsync` must validate the webhook signature or equivalent authenticity proof before processing its payload. - public Task HandleWebhookAsync(string payload, Dictionary headers) - { - // You can leave unimplemented if you not configure webhooks. - throw new System.NotImplementedException(); - } + `IMyGatewayTransactionRepository` is application-owned; it isn't part of the Payment module. Implement `TryBindAsync` as an atomic insert-or-match operation. For this one-time gateway, add unique database constraints for both the provider transaction identifier and the payment request identifier, accept an existing row only when the same pair is retried, and execute the binding and payment-request update in the same unit of work. This persists the provider transaction identifier while rejecting cross-request replay and a different transaction for an already-bound request. - public bool IsValid(PaymentRequest paymentRequest, Dictionary properties) - { - // You can check some custom logic here to make this gateway available or not. - return true; - } - } - ``` + The Payment HTTP API forwards `POST /api/payment/{paymentMethod}/webhook` to `HandleWebhookAsync`. Configure the provider to use this URL with your gateway name as `paymentMethod`, for example `/api/payment/MyGateway/webhook`. Always validate the provider signature before processing the payload. - You should also configure `PaymentOptions` for your gateway in the **Domain** layer of your project as shown below. + Add `using Volo.Abp.Localization;` to the module class file for `FixedLocalizableString`. + ```csharp Configure(options => - { - options.Gateways.Add(new PaymentGatewayConfiguration( - "MyGateway", - LocalizableString.Create("MyGateway"), - isSubscriptionSupported: false, - typeof(MyPaymentGateway) - )); - }); + { + options.Gateways.Add(new PaymentGatewayConfiguration( + "MyGateway", + new FixedLocalizableString("MyGateway"), + isSubscriptionSupported: false, + typeof(MyPaymentGateway) + )); + }); ``` ## Creating the UI -There are 2 types of pages that are supported by default. You can define a pre-payment page and post-payment page. +Two page types are supported by default: a pre-payment page and a post-payment page. -- Create **PreCheckout.cshtml** and **PreCheckout.cshtml.cs** +- Create **Pages/MyGateway/PreCheckout.cshtml** and **Pages/MyGateway/PreCheckout.cshtml.cs**. ```html + @page "/MyGateway/PreCheckout" @model PreCheckoutModel

Pre Checkout

- @@ -96,36 +189,117 @@ There are 2 types of pages that are supported by default. You can define a pre-p ``` ```csharp + using System; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Options; + using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + using Volo.Payment; + using Volo.Payment.Requests; + public class PreCheckoutModel : AbpPageModel { - [BindProperty] public Guid PaymentRequestId { get; set; } + private readonly IOptions _paymentWebOptions; + private readonly IPaymentRequestAppService _paymentRequestAppService; - public virtual ActionResult OnGet() + [BindProperty] + public Guid PaymentRequestId { get; set; } + + public PreCheckoutModel( + IOptions paymentWebOptions, + IPaymentRequestAppService paymentRequestAppService) { - // GET operation is not supported here. All the selected gateway requests will be sent as POST. - return BadRequest(); + _paymentWebOptions = paymentWebOptions; + _paymentRequestAppService = paymentRequestAppService; } - public virtual async Task OnPostAsync() + public virtual ActionResult OnGet() { - // You can get the payment request from the database by using `PaymentRequestId` and render something on the UI side. + return BadRequest(); } - public virtual async Task OnPostContinueToCheckout() + public virtual async Task OnPostAsync() { - return Redirect("the-actual-checkout-link-of-gateway"); + await _paymentWebOptions.SetAsync(); + var rootUrl = _paymentWebOptions.Value.RootUrl.TrimEnd('/'); + + var result = await _paymentRequestAppService.StartAsync( + "MyGateway", + new PaymentRequestStartDto + { + PaymentRequestId = PaymentRequestId, + ReturnUrl = rootUrl + "/MyGateway/PostCheckout", + CancelUrl = rootUrl + }); + + return Redirect(result.CheckoutLink); } } ``` -- Create **PostCheckout.cshtml** and **PostCheckout.cshtml.cs** +- Create **Pages/MyGateway/PostCheckout.cshtml** and **Pages/MyGateway/PostCheckout.cshtml.cs**. ```html + @page "/MyGateway/PostCheckout" @model PostCheckoutModel

Operation Done

``` + ```csharp + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + using Volo.Payment.Requests; + + [IgnoreAntiforgeryToken] + public class PostCheckoutModel : AbpPageModel + { + private readonly IPaymentRequestAppService _paymentRequestAppService; + + public PostCheckoutModel( + IPaymentRequestAppService paymentRequestAppService) + { + _paymentRequestAppService = paymentRequestAppService; + } + + public virtual async Task OnGetAsync() + { + var parameters = Request.Query.ToDictionary( + item => item.Key, + item => item.Value.ToString()); + + return await CompleteAsync(parameters); + } + + public virtual async Task OnPostAsync() + { + var form = await Request.ReadFormAsync(); + var parameters = form.ToDictionary( + item => item.Key, + item => item.Value.ToString()); + + return await CompleteAsync(parameters); + } + + private async Task CompleteAsync( + Dictionary parameters) + { + var paymentRequest = await _paymentRequestAppService.CompleteAsync( + "MyGateway", + parameters); + + return paymentRequest.State == PaymentRequestState.Completed + ? Page() + : BadRequest("The payment was not completed."); + } + } + ``` + + Keep only the callback method used by your provider. If the provider sends an external POST request to the Razor Page, keep `[IgnoreAntiforgeryToken]` and rely on the gateway's provider-signature validation instead of an antiforgery token. Redirect to a success page only after `CompleteAsync` returns the `Completed` state. + - Configure your pages using `PaymentWebOptions` in **Web** layer of your project. ```csharp diff --git a/docs/en/modules/payment.md b/docs/en/modules/payment.md index 487e0dc64d..57425d31bd 100644 --- a/docs/en/modules/payment.md +++ b/docs/en/modules/payment.md @@ -11,7 +11,8 @@ Payment module implements payment gateway integration of an application. It provides one time payment and recurring payment options. -* Supports [Stripe](https://stripe.com/), [PayPal](https://www.paypal.com/), [2Checkout](https://www.2checkout.com/), [PayU](https://corporate.payu.com/), [Iyzico](https://www.iyzico.com/en) and [Alipay](https://global.alipay.com/) payment gateways. +* Supports [Stripe](https://stripe.com/), [PayPal](https://www.paypal.com/), [2Checkout](https://www.2checkout.com/), [PayU](https://corporate.payu.com/), [Iyzico](https://www.iyzico.com/en), and [Alipay](https://global.alipay.com/) payment gateways. +* All listed gateways support one-time payments. Stripe also supports subscriptions. See [the module description page](https://abp.io/modules/Volo.Payment) for an overview of the module features. @@ -34,7 +35,7 @@ If you modified your solution structure, adding a module using ABP CLI might not In order to do that, add packages listed below to matching project on your solution. For example, ```Volo.Payment.Application``` package to your **{ProjectName}.Application.csproj** like below; -```json +```xml ``` @@ -75,35 +76,33 @@ The Payment module provides both **public pages** (for payment processing) and * ### MVC / Razor Pages UI -For MVC/Razor Pages applications, the `abp add-module Volo.Payment` command automatically adds the required packages (`Volo.Payment.Web` and gateway-specific Web packages) and the necessary `DependsOn` statements to your module. The only thing you need to do is configure `PaymentWebOptions` as explained in the [PaymentWebOptions](#paymentweboptions) section. +For MVC/Razor Pages applications, add `Volo.Payment.Web` and the Web package for each gateway you want to use, then add the corresponding module dependencies. Configure `PaymentWebOptions` as explained in the [PaymentWebOptions](#paymentweboptions) section. ### Blazor UI -For Blazor applications, the `abp add-module Volo.Payment` command automatically adds the required packages (`Volo.Payment.Blazor.Server` or `Volo.Payment.Blazor.WebAssembly` and gateway-specific Blazor packages) and the necessary `DependsOn` statements to your module. The only thing you need to do is configure `PaymentBlazorOptions` as explained below. +For Blazor applications, add the core Blazor package and the packages for the gateways you want to use, then add the corresponding module dependencies. Configure `PaymentBlazorOptions` as explained below. #### Installation -> **Note:** If you used the `abp add-module Volo.Payment` command to install the Payment module, the following packages and module dependencies are automatically added to your project. You can skip to the [Gateway-Specific Blazor Packages](#gateway-specific-blazor-packages) section. The information below is provided for reference or manual installation scenarios. - To use the Payment module's public pages in a Blazor application, you need to install the core Blazor packages and the gateway-specific Blazor packages for each payment gateway you want to support. ##### Core Blazor Packages For **Blazor Server** applications, add the following package to your **{ProjectName}.Blazor.Server.csproj** (or **{ProjectName}.Blazor.csproj** for Blazor Web App): -```json +```xml ``` For **Blazor WebAssembly** applications, add the following package to your **{ProjectName}.Blazor.csproj** (or **{ProjectName}.Blazor.Client.csproj** for Blazor Web App): -```json +```xml ``` -#### Gateway-Specific Blazor Packages +##### Gateway-Specific Blazor Packages -Each payment gateway requires its own Blazor package. Add the packages for the gateways you want to support: +Each supported Blazor gateway integration requires its own package. Add the packages for the integrations you want to use: **Stripe:** - Blazor Server: `Volo.Payment.Stripe.Blazor.Server` @@ -114,8 +113,8 @@ Each payment gateway requires its own Blazor package. Add the packages for the g - Blazor WebAssembly: `Volo.Payment.PayPal.Blazor.WebAssembly` **PayU:** -- Blazor Server: `Volo.Payment.PayU.Blazor.Server` -- Blazor WebAssembly: `Volo.Payment.PayU.Blazor.WebAssembly` +- Blazor Server: `Volo.Payment.Payu.Blazor.Server` +- Blazor WebAssembly: `Volo.Payment.Payu.Blazor.WebAssembly` **Iyzico:** - Blazor Server: `Volo.Payment.Iyzico.Blazor.Server` @@ -177,55 +176,32 @@ Configure(options => }); ``` -You can also configure these options in your `appsettings.json` file: +##### Gateway-Specific Blazor Options -```json -{ - "Payment": { - "Blazor": { - "RootUrl": "https://localhost:44300", - "CallbackUrl": "https://localhost:44300/PaymentSucceed", - "GatewaySelectionCheckoutButtonStyle": "btn btn-primary" - } - } -} -``` +Each supported Blazor gateway integration has its own options for customizing the UI. The gateway modules bind them from the following configuration sections: -##### Gateway-Specific Blazor Options +| Gateway | Blazor configuration section | +| --- | --- | +| PayU | `Payment:PayuBlazor` | +| PayPal | `Payment:PayPalBlazor` | +| Iyzico | `Payment:IyzicoBlazor` | +| Alipay | `Payment:AlipayBlazor` | +| Stripe | `Payment:Stripe` | +| TwoCheckout | `Payment:TwoCheckout` | -Each payment gateway has its own Blazor options for customizing the UI. These options can be configured in `appsettings.json`: +For example: ```json { "Payment": { - "Blazor": { - "Payu": { - "PrePaymentCheckoutButtonStyle": "btn btn-success", - "Recommended": true, - "ExtraInfos": ["Fast checkout", "Secure payment"] - }, - "TwoCheckout": { - "Recommended": false, - "ExtraInfos": ["International payments"] - }, - "PayPal": { - "Recommended": true, - "ExtraInfos": ["Pay with PayPal balance", "Buyer protection"] - }, - "Stripe": { - "Recommended": true, - "ExtraInfos": ["Credit/Debit cards", "Apple Pay", "Google Pay"] - }, - "Iyzico": { - "PrePaymentCheckoutButtonStyle": "btn btn-primary", - "Recommended": false, - "ExtraInfos": ["Turkish payment gateway"] - }, - "Alipay": { - "PrePaymentCheckoutButtonStyle": "btn btn-info", - "Recommended": false, - "ExtraInfos": ["Chinese payment gateway", "CNY only"] - } + "PayuBlazor": { + "PrePaymentCheckoutButtonStyle": "btn btn-success", + "Recommended": true, + "ExtraInfos": ["Fast checkout", "Secure payment"] + }, + "Stripe": { + "Recommended": true, + "ExtraInfos": ["Credit/Debit cards", "Apple Pay", "Google Pay"] } } } @@ -292,7 +268,7 @@ var paymentRequest = await PaymentRequestAppService.CreateAsync( TotalPrice = 60 } }, - ExtraProperties = new ExtraPropertyDictionary + ExtraProperties = { // For Iyzico - Customer information { "Name", "John" }, @@ -302,7 +278,7 @@ var paymentRequest = await PaymentRequestAppService.CreateAsync( { "City", "Istanbul" }, { "Country", "Turkey" }, { "ZipCode", "34000" }, - + // For PayU - Customer information { "BuyerName", "John" }, { "BuyerSurname", "Doe" }, @@ -313,41 +289,51 @@ var paymentRequest = await PaymentRequestAppService.CreateAsync( #### Handling the Callback (Optional) -When a user completes a payment on the external payment gateway, the following flow occurs: +When a payment provider redirects the user back to the application, the following flow occurs: 1. The user is redirected to the **PostPayment page** (handled internally by the payment module) -2. The PostPayment page validates the payment with the gateway and updates the payment request status to **Completed** +2. The PostPayment page validates the provider response and updates the payment request state 3. If a `CallbackUrl` is configured in `PaymentBlazorOptions`, the user is then redirected to that URL with the `paymentRequestId` as a query parameter -Create a page to handle this callback and perform any application-specific actions: +The callback URL is a browser navigation target, not proof that the payment succeeded. Query the payment request and check its state before showing a result: ```csharp @page "/PaymentSucceed" -@using Microsoft.AspNetCore.WebUtilities +@using Microsoft.AspNetCore.Components +@using Volo.Payment.Requests +@inject IPaymentRequestAppService PaymentRequestAppService -

Payment Successful!

-

Thank you for your purchase.

-

Payment Request ID: @PaymentRequestId

+@if (PaymentRequest is null) +{ +

Payment request not found.

+} +else if (PaymentRequest.State == PaymentRequestState.Completed) +{ +

Payment Successful!

+

Thank you for your purchase.

+} +else +{ +

The payment was not completed.

+} @code { [Parameter] [SupplyParameterFromQuery] public Guid? PaymentRequestId { get; set; } - - protected override async Task OnInitializedAsync() + + private PaymentRequestWithDetailsDto PaymentRequest { get; set; } + + protected override async Task OnParametersSetAsync() { - if (PaymentRequestId.HasValue) - { - // The payment is already completed at this point. - // Perform application-specific actions here: - // e.g., activate subscription, send confirmation email, - // update order status, grant access to purchased content, etc. - } + PaymentRequest = PaymentRequestId.HasValue + ? await PaymentRequestAppService.GetAsync(PaymentRequestId.Value) + : null; } } ``` -> **Note:** By the time the user reaches your callback page, the payment request status has already been set to **Completed** by the PostPayment page. Your callback page is for performing additional application-specific logic. It is also your responsibility to handle if a payment request is used more than once. If you have already delivered your product for a given `PaymentRequestId`, you should not deliver it again when the callback URL is visited a second time. +Keep the callback page limited to displaying the current result because users can revisit a callback URL. `PaymentRequestCompletedEto` is published when `IPaymentRequestAppService.CompleteAsync` completes a request, so an idempotent handler can fulfill orders for flows that finish through that application service. It isn't emitted for every direct request-state update; for example, the built-in Stripe webhook can complete a request without publishing this event. If your gateway can complete payments only through a webhook, add an application-owned, idempotent fulfillment or reconciliation path for that trusted webhook flow and verify the persisted payment-request state before granting access. ### Angular UI @@ -355,38 +341,61 @@ For Angular applications, you need to read and apply the steps explained in the #### Configurations -In order to configure the application to use the payment module, you first need to import `PaymentAdminConfigModule` from `@volo/abp.ng.payment/admin/config` to the root configuration. `PaymentAdminConfigModule` has a static `forRoot` method which you should call for a proper configuration: +Add `providePaymentAdminConfig` from `@volo/abp.ng.payment/admin/config` to the root application configuration. The same example shows the optional remote endpoint entries when the public and admin APIs are hosted separately: -```js +```typescript // app.config.ts -import { ApplicationConfig, importProvidersFrom } from '@angular/core'; -import { PaymentAdminConfigModule } from '@volo/abp.ng.payment/admin/config'; +import { ApplicationConfig } from '@angular/core'; +import { providePaymentAdminConfig } from '@volo/abp.ng.payment/admin/config'; export const appConfig: ApplicationConfig = { providers: [ // ... - importProvidersFrom([ - PaymentAdminConfigModule.forRoot() - ]), + providePaymentAdminConfig(), ], }; +// environment.ts +export const environment = { + apis: { + default: { + url: 'https://localhost:44300', + }, + AbpPaymentCommon: { + url: 'https://localhost:44301', + }, + AbpPaymentAdmin: { + url: 'https://localhost:44302', + }, + }, +}; ``` -The payment admin module should be imported and lazy-loaded in your routing array as below: +`AbpPaymentCommon` is used by the payment-request proxies and is the server-side remote service name for the gateway endpoints. The currently published Angular gateway proxy, which is used by the public gateway-selection component, sends gateway requests through `AbpPaymentAdmin`; administration proxies also use `AbpPaymentAdmin`. When the APIs are hosted separately, configure both entries and expose `/api/payment/gateways` through the `AbpPaymentAdmin` URL until the client and server remote service names are aligned. Each missing entry independently falls back to `default.url`. -```js +Lazy-load both the admin and public payment routes under the `payment` path: + +```typescript // app.routes.ts +import { Routes } from '@angular/router'; + const APP_ROUTES: Routes = [ // ... { - path: 'payment', - loadChildren: () => - import('@volo/abp.ng.payment/admin').then(c => c.createRoutes()), + path: 'payment', + loadChildren: () => + Promise.all([ + import('@volo/abp.ng.payment/admin').then(c => c.createRoutes()), + import('@volo/abp.ng.payment').then(c => c.createRoutes()), + ]).then(([adminRoutes, publicRoutes]) => [...adminRoutes, ...publicRoutes]), }, ]; ``` +The public route factory adds `gateway-selection`, `:gateway/prepayment`, and `:gateway/post-payment`. The admin route factory adds plans, gateway plans, requests, and payment request products. + +The public package exposes replaceable component keys through `ePaymentComponents`. Use `registerPrePaymentComponent` or `registerPostPaymentComponent` with `ReplaceableComponentsService` to replace a gateway-specific payment page. The admin `createRoutes` method accepts `PaymentConfigOptions` for entity action, toolbar action, entity property, create form, and edit form contributors on the plans and gateway plans pages. + ### Pages #### Public Pages @@ -425,24 +434,28 @@ This page lists all the payment request operations in the application. `PaymentOptions` is used to store list of payment gateways. You don't have to configure this manually for existing payment gateways. You can, however, add a new gateway like below; +Add `using Volo.Abp.Localization;` to the module class file for `FixedLocalizableString`. + ````csharp Configure(options => { - options.Gateways.Add( - new PaymentGatewayConfiguration( - "MyPaymentGatewayName", - new FixedLocalizableString("MyPaymentGatewayName"), - typeof(MyPaymentGateway) - ) - ); + options.Gateways.Add( + new PaymentGatewayConfiguration( + "MyPaymentGatewayName", + new FixedLocalizableString("MyPaymentGatewayName"), + isSubscriptionSupported: false, + typeof(MyPaymentGateway) + ) + ); }); ```` -`AbpIdentityAspNetCoreOptions` properties: +`PaymentOptions` properties: -* `PaymentGatewayConfigurationDictionary`: List of gateway configuration. +* `Gateways`: Dictionary of gateway configurations keyed by gateway name. * ```Name```: Name of payment gateway. * ```DisplayName```: DisplayName of payment gateway. + * ```IsSubscriptionSupported```: Whether the gateway can process recurring payment products. * ```PaymentGatewayType```: type of payment gateway. * ```Order```: Order of payment gateway. @@ -450,10 +463,10 @@ Configure(options => ```PaymentWebOptions``` is used to configure web application related configurations. -* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. User will be redirected to this URL on your website. +* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. A redirect to this URL is not proof of a successful payment. Query the payment request and check its state before displaying the result. * ```RootUrl```: Root URL of your website. * ```GatewaySelectionCheckoutButtonStyle```: CSS style to add Checkout button on gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. -* ```PaymentGatewayWebConfigurationDictionary```: Used to store web related payment gateway configuration. +* ```Gateways```: Used to store web related payment gateway configurations. * ```Name```: Name of payment gateway. * ```PrePaymentUrl```: URL of the page before redirecting user to payment gateway for payment. * ```PostPaymentUrl```: URL of the page when user redirected back from payment gateway to your website. This page is used to validate the payment mostly. @@ -465,10 +478,10 @@ Configure(options => ```PaymentBlazorOptions``` is used to configure Blazor application related configurations. This is the Blazor equivalent of `PaymentWebOptions`. -* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. User will be redirected to this URL on your website after a successful payment. +* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. A redirect to this URL is not proof of a successful payment. Query the payment request and check its state before displaying the result. * ```RootUrl```: Root URL of your Blazor application. * ```GatewaySelectionCheckoutButtonStyle```: CSS style to add to the Checkout button on the gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. -* ```PaymentGatewayBlazorConfigurationDictionary```: Used to store Blazor related payment gateway configuration. +* ```Gateways```: Used to store Blazor related payment gateway configurations. * ```Name```: Name of payment gateway. * ```PrePaymentUrl```: URL of the Blazor page before redirecting user to payment gateway for payment. * ```PostPaymentUrl```: URL of the Blazor page when user is redirected back from payment gateway to your website. @@ -480,10 +493,10 @@ Configure(options => ```PayuOptions``` is used to configure PayU payment gateway options. +* ```CheckoutLink```: PayU checkout URL. Its default value is `https://secure.payu.ro/order/lu.php`. * ```Merchant```: Merchant code for PayU account. * ```Signature```: Signature of Merchant. * ```LanguageCode```: Language of the order. This will be used for notification email that are sent to the client, if available. -* ```CurrencyCode```: Currency code of order (USD, EUR, etc...). * ```VatRate```: Vat rate of order. * ```PriceType```: Price type of order (GROSS or NET). * ```Shipping```: A positive number indicating the price of shipping. @@ -514,7 +527,6 @@ Configure(options => * ```Signature```: Signature of Merchant's 2Checkout account. * ```CheckoutUrl```: 2Checkout checkout URL (it must be set to https://secure.2checkout.com/order/checkout.php). * ```LanguageCode```: Language of the order. This will be used for notification email that are sent to the client, if available. -* ```CurrencyCode```: Currency code of order (USD, EUR, etc...). ### TwoCheckoutWebOptions @@ -560,8 +572,7 @@ Configure(options => ```PayPalOptions``` is used to configure PayPal payment gateway options. * ```ClientId```: Client Id for the PayPal account. -* ```Secret``` Secret for the PayPal account. -* ```CurrencyCode```: Currency code of order (USD, EUR, etc...). +* ```Secret```: Secret for the PayPal account. * ```Environment```: Payment environment. ("Sandbox" or "Live", default value is "Sandbox") * ```Locale```: PayPal-supported language and locale to localize PayPal checkout pages. See https://developer.paypal.com/docs/api/reference/locale-codes/. @@ -583,12 +594,11 @@ Configure(options => ```IyzicoOptions``` is used to configure Iyzico payment gateway options. -* ```BaseUrl```: Base API URL for the Iyzico (ex: https://sandbox-api.iyzipay.com). -* ```ApiKey``` Api key for the Iyzico account. -* ```SecretKey ``` Secret for the Iyzico account. +* ```BaseUrl```: Base API URL for the Iyzico (ex: https://sandbox-api.iyzipay.com). +* ```ApiKey```: API key for the Iyzico account. +* ```SecretKey```: Secret for the Iyzico account. * ```Currency```: Currency code for the order (USD, EUR, GBP and TRY can be used). * ```Locale```: Language of the order. -* ```InstallmentCount```: Installment count value. For single installment payments it should be 1 (valid values: 1, 2, 3, 6, 9, 12). ### IyzicoWebOptions @@ -608,27 +618,27 @@ Configure(options => ### AlipayOptions -```AlipayOptions``` is used to configure Alipay payment gateway options。 **Alipay gateway only supports CNY currency**. +```AlipayOptions``` is used to configure Alipay payment gateway options. **Alipay gateway only supports CNY currency**. -* ```Protocol```:Protocol for the Alipay (ex: https). -* ```GatewayHost```: Gateway host for the Aliapy. +* ```Protocol```: Protocol for Alipay (for example, https). +* ```GatewayHost```: Gateway host for Alipay. * ```SignType```: Sign type for the Alipay. * ```AppId```: AppId for the Alipay account. * ```MerchantPrivateKey```: Merchant private key of the Alipay account. -* ```MerchantCertPath```Merchant cert path of the Alipay account. +* ```MerchantCertPath```: Merchant certificate path of the Alipay account. * ```AlipayCertPath```: Alipay cert path of the Alipay account. * ```AlipayRootCertPath```: Alipay root cert path of the Alipay account. * ```AlipayPublicKey```: Alipay public key of the Alipay account. * ```NotifyUrl```: Notify url of the Alipay. * ```EncryptKey```: Encrypt key of the Alipay. -#### AlipayWebOptions +### AlipayWebOptions * ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. * ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. * ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Alipay prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. -#### AlipayBlazorOptions +### AlipayBlazorOptions ```AlipayBlazorOptions``` is used to configure Alipay payment gateway Blazor options. @@ -638,18 +648,20 @@ Configure(options => > You can check the [Alipay document](https://opendocs.alipay.com/open/02np97) for more details. +Set the currency for a one-time payment with `PaymentRequestCreateDto.Currency`. The legacy `CurrencyCode` settings on PayU, PayPal, and TwoCheckout options are obsolete fallbacks. In the current TwoCheckout integration, you must still configure `CurrencyCode` because it is also used when the product price parameters are generated. + Instead of configuring options in your module class, you can configure it in your appsettings.json file like below; ```json -"Payment": { +{ + "Payment": { "Payu": { "Merchant": "TEST", "Signature": "SECRET_KEY", "LanguageCode": "en", - "CurrencyCode": "USD", - "VatRate": "0", + "VatRate": 0, "PriceType": "GROSS", - "Shipping": "0", + "Shipping": 0, "Installment": "1", "TestOrder": "1", "Debug": "1" @@ -659,12 +671,11 @@ Instead of configuring options in your module class, you can configure it in you "CheckoutUrl": "https://secure.2checkout.com/order/checkout.php", "LanguageCode": "en", "CurrencyCode": "USD", - "TestOrder": "1" + "TestOrder": true }, "PayPal": { "ClientId": "CLIENT_ID", "Secret": "SECRET", - "CurrencyCode": "USD", "Environment": "Sandbox", "Locale": "en_US" }, @@ -687,8 +698,11 @@ Instead of configuring options in your module class, you can configure it in you "MerchantPrivateKey": "MERCHANT_PRIVATE_KEY" } } +} ``` +The domain and MVC gateway modules share `Payment:Payu`, `Payment:PayPal`, `Payment:Iyzico`, `Payment:Stripe`, and `Payment:TwoCheckout`. Alipay MVC options use `Payment:AlipayWeb`. + ## Internals ### Domain layer @@ -708,17 +722,19 @@ A payment request represents a request for a payment in the application. * `Gateway` : Name of payment gateway used for this payment request. * ```FailReason```: Reason for failed payment requests. +`Complete()` moves a waiting or failed request to `Completed` and is idempotent for an already completed request. `Failed()` accepts waiting or failed requests, while `Refunded()` accepts only completed requests. + ##### Plan -A plan is used for subscription payments. Contains PlanGateway list to configure each gateway. +A plan is used for subscription payments. It contains a `GatewayPlans` collection for gateway-specific configurations. - `Plan` (aggregate root): Represents a plan for recurring payments. - - `PlanGateways` (collection): List of gateway plans. - - `Name` : An optional name of plan. + - `GatewayPlans` (collection): List of gateway plans. + - `Name`: Required name of the plan. - `GatewayPlan` (entity): Represents a gateway configuration for a plan. - `PlanId`: Represents a plan belong to. - `Gateway`: Represents a gateway belong to. It has to be unique. - - `ExternalId`: Stores a unique configuration of gateway for subscrtiption, such as priceId, planId, subscriptionId or productId etc. + - `ExternalId`: Stores the gateway's external subscription configuration, such as a price ID, plan ID, subscription ID, or product ID. #### Repositories @@ -747,7 +763,7 @@ All tables/collections use the `Pay` prefix by default. Set static properties on ##### Connection string -This module uses `AbpPayment` 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 `Payment` 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. @@ -756,8 +772,7 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do ##### Tables * **PayPaymentRequests** - * **AbpRoleClaims** - * PayPaymentRequestProducts +* **PayPaymentRequestProducts** * **PayPlans** * **PayGatewayPlans** @@ -832,7 +847,7 @@ public static void ConfigureExtraProperties() ## Distributed Events -- `Volo.Payment.PaymentRequestCompleted` (**PaymentRequestCompletedEto**): Published when a payment is completed. +- `Volo.Payment.PaymentRequestCompleted` (**PaymentRequestCompletedEto**): Published by `IPaymentRequestAppService.CompleteAsync` when the resolved gateway returns a completed payment request. It isn't a notification for every direct payment-request state update; for example, the built-in Stripe webhook can complete a request without publishing this event. - `Id`: Represents PaymentRequest entity Id. - `Gateway`: Represents the gateway which payment was done with. @@ -874,7 +889,7 @@ public static void ConfigureExtraProperties() This module implements one-time payments; -* Supports [Stripe](https://stripe.com/), [PayPal](https://www.paypal.com/), [2Checkout](https://www.2checkout.com/), [PayU](https://corporate.payu.com/) and [Iyzico](https://www.iyzico.com/en) payment gateways. +* Supports [Stripe](https://stripe.com/), [PayPal](https://www.paypal.com/), [2Checkout](https://www.2checkout.com/), [PayU](https://corporate.payu.com/), [Iyzico](https://www.iyzico.com/en), and [Alipay](https://global.alipay.com/) payment gateways. You can get one-time payments from your customers using one or more payment gateways supported by the payment module. Payment module works in a very simple way for one-time payments. It creates a local payment request record and redirects customer to payment gateway (PayPal, Stripe etc...) for processing the payment. When the customer pays on the payment gateway, payment module handles the external payment gateway's response and validates the payment to see if it is really paid or not. If the payment is validated, payment module redirects customer to main application which initiated the payment process at the beginning. @@ -886,18 +901,18 @@ Each payment gateway implementation contains PrePayment and PostPayment pages. PrePayment page asks users for extra information if requested by the external payment gateway. For example, 2Checkout doesn't require any extra information, so PrePayment page for 2Checkout redirects user to 2Checkout without asking any extra information. -PostPayment page is responsible for validation of the response of the external payment gateway. When a user completes the payment, user is redirected to PostPayment page for that payment gateway and PostPayment page validates the status of the payment. If the payment is succeeded, status of the payment request is updated and user is redirected to main application. +PostPayment page is responsible for validating the response of the external payment gateway. When a user returns from the gateway, the PostPayment page validates the payment and updates the payment request state. A subsequent redirect to the main application is a browser navigation target, not proof that the payment succeeded. -Note: It is the main application's responsibility to handle if a payment request is used more than once. For example, if the PostPayment page generates a URL like https://mywebsite.com/PaymentSucceed?PaymentRequestId={PaymentRequestId}, this URL can be visited more than once manually by end users. If you have already delivered your product for a given PaymentRequestId, you shouldn't deliver it when this URL is visited a second time. +Query the payment request and check its state before displaying the result. Do not perform fulfillment from the callback page because users can revisit or forge callback URLs. For flows that finish through `IPaymentRequestAppService.CompleteAsync`, use an idempotent `PaymentRequestCompletedEto` handler. For webhook-only completion, use an application-owned, idempotent fulfillment or reconciliation path that runs after the trusted webhook is processed and verifies the persisted request state. ### Creating One-Time Payment -In order to initiate a payment process, inject `IPaymentRequestAppService`, create a payment request using it's `CreateAsync` method and redirect user to gateway selection page with the created payment request's Id. Here is a sample Razor Page code which starts a payment process on it's OnPost method. +In order to initiate a payment process, inject `IPaymentRequestAppService`, create a payment request using its `CreateAsync` method and redirect user to gateway selection page with the created payment request's Id. Here is a sample Razor Page code which starts a payment process on its OnPost method. > Redirection of the gateway selection page has to be a **POST** request. If you implement it as a **GET** request, you will get an error. You can use `LocalRedirectPreserveMethod` to keep the method as POST in the redirected request. -```c# -public class IndexModel: PageModel +```csharp +public class IndexModel : PageModel { private readonly IPaymentRequestAppService _paymentRequestAppService; @@ -919,7 +934,7 @@ public class IndexModel: PageModel Name = "LEGO Super Mario", Count = 2, UnitPrice = 60, - TotalPrice = 200 + TotalPrice = 120 } } }); @@ -929,7 +944,9 @@ public class IndexModel: PageModel } ``` -If the payment is successful, payment module will return to the configured ```PaymentWebOptions.CallbackUrl```. The main application can take necessary actions for a successful payment (activating a user account, triggering a shipment start process, etc.). +`TotalPrice` is optional. When it is omitted, the module calculates it as `UnitPrice * Count`. + +The Payment module can redirect the browser to the configured `PaymentWebOptions.CallbackUrl` after processing the gateway response. Treat this URL only as a result page: query the payment request and check its state before displaying the result. `PaymentRequestCompletedEto` can drive idempotent fulfillment for flows completed through `IPaymentRequestAppService.CompleteAsync`; cover webhook-only completion with a trusted, application-owned reconciliation or fulfillment path as described above. ## Subscriptions @@ -1009,4 +1026,4 @@ public class SubscriptionModel : PageModel } ``` -> To track that subscription is continuing or canceled, you should keep the SubscriptionId, all events contain it. +> To track whether the subscription continues or is canceled, store its `ExternalSubscriptionId`. All subscription lifecycle events contain this value. From 021d67204edff7549d8fa7b1e73a95727631f100 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 12:11:39 +0800 Subject: [PATCH 30/41] Update Blogging and Identity module navigation --- docs/en/docs-nav.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index b205037c74..95e010c011 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -2647,6 +2647,10 @@ "text": "Background Jobs", "path": "modules/background-jobs.md" }, + { + "text": "Blogging", + "path": "modules/blogging.md" + }, { "text": "Chat (Pro)", "path": "modules/chat.md" @@ -2765,7 +2769,19 @@ }, { "text": "Identity", - "path": "modules/identity.md" + "isLazyExpandable": true, + "path": "modules/identity.md", + "items": [ + { + "text": "Overview", + "path": "modules/identity.md", + "isIndex": true + }, + { + "text": "User Lookup and Synchronization", + "path": "modules/identity/user-synchronization.md" + } + ] }, { "text": "Identity (Pro)", From 850b75e1174c90fefa77bf62343d9f792cde92e3 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 19 Jul 2026 13:36:22 +0800 Subject: [PATCH 31/41] Expand CMS Kit Pro documentation --- docs/en/docs-nav.json | 2 +- docs/en/modules/cms-kit-pro/contact-form.md | 27 +++-- docs/en/modules/cms-kit-pro/faq.md | 54 ++++----- docs/en/modules/cms-kit-pro/index.md | 111 +++++++++++------- docs/en/modules/cms-kit-pro/newsletter.md | 89 ++++++++++---- docs/en/modules/cms-kit-pro/page-feedback.md | 69 ++++++----- docs/en/modules/cms-kit-pro/poll.md | 41 +++++-- docs/en/modules/cms-kit-pro/url-forwarding.md | 67 ++++++++--- 8 files changed, 291 insertions(+), 169 deletions(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 95e010c011..d48bd85612 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -2727,7 +2727,7 @@ }, { "text": "URL Forwarding System", - "path": "modules/cms-kit-pro/URL-forwarding.md" + "path": "modules/cms-kit-pro/url-forwarding.md" }, { "text": "Poll System", diff --git a/docs/en/modules/cms-kit-pro/contact-form.md b/docs/en/modules/cms-kit-pro/contact-form.md index eafa4b80b3..005e85073c 100644 --- a/docs/en/modules/cms-kit-pro/contact-form.md +++ b/docs/en/modules/cms-kit-pro/contact-form.md @@ -13,25 +13,25 @@ CMS Kit provides a widget to create a contact form on your website. ## Enabling the Contact Management System -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime. +By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime. -> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. +> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time. ## Contact Widget The contact management system provides a contact form [widget](../../framework/ui/mvc-razor-pages/widgets.md) to create contact forms on the UI: ```csharp -@await Component.InvokeAsync(typeof(ContactViewComponent)) +@await Component.InvokeAsync(typeof(ContactViewComponent), new { }) ``` -Here, a screenshot from the widget: +Here is a screenshot of the widget: ![contact-form](../../images/cmskit-module-contact-form.png) ## Multiple Contact Widgets -The contact management system allows you to create multiple contact forms. You can define a named contact widget as below: +The contact management system allows you to create multiple contact forms with different receivers. You can define a named contact widget as shown below: ```csharp @await Component.InvokeAsync(typeof(ContactViewComponent), new @@ -40,7 +40,7 @@ The contact management system allows you to create multiple contact forms. You c }); ``` -Then, you need to configure the defined contact widgets in the `ConfigureServices` method of your module class: +Then, configure the receiver for each name in the `ConfigureServices` method of your module class: ```csharp Configure(options => @@ -50,29 +50,30 @@ Configure(options => }); ``` -Here, is a screenshot that shows multiple contact forms on a page: +The following screenshot shows multiple contact forms on a page: ![multiple-contact-forms](../../images/cmskit-module-multiple-contact-forms.png) +When the submitted `contactName` matches a configured entry, that entry's receiver is used. Otherwise, the module uses the receiver email address configured on the CMS settings page. The contact name is also prefixed to the email subject when it is not empty. ## Options -You can configure the `CmsKitContactOptions` to enable/disable recaptcha for contact form in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md). +You can configure `CmsKitContactOptions` to enable or disable reCAPTCHA for the contact form in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md). Example: ```csharp Configure(options => { - options.IsRecaptchaEnabled = true; //false by default + options.IsRecaptchaEnabled = true; }); ``` `CmsKitContactOptions` properties: -* `IsRecaptchaEnabled` (default: false): This flag enables or disables the reCaptcha for the contact form. You can set it as **true** if you want to use reCaptcha in your contact form. +* `IsRecaptchaEnabled` (default: `false`): Enables reCAPTCHA v3 validation for public contact submissions. -If you set **IsRecaptchaEnabled** as **true**, you also need to specify **SiteKey** and **SiteSecret** options for reCaptcha. To do that, add **CmsKit:Contact** section into your `appsettings.json` file: +If you set `IsRecaptchaEnabled` to `true`, also specify `SiteKey` and `SiteSecret` for reCAPTCHA. Add the `CmsKit:Contact` section to your `appsettings.json` file: ```json { @@ -85,9 +86,9 @@ If you set **IsRecaptchaEnabled** as **true**, you also need to specify **SiteKe } ``` -## Settings +## Settings -You can configure the receiver (email address) by using the CMS tab in the settings page. +You can configure the fallback receiver email address on the CMS tab of the settings page. This setting is tenant-aware and is used when the form has no matching named receiver. Its default value is `info@mycompanyname.com`; replace it with an address that belongs to your application before deploying to production. ![contact-settings](../../images/cmskit-module-contact-settings.png) diff --git a/docs/en/modules/cms-kit-pro/faq.md b/docs/en/modules/cms-kit-pro/faq.md index 23d90d9705..230b9578cc 100644 --- a/docs/en/modules/cms-kit-pro/faq.md +++ b/docs/en/modules/cms-kit-pro/faq.md @@ -9,15 +9,15 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use CMS Kit Pro module's features. -The CMS kit provides a **FAQ** system to allow users to create, edit and delete FAQ's. Here is a screenshot of the FAQ widget: +CMS Kit Pro provides an **FAQ** system to organize questions into groups and sections and display them on public pages. Here is a screenshot of the FAQ widget: ![cmskit-module-faq-widget](../../images/cmskit-module-faq-widget.png) ## Enabling the FAQ System -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime. +By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime. -> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. +> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time. ## User Interface @@ -25,21 +25,21 @@ By default, CMS Kit features are disabled. Therefore, you need to enable the fea CMS Kit module admin side adds the following items to the main menu, under the **CMS** menu item: -**FAQ's**: FAQ management page. +**FAQs**: FAQ group, section and question management page. `CmsKitProAdminMenus` class has the constants for the menu item names. ### Pages -You can list, create, update and delete sections and their questions FAQ's on the admin side of your solution. +You can list, create, update and delete FAQ groups, sections and questions on the admin side of your solution. A group contains sections, and a section contains questions. ![faq-page](../../images/cmskit-module-faq-page.png) ![faq-edit-page](../../images/cmskit-module-faq-edit-page.png) ![faq-edit-question-page](../../images/cmskit-module-faq-edit-question-page.png) -## Faq Widget +## FAQ Widget -The FAQ system provides a FAQ [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to display FAQ's. You can place the widget on a page like below: +The FAQ system provides an FAQ [widget](../../framework/ui/mvc-razor-pages/widgets.md) for displaying FAQs. You can place the widget on a page as shown below: ```csharp @await Component.InvokeAsync( @@ -47,30 +47,20 @@ The FAQ system provides a FAQ [widget](../../framework/ui/mvc-razor-pages/widget new { groupName = "Community", - name = "Development" + sectionName = "Development" }) ``` `FaqViewComponent` parameters: -- `groupName` (optional): It allows to specify which FAQ group to show. If not specified, all groups will be shown. -- `sectionName` (optional): It is used to determine which section within the specified group will be shown. If not specified, all sections in the related group will be shown. -The FAQ system can also be used in combination with the [dynamic widget](../cms-kit/dynamic-widget.md) feature. - -## Options - -The FAQ system provides a mechanism to group sections by group name. For example, if you want to use the FAQ system for community and support page, you need to define two group names named Community and Support and add sections under these groups. So, before using the FAQ system, you need to define groups. For that, you can use `FaqOptions`. `FaqOptions` can be configured at the domain layer, in the `ConfigureServices` method of your [module]../../framework/architecture/modularity/basics.md). +- `groupName` (required): Specifies the FAQ group to show. Create this group on the FAQ administration page before rendering the widget. +- `sectionName` (optional): Specifies a section within the selected group. If it is not set, all sections in the group are shown. -```csharp -Configure(options => -{ - options.SetGroups(new[] { "General", "Community", "Support" }); -}); -``` +The FAQ system can also be used in combination with the [dynamic widget](../cms-kit/dynamic-widget.md) feature. -`FaqOptions` properties: +## FAQ Groups -- `Groups`: Dictionary of defined groups in the FAQ system. The `options.SetGroups` method is a shortcut to add a new groups to this dictionary. +FAQ groups are persisted data and are managed from the FAQ administration page. Create groups such as `Community` or `Support`, then assign each section to one of those groups. Group names must be unique. ## Internals @@ -82,10 +72,11 @@ This module follows the [Entity Best Practices & Conventions](../../framework/ar ##### FAQ -A FAQ represents a generated FAQ with its questions: +An FAQ represents a generated FAQ with its questions: - `FaqSection` (aggregate root): Represents the defined FAQ sections related to the FAQ in the system. - `FaqQuestion` (aggregate root): Represents the defined FAQ questions with section identifier related to the FAQ in the system. +- `FaqGroup` (aggregate root): Represents a named group that contains FAQ sections. #### Repositories @@ -95,6 +86,7 @@ The following special repositories are defined for these features: - `IFaqSectionRepository` - `IFaqQuestionRepository` +- `IFaqGroupRepository` #### Domain services @@ -108,7 +100,9 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra - `FaqSectionAdminAppService` (implements `IFaqSectionAdminAppService`): Implements the use cases of FAQ section management for admin side. - `FaqQuestionAdminAppService` (implements `IFaqQuestionAdminAppService`): Implements the use cases of FAQ question management for admin side. -- `FaqSectionPublicAppService` (implements `IFaqSectionPublicAppService`): Implements the use cases of FAQ's for public websites. +- `FaqGroupAdminAppService` (implements `IFaqGroupAdminAppService`): Implements the use cases of FAQ group management for admin side. +- `FaqSectionPublicAppService` (implements `IFaqSectionPublicAppService`): Implements the use cases of FAQs for public websites. +- `FaqGroupPublicAppService` (implements `IFaqGroupPublicAppService`): Finds FAQ groups by name for public widgets. ### Database providers @@ -116,11 +110,11 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string -This module uses `CmsKit` 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 `CmsKit` 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. @@ -130,6 +124,7 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md) - CmsFaqSections - CmsFaqQuestions +- CmsFaqGroups #### MongoDB @@ -137,7 +132,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md) - CmsFaqSections - CmsFaqQuestions - -## Entity Extensions - -Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the FAQ Feature of the CMS Kit Pro module. \ No newline at end of file +- CmsFaqGroups diff --git a/docs/en/modules/cms-kit-pro/index.md b/docs/en/modules/cms-kit-pro/index.md index 2aec352cab..13c5f416f7 100644 --- a/docs/en/modules/cms-kit-pro/index.md +++ b/docs/en/modules/cms-kit-pro/index.md @@ -11,7 +11,7 @@ This module extends the [open-source CMS Kit module](../cms-kit) and adds additional CMS (Content Management System) capabilities to your application. -> **This module is currently available for MVC / Razor Pages and Blazor UIs**. +The administration UI is available for MVC / Razor Pages, Angular and Blazor (Blazorise and MudBlazor). The public website widgets documented in the feature pages are MVC / Razor Pages components. The following features are provided by the open-source CMS Kit module: @@ -30,10 +30,10 @@ The following features are provided by the CMS Kit Pro version: * [**Newsletter**](newsletter.md) It allows users to subscribe to newsletters. * [**Contact form**](contact-form.md) It allows users to write messages to you. -* [**URL forwarding**](URL-forwarding.md) It allows the creation of URLs that point to other pages or external websites. -* [**Poll**](poll.md) It allows to create simple polls for your visitors. +* [**URL forwarding**](url-forwarding.md) It allows the creation of URLs that point to other pages or external websites. +* [**Poll**](poll.md) Allows you to create simple polls for your visitors. * [**Page Feedback**](page-feedback.md) It allows users to send feedback for your pages. -* [**Faq**](faq.md) system to create dynamic FAQ. +* [**FAQ**](faq.md) system to create dynamic FAQs. Click on a feature to understand and learn how to use it. See [the module description page](https://abp.io/modules/Volo.CmsKit.Pro) for an overview of the module features. @@ -41,7 +41,7 @@ Click on a feature to understand and learn how to use it. See [the module descri ### New Solutions -CMS Kit Pro is pre-installed in [the startup templates](../../solution-templates) if you create the solution with the **public website** option. If you are using ABP CLI, you should specify the the `--with-public-website` option as shown below: +CMS Kit Pro is pre-installed in [the startup templates](../../solution-templates) if you create the solution with the **public website** option. If you are using ABP CLI, specify the `--with-public-website` option as shown below: ```bash abp new Acme.BookStore --with-public-website @@ -49,7 +49,7 @@ abp new Acme.BookStore --with-public-website ### Existing Solutions -If you want to add the CMS kit to your existing solution, you can use the ABP CLI `add-module` command: +If you want to add CMS Kit Pro to your existing solution, you can use the ABP CLI `add-module` command: ```bash abp add-module Volo.CmsKit.Pro @@ -77,11 +77,47 @@ Alternatively, you can enable features individually, like `cmsKit.Comments.Enabl > If you are using Entity Framework Core, remember to add a new migration and update your database. +### Angular Administration UI + +The Angular package publishes the administration routes and their menu configuration in separate entry points. Register the configuration provider in your application configuration: + +```typescript +import { ApplicationConfig } from '@angular/core'; +import { provideCmsKitAdminConfig } from '@abp/ng.cms-kit/admin/config'; +import { provideCmsKitProAdminConfig } from '@volo/abp.ng.cms-kit-pro/admin/config'; + +export const appConfig: ApplicationConfig = { + providers: [provideCmsKitAdminConfig(), provideCmsKitProAdminConfig()], +}; +``` + +Then combine the open-source and Pro administration routes under the `cms` path: + +```typescript +import { Routes } from '@angular/router'; + +export const appRoutes: Routes = [ + { + path: 'cms', + loadChildren: () => + Promise.all([ + import('@volo/abp.ng.cms-kit-pro/admin').then(cmsKitPro => + cmsKitPro.createRoutes(), + ), + import('@abp/ng.cms-kit/admin').then(cmsKit => cmsKit.createRoutes()), + ]).then(([cmsKitProRoutes, cmsKitRoutes]) => [ + ...cmsKitProRoutes, + ...cmsKitRoutes, + ]), + }, +]; +``` + ## Entity Extensions -[Module entity extension](../../framework/architecture/modularity/extending/module-entity-extensions.md) system is a **high-level** extension system that allows you to **define new properties** for existing entities of the dependent modules. It automatically **adds properties to the entity**, **database**, **HTTP API and user interface** in a single point. +The [module entity extension](../../framework/architecture/modularity/extending/module-entity-extensions.md) system allows you to define new properties for supported entities of a dependent module from a single configuration point. -To extend entities of the CMS Kit Pro module, open your `YourProjectNameModuleExtensionConfigurator` class inside of your `DomainShared` project and change the `ConfigureExtraProperties` method like shown below. +To extend entities of the CMS Kit Pro module, open your `YourProjectNameModuleExtensionConfigurator` class in the `Domain.Shared` project and change the `ConfigureExtraProperties` method as shown below. ```csharp public static void ConfigureExtraProperties() @@ -91,49 +127,44 @@ public static void ConfigureExtraProperties() ObjectExtensionManager.Instance.Modules() .ConfigureCmsKitPro(cmsKitPro => { - cmsKitPro.ConfigurePoll(plan => // extend the Poll entity + cmsKitPro.ConfigurePoll(poll => { - plan.AddOrUpdateProperty( //property type: string - "PollDescription", //property name - property => { - //validation rules - property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property - - //...other configurations for this property - } + poll.AddOrUpdateProperty( + "PollDescription", + property => + { + property.Attributes.Add(new RequiredAttribute()); + } ); - }); + }); - cmsKitPro.ConfigureNewsletterRecord(newsletterRecord => // extend the NewsletterRecord entity + cmsKitPro.ConfigureNewsletterRecord(newsletterRecord => { - newsletterRecord.AddOrUpdateProperty( //property type: string - "NewsletterRecordDescription", //property name - property => { - //validation rules - property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property - property.Attributes.Add( - new StringLengthAttribute(MyConsts.MaximumDescriptionLength) { - MinimumLength = MyConsts.MinimumDescriptionLength - } - ); - - //...other configurations for this property - } + newsletterRecord.AddOrUpdateProperty( + "NewsletterRecordDescription", + property => + { + property.Attributes.Add(new RequiredAttribute()); + property.Attributes.Add( + new StringLengthAttribute(MyConsts.MaximumDescriptionLength) + { + MinimumLength = MyConsts.MinimumDescriptionLength + } + ); + } ); - }); + }); }); }); } ``` - + * `ConfigureCmsKitPro` method is used to configure the entities of the CMS Kit Pro module. -* `cmsKit.ConfigurePoll(...)` is used to configure the **Poll** entity of the CMS Kit Pro module. You can add or update the extra properties of the **Poll** entity. +* `cmsKitPro.ConfigurePoll(...)` is used to configure the **Poll** entity of the CMS Kit Pro module. You can add or update the extra properties of the **Poll** entity. -* `cmsKit.ConfigureNewsletterRecord(...)` is used to configure the **NewsletterRecord** entity of the CMS Kit Pro module. You can add or update the extra properties of the **NewsletterRecord** entity. +* `cmsKitPro.ConfigureNewsletterRecord(...)` is used to configure the **NewsletterRecord** entity of the CMS Kit Pro module. You can add or update the extra properties of the **NewsletterRecord** entity. -* You can also set some validation rules for the property that you defined. In the above sample, `RequiredAttribute` and `StringLengthAttribute` were added for the property named **"NewsletterRecord"**. +* You can also set validation rules for the properties you define. In the example above, `RequiredAttribute` and `StringLengthAttribute` are added to the **NewsletterRecordDescription** property. -* When you define the new property, it will automatically add to **Entity**, **HTTP API** and **UI** for you. - * Once you define a property, it appears in the create and update forms of the related entity. - * New properties also appear in the data table on the related page. +* Extra properties are added to the entity and HTTP API. UI integration is entity- and UI-specific: Poll has create and update forms, while Newsletter records are read-only and expose their extra properties through the application DTOs. diff --git a/docs/en/modules/cms-kit-pro/newsletter.md b/docs/en/modules/cms-kit-pro/newsletter.md index 3097011658..6f2837fecd 100644 --- a/docs/en/modules/cms-kit-pro/newsletter.md +++ b/docs/en/modules/cms-kit-pro/newsletter.md @@ -9,15 +9,15 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use CMS Kit Pro module's features. -CMS Kit provides a **newsletter** system to allow users to subscribe to newsletters. Here a screenshot of the newsletter subscription widget: +CMS Kit provides a **newsletter** system that allows users to subscribe to newsletters. Here is a screenshot of the newsletter subscription widget: ![cmskit-module-newsletter-widget](../../images/cmskit-module-newsletter-widget.png) ## Enabling the Newsletter System -By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime. +By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime. -> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. +> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time. ## User Interface @@ -29,20 +29,19 @@ By default, CMS Kit features are disabled. Therefore, you need to enable the fea #### Newsletters -You can then view the subscribers and export the list as CSV file, in the admin side of your solution: +You can view subscribers, edit their preferences, import subscriptions from a CSV file and export the filtered list as a CSV file on the admin side of your solution: ![newsletter-page](../../images/cmskit-module-newsletter-page.png) #### Email Preferences Management -You (and users of your public web application) can manage your email preferences and unsubscribe from newsletters by visiting the **Email Preferences page** (*/cms/newsletter/email-preferences*), in the public side of your solution: +Users can manage their email preferences and unsubscribe from newsletters on the public **Email Preferences** page at `/cms/newsletter/email-preferences`: ![manage-email-preferences](../../images/manage-email-preferences.png) ## The Newsletter Subscription Widget -The newsletter subscription system provides a newsletter subscription [widget](../../framework/ui/mvc-razor-pages/widgets.md) to allow users to subscribe to a newsletter. -You can simply place the widget on a page like below: +The newsletter subscription system provides a newsletter subscription [widget](../../framework/ui/mvc-razor-pages/widgets.md) to allow users to subscribe to a newsletter. You can place the widget on a page as shown below: ```csharp @await Component.InvokeAsync( @@ -52,10 +51,12 @@ You can simply place the widget on a page like below: preference = "TechNewsletter", source = "Footer", requestAdditionalPreferencesLater = false - }) +}) ``` -When you're adding the newsletter component, you can the specify `source` parameter to see where users subscribe to newsletters. See the options to understand the preferences. +The `preference` and `source` parameters are required. `preference` must match a registered preference. Use `source` to distinguish where subscriptions originate, such as `Footer` or `Blog`. If `requestAdditionalPreferencesLater` is `true`, the widget requests the additional subscriptions in the success dialog instead of the initial form. You can also pass `privacyPolicyConfirmation` to override the preference's configured privacy-policy text for that widget instance. + +New subscriptions require email confirmation. Once confirmed, users can manage all registered preferences from `/cms/newsletter/email-preferences`; disabling every preference removes the subscription record. ## Options @@ -64,25 +65,67 @@ Before using the newsletter system, you need to define the preferences. You can **Example:** ```csharp -options.AddPreference("TechNewsletter", - new NewsletterPreferenceDefinition( - "Daily Technology Newsletter", - privacyPolicyConfirmation: "I accept the Privacy Policy.") - ) -); +Configure(options => +{ + options.AddPreference( + "ProductUpdates", + new NewsletterPreferenceDefinition( + new LocalizableString( + typeof(MyProjectResource), + "Newsletter:ProductUpdates") + ) + ); + + options.AddPreference( + "TechNewsletter", + new NewsletterPreferenceDefinition( + new LocalizableString( + typeof(MyProjectResource), + "Newsletter:TechNewsletter"), + definition: new LocalizableString( + typeof(MyProjectResource), + "Newsletter:TechNewsletterDescription"), + privacyPolicyConfirmation: new LocalizableString( + typeof(MyProjectResource), + "Newsletter:PrivacyPolicyConfirmation"), + additionalPreferences: new List { "ProductUpdates" } + ) + ); +}); ``` `NewsletterOptions` properties: -- `Preferences`: List of defined newsletter preferences (`NewsletterPreferenceDefinition`) in the newsletter system. +- `Preferences`: Dictionary of registered preference names and their `NewsletterPreferenceDefinition` values. - `WidgetViewPath`: Default view path for all newsletter preferences. `NewsletterPreferenceDefinition` properties: - `Preference`: Name of the preference. We will use this field while displaying the newsletter component on the UI. -- `PrivacyPolicyConfirmation`: Privacy policy confirmation text shown in the newsletter subscription widget. -- `AdditionalPreferences`: Additional preference list that will show up after a user subscribes to the newsletter. -- `WidgetPath`: If you want to use a different newsletter widget instead of the default widget, you can specify the newsletter widget path using this field. +- `DisplayPreference`: Localizable display name of the preference. +- `Definition`: Optional localizable description shown on the email preferences page. +- `PrivacyPolicyConfirmation`: Privacy policy confirmation text for the newsletter subscription widget. The preference-level value currently reaches the widget only when the selected definition has a non-empty `AdditionalPreferences` list; otherwise the service returns before localizing this value. Pass `privacyPolicyConfirmation` when invoking the widget if you need an override that is independent of that list. +- `AdditionalPreferences`: Names of other registered preferences that participate in the additional-preference flow. +- `WidgetViewPath`: Optional Razor view path for this preference. It overrides the default `NewsletterOptions.WidgetViewPath`. + +The widget uses `~/Pages/Public/Shared/Components/Newsletter/Default.cshtml` when neither view-path option is set. + +The current implementation first checks whether the selected preference has a non-empty `AdditionalPreferences` list. If it does, the service collects registered preference names referenced by the `AdditionalPreferences` lists of all registered definitions, excludes the selected preference and removes duplicates. If the selected preference has no additional preferences, the widget does not offer any. Keep this global collection behavior in mind when multiple definitions reference different additional preferences. + +### Email Preferences Page Options + +Use `NewsletterPreferencesManagementOptions` to set the source recorded for changes made on the email preferences page and an optional privacy-policy confirmation message: + +```csharp +Configure(options => +{ + options.Source = "EmailPreferences"; + options.PrivacyPolicyConfirmation = new LocalizableString( + typeof(MyProjectResource), + "Newsletter:PrivacyPolicyConfirmation" + ); +}); +``` ## Internals @@ -94,7 +137,7 @@ This module follows the [Entity Best Practices & Conventions](../../framework/ar ##### NewsletterRecord -A newsletter record represents a newsletter subscription for a specific email address +A newsletter record represents a newsletter subscription for a specific email address. - `NewsletterRecord` (aggregate root): Represents a newsletter subscription in the system. @@ -127,11 +170,11 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra ##### Table / collection prefix & schema -All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). +All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). ##### Connection string -This module uses `CmsKit` 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 `CmsKit` 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. @@ -150,4 +193,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md) ## Entity Extensions -Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Newsletter Feature of the CMS Kit Pro module. \ No newline at end of file +Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Newsletter Feature of the CMS Kit Pro module. diff --git a/docs/en/modules/cms-kit-pro/page-feedback.md b/docs/en/modules/cms-kit-pro/page-feedback.md index 559ed98a3a..495f7b6669 100644 --- a/docs/en/modules/cms-kit-pro/page-feedback.md +++ b/docs/en/modules/cms-kit-pro/page-feedback.md @@ -15,9 +15,9 @@ The CMS Kit Pro module provides a comprehensive **Page Feedback** system that en ## Enabling the Page Feedback System -All CMS Kit features are disabled bu default. Therefore, you need to enable the features you want before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable the CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime. +All CMS Kit features are disabled by default. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime. -> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time. +> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time. ## User Interface @@ -27,13 +27,13 @@ The CMS Kit module admin side adds the following items to the main menu, under t **Page Feedbacks**: Page feedback management page. -The `CmsKitProAdminMenus` class has the constants for the menu items names. +The `CmsKitProAdminMenus` class defines the menu item name constants. ### Pages #### Page Feedbacks -You can list, view, update and delete page feedbacks in the admin side of your solution, and you can also set the email addresses to send notifications. +You can list, view, update and delete page feedback from the administration interface. You can also configure the email addresses that receive notifications. ![page-feedback-page](../../images/cmskit-module-page-feedback-page.png) ![page-feedback-view-page](../../images/cmskit-module-page-feedback-view-page.png) @@ -42,7 +42,16 @@ You can list, view, update and delete page feedbacks in the admin side of your s ## Page Feedback Widget -The page feedback system provides a page feedback [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to send feedback about the current page. You can place the widget on a page like the below: +The page feedback system accepts only registered entity types. Register the entity type in the domain layer before rendering its widget: + +```csharp +Configure(options => +{ + options.EntityTypes.Add(new PageFeedbackEntityTypeDefinition("Page")); +}); +``` + +You can then place the page feedback [widget](../../framework/ui/mvc-razor-pages/widgets.md) on a page: ```csharp @(await Component.InvokeAsync(typeof(PageFeedbackViewComponent), new PageFeedbackViewDto @@ -59,8 +68,12 @@ The page feedback system provides a page feedback [widget](../../framework/ui/mv - `YesButtonText`: Yes button text. Used to change the default text of the yes button. Default value is `Yes`. +- `VeryHelpfulText`: Description shown with the positive feedback choice. + - `NoButtonText`: No button text. Used to change the default text of the no button. Default value is `No`. +- `NeedsImprovementText`: Description shown with the negative feedback choice. + - `UserNotePlaceholder`: User note placeholder. Used to change the default placeholder of the user note input. - `SubmitButtonText`: Submit button text. Used to change the default text of the submit button. Default value is `Submit`. @@ -77,7 +90,7 @@ The page feedback system provides a page feedback [widget](../../framework/ui/mv ### Page Feedback Modal Widget -The page feedback system provides a page feedback modal [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to send feedback about the current page. You can place the widget on a page like the below: +The page feedback system provides a page feedback modal [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to send feedback about the current page. You can place the widget on a page as shown below: ```html @@ -218,7 +218,12 @@ Two page types are supported by default: a pre-payment page and a post-payment p return BadRequest(); } - public virtual async Task OnPostAsync() + public virtual IActionResult OnPost() + { + return Page(); + } + + public virtual async Task OnPostContinueToCheckoutAsync() { await _paymentWebOptions.SetAsync(); var rootUrl = _paymentWebOptions.Value.RootUrl.TrimEnd('/'); @@ -237,6 +242,8 @@ Two page types are supported by default: a pre-payment page and a post-payment p } ``` + The gateway selection page preserves the POST method when it redirects to the pre-payment URL. `OnPost` handles that initial request and displays the page. The `ContinueToCheckout` handler starts the payment only after the user submits the pre-payment form. + - Create **Pages/MyGateway/PostCheckout.cshtml** and **Pages/MyGateway/PostCheckout.cshtml.cs**. ```html diff --git a/docs/en/modules/payment.md b/docs/en/modules/payment.md index 57425d31bd..193580dac2 100644 --- a/docs/en/modules/payment.md +++ b/docs/en/modules/payment.md @@ -23,7 +23,7 @@ The Payment module is not installed in [the startup templates](../solution-templ ### Using ABP CLI -ABP CLI allows adding a module to a solution using ```add-module``` command. You can check its [documentation](../cli#add-module) for more information. So, payment module can be added using the command below; +Use the ABP CLI `add-module` command to add the Payment module to an existing solution: ```bash abp add-module Volo.Payment @@ -33,7 +33,7 @@ abp add-module Volo.Payment If you modified your solution structure, adding a module using ABP CLI might not work for you. In such cases, the Payment module can be added to a solution manually. -In order to do that, add packages listed below to matching project on your solution. For example, ```Volo.Payment.Application``` package to your **{ProjectName}.Application.csproj** like below; +In order to do that, add packages listed below to matching project on your solution. For example, `Volo.Payment.Application` package to your **{ProjectName}.Application.csproj** like below; ```xml @@ -54,7 +54,7 @@ After adding the package reference, open the module class of the project (eg: `{ ### Supported Gateway Packages -In order to use a Payment Gateway, you need to add related NuGet packages to your related project as explained in Manual Installation section above and add ```DependsOn``` to your related module. For example, if you don't want to use PayU, you don't have to use its NuGet packages. +In order to use a Payment Gateway, you need to add related NuGet packages to your related project as explained in Manual Installation section above and add `DependsOn` to your related module. For example, if you don't want to use PayU, you don't have to use its NuGet packages. After adding packages of a payment gateway to your application, you also need to configure global payment module options and options for the payment modules you have added. See the Options section below. @@ -453,198 +453,198 @@ Configure(options => `PaymentOptions` properties: * `Gateways`: Dictionary of gateway configurations keyed by gateway name. - * ```Name```: Name of payment gateway. - * ```DisplayName```: DisplayName of payment gateway. - * ```IsSubscriptionSupported```: Whether the gateway can process recurring payment products. - * ```PaymentGatewayType```: type of payment gateway. - * ```Order```: Order of payment gateway. + * `Name`: Name of payment gateway. + * `DisplayName`: DisplayName of payment gateway. + * `IsSubscriptionSupported`: Whether the gateway can process recurring payment products. + * `PaymentGatewayType`: type of payment gateway. + * `Order`: Order of payment gateway. ### PaymentWebOptions -```PaymentWebOptions``` is used to configure web application related configurations. +`PaymentWebOptions` is used to configure web application related configurations. -* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. A redirect to this URL is not proof of a successful payment. Query the payment request and check its state before displaying the result. -* ```RootUrl```: Root URL of your website. -* ```GatewaySelectionCheckoutButtonStyle```: CSS style to add Checkout button on gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. -* ```Gateways```: Used to store web related payment gateway configurations. - * ```Name```: Name of payment gateway. - * ```PrePaymentUrl```: URL of the page before redirecting user to payment gateway for payment. - * ```PostPaymentUrl```: URL of the page when user redirected back from payment gateway to your website. This page is used to validate the payment mostly. - * ```Order```: Order of payment gateway for gateway selection page. - * ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. - * ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `CallbackUrl`: Final callback URL for internal payment gateway modules to return. A redirect to this URL is not proof of a successful payment. Query the payment request and check its state before displaying the result. +* `RootUrl`: Root URL of your website. +* `GatewaySelectionCheckoutButtonStyle`: CSS style to add Checkout button on gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. +* `Gateways`: Used to store web related payment gateway configurations. + * `Name`: Name of payment gateway. + * `PrePaymentUrl`: URL of the page before redirecting user to payment gateway for payment. + * `PostPaymentUrl`: URL of the page when user redirected back from payment gateway to your website. This page is used to validate the payment mostly. + * `Order`: Order of payment gateway for gateway selection page. + * `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. + * `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### PaymentBlazorOptions -```PaymentBlazorOptions``` is used to configure Blazor application related configurations. This is the Blazor equivalent of `PaymentWebOptions`. +`PaymentBlazorOptions` is used to configure Blazor application related configurations. This is the Blazor equivalent of `PaymentWebOptions`. -* ```CallbackUrl```: Final callback URL for internal payment gateway modules to return. A redirect to this URL is not proof of a successful payment. Query the payment request and check its state before displaying the result. -* ```RootUrl```: Root URL of your Blazor application. -* ```GatewaySelectionCheckoutButtonStyle```: CSS style to add to the Checkout button on the gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. -* ```Gateways```: Used to store Blazor related payment gateway configurations. - * ```Name```: Name of payment gateway. - * ```PrePaymentUrl```: URL of the Blazor page before redirecting user to payment gateway for payment. - * ```PostPaymentUrl```: URL of the Blazor page when user is redirected back from payment gateway to your website. - * ```Order```: Order of payment gateway for gateway selection page. - * ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. - * ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `CallbackUrl`: Final callback URL for internal payment gateway modules to return. A redirect to this URL is not proof of a successful payment. Query the payment request and check its state before displaying the result. +* `RootUrl`: Root URL of your Blazor application. +* `GatewaySelectionCheckoutButtonStyle`: CSS style to add to the Checkout button on the gateway selection page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. +* `Gateways`: Used to store Blazor related payment gateway configurations. + * `Name`: Name of payment gateway. + * `PrePaymentUrl`: URL of the Blazor page before redirecting user to payment gateway for payment. + * `PostPaymentUrl`: URL of the Blazor page when user is redirected back from payment gateway to your website. + * `Order`: Order of payment gateway for gateway selection page. + * `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. + * `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### PayuOptions -```PayuOptions``` is used to configure PayU payment gateway options. +`PayuOptions` is used to configure PayU payment gateway options. -* ```CheckoutLink```: PayU checkout URL. Its default value is `https://secure.payu.ro/order/lu.php`. -* ```Merchant```: Merchant code for PayU account. -* ```Signature```: Signature of Merchant. -* ```LanguageCode```: Language of the order. This will be used for notification email that are sent to the client, if available. -* ```VatRate```: Vat rate of order. -* ```PriceType```: Price type of order (GROSS or NET). -* ```Shipping```: A positive number indicating the price of shipping. -* ```Installment```: The number of installments. It can be an integer between 1 and 12. -* ```TestOrder```: Is the order a test order or not (true or false). -* ```Debug```: Writes detailed log on PAYU side. +* `CheckoutLink`: PayU checkout URL. Its default value is `https://secure.payu.ro/order/lu.php`. +* `Merchant`: Merchant code for PayU account. +* `Signature`: Signature of Merchant. +* `LanguageCode`: Language of the order. This will be used for notification email that are sent to the client, if available. +* `VatRate`: Vat rate of order. +* `PriceType`: Price type of order (GROSS or NET). +* `Shipping`: A positive number indicating the price of shipping. +* `Installment`: The number of installments. It can be an integer between 1 and 12. +* `TestOrder`: Is the order a test order or not (true or false). +* `Debug`: Writes detailed log on PAYU side. ### PayuWebOptions -```PayuWebOptions``` is used to configure PayU payment gateway web options. +`PayuWebOptions` is used to configure PayU payment gateway web options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. -* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the PayU prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `PrePaymentCheckoutButtonStyle`: CSS style to add to the Checkout button on the PayU prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. ### PayuBlazorOptions -```PayuBlazorOptions``` is used to configure PayU payment gateway Blazor options. +`PayuBlazorOptions` is used to configure PayU payment gateway Blazor options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. -* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the PayU prepayment page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `PrePaymentCheckoutButtonStyle`: CSS style to add to the Checkout button on the PayU prepayment page. ### TwoCheckoutOptions -```TwoCheckoutOptions``` is used to configure TwoCheckout payment gateway options. +`TwoCheckoutOptions` is used to configure TwoCheckout payment gateway options. -* ```Signature```: Signature of Merchant's 2Checkout account. -* ```CheckoutUrl```: 2Checkout checkout URL (it must be set to https://secure.2checkout.com/order/checkout.php). -* ```LanguageCode```: Language of the order. This will be used for notification email that are sent to the client, if available. +* `Signature`: Signature of Merchant's 2Checkout account. +* `CheckoutUrl`: 2Checkout checkout URL (it must be set to https://secure.2checkout.com/order/checkout.php). +* `LanguageCode`: Language of the order. This will be used for notification email that are sent to the client, if available. ### TwoCheckoutWebOptions -```TwoCheckoutWebOptions``` is used to configure TwoCheckout payment gateway web options. +`TwoCheckoutWebOptions` is used to configure TwoCheckout payment gateway web options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### TwoCheckoutBlazorOptions -```TwoCheckoutBlazorOptions``` is used to configure TwoCheckout payment gateway Blazor options. +`TwoCheckoutBlazorOptions` is used to configure TwoCheckout payment gateway Blazor options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### StripeOptions -```StripeOptions```: is used to configure Stripe payment gateway options. +`StripeOptions`: is used to configure Stripe payment gateway options. -* ```PublishableKey```: Publishable Key for Stripe account. -* ```SecretKey```: Secret Key for Stripe account. +* `PublishableKey`: Publishable Key for Stripe account. +* `SecretKey`: Secret Key for Stripe account. * `WebhookSecret`: Used for handling webhooks. You can get if from [Stripe Dashboard](https://dashboard.stripe.com/webhooks). If you don't use subscription & recurring payment it's not necessary. -* ```Currency```: Currency code of order (USD, EUR, etc..., see [Stripe docs](https://stripe.com/docs/currencies) for the full list). Its default value is USD. -* ```Locale```: Language of the order. Its default value is 'auto'. -* ```PaymentMethodTypes```: A list of the types of payment methods (e.g., card) this Checkout session can accept. See https://stripe.com/docs/payments/checkout/payment-methods. Its default value is 'card'. +* `Currency`: Currency code of order (USD, EUR, etc..., see [Stripe docs](https://stripe.com/docs/currencies) for the full list). Its default value is USD. +* `Locale`: Language of the order. Its default value is 'auto'. +* `PaymentMethodTypes`: A list of the types of payment methods (e.g., card) this Checkout session can accept. See https://stripe.com/docs/payments/checkout/payment-methods. Its default value is 'card'. ### StripeWebOptions -```StripeWebOptions``` is used to configure Stripe payment gateway web options. +`StripeWebOptions` is used to configure Stripe payment gateway web options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### StripeBlazorOptions -```StripeBlazorOptions``` is used to configure Stripe payment gateway Blazor options. +`StripeBlazorOptions` is used to configure Stripe payment gateway Blazor options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### PayPalOptions -```PayPalOptions``` is used to configure PayPal payment gateway options. +`PayPalOptions` is used to configure PayPal payment gateway options. -* ```ClientId```: Client Id for the PayPal account. -* ```Secret```: Secret for the PayPal account. -* ```Environment```: Payment environment. ("Sandbox" or "Live", default value is "Sandbox") -* ```Locale```: PayPal-supported language and locale to localize PayPal checkout pages. See https://developer.paypal.com/docs/api/reference/locale-codes/. +* `ClientId`: Client Id for the PayPal account. +* `Secret`: Secret for the PayPal account. +* `Environment`: Payment environment. ("Sandbox" or "Live", default value is "Sandbox") +* `Locale`: PayPal-supported language and locale to localize PayPal checkout pages. See https://developer.paypal.com/docs/api/reference/locale-codes/. ### PayPalWebOptions -```PayPalWebOptions``` is used to configure PayPal payment gateway web options. +`PayPalWebOptions` is used to configure PayPal payment gateway web options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### PayPalBlazorOptions -```PayPalBlazorOptions``` is used to configure PayPal payment gateway Blazor options. +`PayPalBlazorOptions` is used to configure PayPal payment gateway Blazor options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. ### IyzicoOptions -```IyzicoOptions``` is used to configure Iyzico payment gateway options. +`IyzicoOptions` is used to configure Iyzico payment gateway options. -* ```BaseUrl```: Base API URL for the Iyzico (ex: https://sandbox-api.iyzipay.com). -* ```ApiKey```: API key for the Iyzico account. -* ```SecretKey```: Secret for the Iyzico account. -* ```Currency```: Currency code for the order (USD, EUR, GBP and TRY can be used). -* ```Locale```: Language of the order. +* `BaseUrl`: Base API URL for the Iyzico (ex: https://sandbox-api.iyzipay.com). +* `ApiKey`: API key for the Iyzico account. +* `SecretKey`: Secret for the Iyzico account. +* `Currency`: Currency code for the order (USD, EUR, GBP and TRY can be used). +* `Locale`: Language of the order. ### IyzicoWebOptions -```IyzicoWebOptions``` is used to configure Iyzico payment gateway web options. +`IyzicoWebOptions` is used to configure Iyzico payment gateway web options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. -* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Iyzico prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `PrePaymentCheckoutButtonStyle`: CSS style to add to the Checkout button on the Iyzico prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. ### IyzicoBlazorOptions -```IyzicoBlazorOptions``` is used to configure Iyzico payment gateway Blazor options. +`IyzicoBlazorOptions` is used to configure Iyzico payment gateway Blazor options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. -* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Iyzico prepayment page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `PrePaymentCheckoutButtonStyle`: CSS style to add to the Checkout button on the Iyzico prepayment page. ### AlipayOptions -```AlipayOptions``` is used to configure Alipay payment gateway options. **Alipay gateway only supports CNY currency**. +`AlipayOptions` is used to configure Alipay payment gateway options. **Alipay gateway only supports CNY currency**. -* ```Protocol```: Protocol for Alipay (for example, https). -* ```GatewayHost```: Gateway host for Alipay. -* ```SignType```: Sign type for the Alipay. -* ```AppId```: AppId for the Alipay account. -* ```MerchantPrivateKey```: Merchant private key of the Alipay account. -* ```MerchantCertPath```: Merchant certificate path of the Alipay account. -* ```AlipayCertPath```: Alipay cert path of the Alipay account. -* ```AlipayRootCertPath```: Alipay root cert path of the Alipay account. -* ```AlipayPublicKey```: Alipay public key of the Alipay account. -* ```NotifyUrl```: Notify url of the Alipay. -* ```EncryptKey```: Encrypt key of the Alipay. +* `Protocol`: Protocol for Alipay (for example, https). +* `GatewayHost`: Gateway host for Alipay. +* `SignType`: Sign type for the Alipay. +* `AppId`: AppId for the Alipay account. +* `MerchantPrivateKey`: Merchant private key of the Alipay account. +* `MerchantCertPath`: Merchant certificate path of the Alipay account. +* `AlipayCertPath`: Alipay cert path of the Alipay account. +* `AlipayRootCertPath`: Alipay root cert path of the Alipay account. +* `AlipayPublicKey`: Alipay public key of the Alipay account. +* `NotifyUrl`: Notify url of the Alipay. +* `EncryptKey`: Encrypt key of the Alipay. ### AlipayWebOptions -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. -* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Alipay prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `PrePaymentCheckoutButtonStyle`: CSS style to add to the Checkout button on the Alipay prepayment page. This class can be used for tracking user activity via 3rd party tools like Google Tag Manager. ### AlipayBlazorOptions -```AlipayBlazorOptions``` is used to configure Alipay payment gateway Blazor options. +`AlipayBlazorOptions` is used to configure Alipay payment gateway Blazor options. -* ```Recommended```: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. -* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. -* ```PrePaymentCheckoutButtonStyle```: CSS style to add to the Checkout button on the Alipay prepayment page. +* `Recommended`: Is payment gateway recommended or not. This information is displayed on payment gateway selection page. +* `ExtraInfos`: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +* `PrePaymentCheckoutButtonStyle`: CSS style to add to the Checkout button on the Alipay prepayment page. > You can check the [Alipay document](https://opendocs.alipay.com/open/02np97) for more details. @@ -720,7 +720,7 @@ A payment request represents a request for a payment in the application. * `State` : State of payment request (can be Waiting, Completed, Failed or Refunded). * `Currency` : Currency code of payment request (USD, EUR, etc...). * `Gateway` : Name of payment gateway used for this payment request. - * ```FailReason```: Reason for failed payment requests. + * `FailReason`: Reason for failed payment requests. `Complete()` moves a waiting or failed request to `Completed` and is idempotent for an already completed request. `Failed()` accepts waiting or failed requests, while `Refunded()` accepts only completed requests. From a59cf83925d571a34cc90aae3c2f8bb58a1087ff Mon Sep 17 00:00:00 2001 From: irem1demirci Date: Mon, 20 Jul 2026 11:55:10 +0300 Subject: [PATCH 34/41] Update post.md --- .../2026-07-17-WAD-RECAP/post.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/en/Community-Articles/2026-07-17-WAD-RECAP/post.md b/docs/en/Community-Articles/2026-07-17-WAD-RECAP/post.md index e3cfad2361..69e5fe24db 100644 --- a/docs/en/Community-Articles/2026-07-17-WAD-RECAP/post.md +++ b/docs/en/Community-Articles/2026-07-17-WAD-RECAP/post.md @@ -1,24 +1,24 @@ -WeAreDevelopers World Congress 2026 has come to an end, and we'd like to thank everyone who stopped by the ABP booth in Berlin! +WeAreDevelopers World Congress 2026 has come to an end, and we'd like to thank everyone who stopped by the ABP booth in Berlin! -We had the opportunity to meet developers, architects, engineering leaders, and technology enthusiasts from around the world. It was a pleasure connecting with so many members of the developer community, hearing about the projects you're building, and discussing the challenges and opportunities shaping modern software development. +We had the opportunity to meet developers, architects, engineering leaders, and technology enthusiasts from around the world. It was a pleasure connecting with so many members of the developer community, hearing about the projects you're building, and discussing the challenges and opportunities shaping modern software development. ![ABP team at WeAreDevelopers World Congress 2026.1](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS4zHy%2BR4dF3%2BYf%2FtQCpm6USG%2BPfkEFUE0giWmqZzA%2FqHbedsADTkW5jxPZVxyJjD1ZxYkCFVpgySB8KSYBTBAdDql%2FEFmCA8GZ7%2F1p0W2Y5V2ob%2F5I77rotPvY3K2lPaKkH4WPQnNbou02%2BJVW6wPxN) ![ABP team at WeAreDevelopers World Congress 2026.2](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS69OYPeQbcVnawHZSL4ohAgZW3zQ%2BLnKM2ZJcj3mVMXlqXqyI7JQNCfbjJODmfRzdEaEyjXI2Afgx4q9gnOTM%2F8jfISltQ%2FhJIlLt8cyKzG4t%2FLWhjP4K0olwpw2AxU1FHx30pKKKb03NCGPF%2BXoq67) -## **Great Conversations and Product Demos** +## **Great Conversations and Product Demos** -Throughout the event, our team showcased the latest developments across the ABP ecosystem, including ABP Framework, ABP Studio, and our AI-powered development capabilities. +Throughout the event, our team showcased the latest developments across the ABP ecosystem, including ABP Framework, ABP Studio, and our AI-powered development capabilities. -We had countless conversations about modular application development, clean architecture, microservices, AI-assisted development, and how teams can build enterprise applications faster while maintaining long-term quality and maintainability. +We had countless conversations about modular application development, clean architecture, microservices, AI-assisted development, and how teams can build enterprise applications faster while maintaining long-term quality and maintainability. -Thank you to everyone who shared feedback, asked questions, and explored how ABP can support your development journey. +Thank you to everyone who shared feedback, asked questions, and explored how ABP can support your development journey. ![ABP team at WeAreDevelopers World Congress 2026.3](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS5Lh7%2Fs%2BJm9dBfa89UIM8DtdlERFkfiUAx2DzqxE0v8z5hf%2BDWpFiiYVOjWq5NTdFxaqqzM079kwIYLEGcL7LljvWtjm5EZtbFYBlqXt8P4stnEmMSclaZocHZp4OgS%2BfN0caba4RQpLQgjIESBICFU) ![ABP team at WeAreDevelopers World Congress 2026.4](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS4MxSzQqrl5ZNL%2BYgzHUxeTjm0uv%2B68PDOkP8P%2BwVJeSiVYFwsyumyB2tI85Ik5nJMbWt5zzaOHUUhYKKSHFZMRg4uAcAVSyT9hIdF3G3DeN6lfzCraFeA7SOvbgPTSiho6VxgzIndricFCd6bfMleD) -## **Sharing Our Experience on Stage** +## **Sharing Our Experience on Stage** In addition to connecting with attendees at our booth, we were proud to see our Co-founder, **Halil İbrahim Kalkan**, speak at WeAreDevelopers World Congress 2026. @@ -28,19 +28,19 @@ It was a great opportunity to share the engineering practices and ideas behind A ![ABP team at WeAreDevelopers World Congress 2026.5](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS6o2J%2FNrkCu8kKnfIGPt4h9zpeS61T2EHZ76ICCnBJlBMiQbXHWjHfPa7ZrWWmSA8om%2F5%2FPGUtcVR9yeGXj7jckumTHqSk1hTDQLDrs8pyYs4K1hz3FOpDmsNo8DBxaf8BDBtYY8RnfSMjUuhTiTMVn) -## **More Than Just a Conference** +## **More Than Just a Conference** -WeAreDevelopers World Congress wasn't only about technical sessions. The event also featured interactive experiences, including a lively arcade gaming area, creating plenty of opportunities for attendees to relax, connect, and enjoy the conference between talks. +WeAreDevelopers World Congress wasn't only about technical sessions. The event also featured interactive experiences, including a lively arcade gaming area, creating plenty of opportunities for attendees to relax, connect, and enjoy the conference between talks. -This is the approach I'd recommend. It keeps the ABP story focused while giving you a natural place to include photos or videos of the arcade area. +This is the approach I'd recommend. It keeps the ABP story focused while giving you a natural place to include photos or videos of the arcade area. [![Watch the Gaming Area video on YouTube](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS7Zcq8bydK7%2BRRT0moIRkCgYSX6gkXffggpSCpS7%2B%2BZQuC42apGawV4nYr%2FKDuY7UHtlw7AbIFV5cIIevx2UqQ1IPG%2Bp4IWeYKH0isSwi0Jk36jBkH21UQiNlUFnif38Cd8copX22EX1eGV0DGpwjId)](https://youtu.be/K2WzoMfO76k) ![ABP team at WeAreDevelopers World Congress 2026.7](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS5E2azupUQeKZCFpz8uOxJfDT%2BoD%2B6sKWEDVrQZLKPgwWauQman6CjAA35QPNHDPaR89CJNnOaqAr%2BDVZNUF5LLpqtkkbhDxL8cs19hRvOozR%2B%2FEMDMLQv05ZicCEswkaH68pIi8Htau91x2j%2B%2FZUNY) -## **Meeting The Developer Community** +## **Meeting The Developer Community** -One of the best parts of WeAreDevelopers World Congress is bringing together developers, architects, engineering leaders, and technology experts from around the world. The conference featured inspiring keynotes and technical sessions covering AI, software architecture, cloud, developer productivity, and many other topics that are shaping the future of software development. +One of the best parts of WeAreDevelopers World Congress is bringing together developers, architects, engineering leaders, and technology experts from around the world. The conference featured inspiring keynotes and technical sessions covering AI, software architecture, cloud, developer productivity, and many other topics that are shaping the future of software development. ![ABP team at WeAreDevelopers World Congress 2026.8](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS4sS8dtDS3uz8BQ08GHrJhfYezTNm7iIPkGxmwSzAdqu3I5Xm83qEuWbyZyrkz%2FexjK%2BqWZ%2BwC2eUpOcjNJPk7a4RM97Es6Yy1SVC1k08fcpqmbF22enrV9%2FCRwLRaA0693i9TAlo1NBOcQAGDDLgfx) @@ -48,13 +48,13 @@ It was a great opportunity to share the engineering practices and ideas behind A ![ABP team at WeAreDevelopers World Congress 2026.11](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS7FG5qcTtOkfnSg7C9sq9zTrhQ%2FlW5Qe3YPb1WUnp6Yg%2BSXIPdg8B7L%2BiAtW7wqVK6%2FLU3EFpxIlEA1zYa23xwlggpo6V8%2BWFppT88NGxQ5Kn6vBV8S2vX1rytjS7RUjBHVXxJR8X5Vk49WYv5kCER9) -## **Until Next Time** +## **Until Next Time** -A big thank you to the WeAreDevelopers team for organizing another fantastic event and to everyone who visited us at Hall A, Booth A-41. +A big thank you to the WeAreDevelopers team for organizing another fantastic event and to everyone who visited us at Hall A, Booth A-41. -If we didn't get the chance to meet in Berlin, you can always explore ABP online, join our community, or reach out to us with your questions and feedback. +If we didn't get the chance to meet in Berlin, you can always explore ABP online, join our community, or reach out to us with your questions and feedback. -We appreciate everyone who made WeAreDevelopers World Congress 2026 such a memorable experience, and we look forward to seeing you again at future events! +We appreciate everyone who made WeAreDevelopers World Congress 2026 such a memorable experience, and we look forward to seeing you again at future events! ​ ![ABP team at WeAreDevelopers World Congress 2026.12](https://abp.io/api/file-management/file-descriptor/share?shareToken=CfDJ8BFJWl%2BAUE9Cj%2FpQ216BBS60ifxXi0WKAOMXk3uWgymwuMfSRR441sbaecJaHrzvlKokwVAQHsFtcr%2ByT9WJlUME5VTvE3iny0Rx9tVmSqlKRfKrAgvsEsyl1ACFjqjUvzqlXvswIpLWXNxoqIm%2BGylB6JFSA1cASNXNIs21tq9P) From d093ac1528c399bb8777a2562cf7aa1ee403f061 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 21 Jul 2026 10:34:42 +0800 Subject: [PATCH 35/41] Fix index misalignment in MultiLingualObjectManager bulk fallback --- .../MultiLingualObjectManager.cs | 23 ++----- .../MultiLingualObjectManager_Tests.cs | 24 +++++-- .../MultiLingualObjectManager_Tests.cs | 68 +++++++++++++++++-- 3 files changed, 83 insertions(+), 32 deletions(-) diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs index c41605ad0e..8625355e52 100644 --- a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs +++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs @@ -152,26 +152,11 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD var index = 0; foreach (var translations in translationsCombined) { - if (!translations.Any()) - { - //don't try to find a translation - } - else + //if the src has no translations, don't try to find a translation + if (translations.Any() && res[index] == null) { - var translation = res[index]; - if (translation != null) - { - continue; - } - translation = translations.FirstOrDefault(pt => pt.Language == defaultLanguage); - if (translation != null) - { - res[index] = translation; - } - else - { - res[index] = translations.FirstOrDefault(); - } + res[index] = translations.FirstOrDefault(pt => pt.Language == defaultLanguage) ?? + translations.FirstOrDefault(); } index++; } diff --git a/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs index 9053472c48..6fdb848f49 100644 --- a/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs +++ b/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs @@ -107,14 +107,20 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_books); foreach (var (entity, translation) in translations) { - if (entity.Translations.Any(x => x.Language == "en")) + if (!entity.Translations.Any()) + { + translation.ShouldBeNull(); + } + else if (entity.Translations.Any(x => x.Language == "en")) { translation.ShouldNotBeNull(); translation.Name.ShouldBe(_testTranslations["en"]); } else { - translation.ShouldBeNull(); + //Falls back to the default language or the first available translation + translation.ShouldNotBeNull(); + entity.Translations.ShouldContain(translation); } } } @@ -126,10 +132,12 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Translations)); - foreach (var translation in translations) - { - translation?.Name.ShouldBe(_testTranslations["en"]); - } + translations.Count.ShouldBe(_books.Count); + translations[0].ShouldBeNull(); + translations[1]!.Name.ShouldBe(_testTranslations["en"]); + translations[2]!.Name.ShouldBe(_testTranslations["ar"]); + translations[3]!.Name.ShouldBe(_testTranslations["en"]); + translations[4]!.Name.ShouldBe(_testTranslations["en"]); } } @@ -149,7 +157,9 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Language == "en")?.Name, m.Name); + var expectedName = og.Translations.FirstOrDefault(x => x.Language == "en")?.Name ?? + og.Translations.FirstOrDefault()?.Name; + Assert.Equal(expectedName, m.Name); } } } diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs index bb527d2fcd..ef65c6a133 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs @@ -108,14 +108,20 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_books); foreach (var (entity, translation) in translations) { - if (entity.Translations.Any(x => x.Language == "en")) + if (!entity.Translations.Any()) + { + translation.ShouldBeNull(); + } + else if (entity.Translations.Any(x => x.Language == "en")) { translation.ShouldNotBeNull(); translation.Name.ShouldBe(_testTranslations["en"]); } else { - translation.ShouldBeNull(); + //Falls back to the default language or the first available translation + translation.ShouldNotBeNull(); + entity.Translations.ShouldContain(translation); } } } @@ -127,10 +133,58 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Translations)); - foreach (var translation in translations) + translations.Count.ShouldBe(_books.Count); + translations[0].ShouldBeNull(); + translations[1]!.Name.ShouldBe(_testTranslations["en"]); + translations[2]!.Name.ShouldBe(_testTranslations["ar"]); + translations[3]!.Name.ShouldBe(_testTranslations["en"]); + translations[4]!.Name.ShouldBe(_testTranslations["en"]); + } + } + + [Fact] + public async Task GetBulkTranslationsAsync_Should_Keep_Translations_Aligned_With_Entities() + { + using (CultureHelper.Use("en-us")) + { + var books = new List { - translation?.Name.ShouldBe(_testTranslations["en"]); - } + //resolved in the first pass + GetTestBook("en"), + //has no translations + GetTestBook(), + //resolved by the fallback pass + GetTestBook("ar") + }; + + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(books); + + translations.Count.ShouldBe(3); + translations[0].entity.ShouldBe(books[0]); + translations[0].translation.ShouldNotBeNull(); + translations[0].translation!.Name.ShouldBe(_testTranslations["en"]); + translations[1].entity.ShouldBe(books[1]); + translations[1].translation.ShouldBeNull(); + translations[2].entity.ShouldBe(books[2]); + translations[2].translation.ShouldNotBeNull(); + translations[2].translation!.Name.ShouldBe(_testTranslations["ar"]); + } + } + + [Fact] + public async Task GetBulkTranslationsAsync_Should_Prefer_Default_Language_Over_First_Translation() + { + using (CultureHelper.Use("fr-FR")) + { + var books = new List + { + GetTestBook("ar", "en") + }; + + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(books); + + translations[0].translation.ShouldNotBeNull(); + translations[0].translation!.Name.ShouldBe(_testTranslations["en"]); } } @@ -150,7 +204,9 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Language == "en")?.Name, m.Name); + var expectedName = og.Translations.FirstOrDefault(x => x.Language == "en")?.Name ?? + og.Translations.FirstOrDefault()?.Name; + Assert.Equal(expectedName, m.Name); } } } From ee67e1bd7a3a65b6f65a81bba978db51aa3a1e00 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 21 Jul 2026 10:35:33 +0800 Subject: [PATCH 36/41] Improve accuracy of framework and module documentation --- .../domain-driven-design/repositories.md | 2 +- .../framework/architecture/modularity/basics.md | 9 ++++++++- .../fundamentals/application-startup.md | 2 +- docs/en/framework/fundamentals/validation.md | 2 +- .../background-workers/hangfire.md | 2 +- docs/en/framework/infrastructure/json.md | 2 +- docs/en/framework/ui/angular/commercial-ui.md | 4 ++-- .../ui/angular/lookup-search-component.md | 1 + docs/en/framework/ui/angular/oauth-module.md | 2 +- docs/en/framework/ui/angular/tree-component.md | 17 ++++------------- .../ui/mvc-razor-pages/javascript-api/clock.md | 2 +- docs/en/modules/ai-management/index.md | 2 +- docs/en/modules/cms-kit/blogging.md | 2 +- docs/en/modules/cms-kit/comments.md | 2 +- docs/en/modules/identity-server.md | 2 +- docs/en/modules/openiddict-pro.md | 4 +++- docs/en/modules/payment-custom-gateway.md | 4 ++-- docs/en/modules/saas.md | 2 +- docs/en/multi-lingual-entities.md | 2 +- 19 files changed, 33 insertions(+), 32 deletions(-) diff --git a/docs/en/framework/architecture/domain-driven-design/repositories.md b/docs/en/framework/architecture/domain-driven-design/repositories.md index f9b1c7d4dd..ea74f0976a 100644 --- a/docs/en/framework/architecture/domain-driven-design/repositories.md +++ b/docs/en/framework/architecture/domain-driven-design/repositories.md @@ -311,7 +311,7 @@ Methods: - `GetQueryableAsync()` - `WithDetailsAsync()` 1 overload -The synchronous `WithDetails()` overload is obsolete. Use `WithDetailsAsync()` for new code. +The synchronous `WithDetails()` overloads are obsolete. Use `WithDetailsAsync()` for new code. Whereas the `IReadOnlyBasicRepository` provides the following methods: diff --git a/docs/en/framework/architecture/modularity/basics.md b/docs/en/framework/architecture/modularity/basics.md index 560d794f96..c6ee6f567c 100644 --- a/docs/en/framework/architecture/modularity/basics.md +++ b/docs/en/framework/architecture/modularity/basics.md @@ -149,7 +149,7 @@ You can also perform startup logic if your module requires it `IModuleLifecycleContributor` is an advanced extension point for adding an application-wide initialization or shutdown phase. A contributor is invoked for every loaded module. Initialization follows module dependency order, while shutdown processes modules in reverse order. -Derive from `ModuleLifecycleContributorBase` and override only the phases you need: +Derive from `ModuleLifecycleContributorBase` and override only the phases you need. Each phase has a synchronous and an asynchronous method; the application calls one of them depending on whether it is initialized synchronously or asynchronously, so override both to cover the two startup paths: ````csharp public class MyModuleLifecycleContributor : ModuleLifecycleContributorBase @@ -161,6 +161,13 @@ public class MyModuleLifecycleContributor : ModuleLifecycleContributorBase // Run initialization logic for the current module. return Task.CompletedTask; } + + public override void Initialize( + ApplicationInitializationContext context, + IAbpModule module) + { + AsyncHelper.RunSync(() => InitializeAsync(context, module)); + } } ```` diff --git a/docs/en/framework/fundamentals/application-startup.md b/docs/en/framework/fundamentals/application-startup.md index acfe4bf2aa..39a849b7bc 100644 --- a/docs/en/framework/fundamentals/application-startup.md +++ b/docs/en/framework/fundamentals/application-startup.md @@ -215,7 +215,7 @@ We've passed a lambda method to configure the `ApplicationName` option. Here's a * `Configuration`: Can be used to setup the [application configuration](./configuration.md) when it is not provided by the hosting system. It is not needed for ASP.NET Core and other .NET hosted applications. However, if you've used `AbpApplicationFactory` with an internal service provider, you can use this option to configure how the application configuration is built. * `FileName` (default: `appsettings`), `Optional` (default: `true`) and `ReloadOnChange` (default: `true`) configure the JSON files. * The builder loads `.json` first and then the optional `.secrets.json` file. When `EnvironmentName` is set, it loads `..json` after both files. - * `EnvironmentName` adds the corresponding environment-specific JSON file. In the `Development` environment, `UserSecretsId` is used before `UserSecretsAssembly` when both are set. + * `EnvironmentName` adds the corresponding environment-specific JSON file. In the `Development` environment, user secrets are added from `UserSecretsId` when it is set; otherwise from `UserSecretsAssembly`. * `BasePath` changes the configuration file base path. The current directory is used by default. * `EnvironmentVariablesPrefix` filters environment variables, and `CommandLineArgs` adds command-line configuration after environment variables. * `Environment`: Environment name for the application. diff --git a/docs/en/framework/fundamentals/validation.md b/docs/en/framework/fundamentals/validation.md index 4d9d8a4d45..2a05e60ad0 100644 --- a/docs/en/framework/fundamentals/validation.md +++ b/docs/en/framework/fundamentals/validation.md @@ -196,7 +196,7 @@ public class MyObjectValidationContributor ### Ignoring Types During Recursive Validation -`AbpValidationOptions.IgnoredTypes` prevents matching values from being recursively validated by the default data annotation contributor. Derived and implementing types are also matched. +`AbpValidationOptions.IgnoredTypes` prevents the default data annotation contributor from descending into the properties of matching values during recursive validation. The data annotations on the matching value itself are still validated. Derived and implementing types are also matched. ````csharp Configure(options => diff --git a/docs/en/framework/infrastructure/background-workers/hangfire.md b/docs/en/framework/infrastructure/background-workers/hangfire.md index 887266e95c..43ddaceb19 100644 --- a/docs/en/framework/infrastructure/background-workers/hangfire.md +++ b/docs/en/framework/infrastructure/background-workers/hangfire.md @@ -47,7 +47,7 @@ public class YourModule : AbpModule > Hangfire background worker integration provides an adapter `HangfirePeriodicBackgroundWorkerAdapter` to automatically load any `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived classes as `IHangfireBackgroundWorker` instances. This allows you to still to easily switch over to use Hangfire as the background manager even you have existing background workers that are based on the [default background workers implementation](../background-workers). -The adapter uses UTC for recurring schedules by default and uses the default Hangfire queue when no queue is specified. You can configure both values globally for adapted periodic workers: +The adapter uses UTC for recurring schedules by default and uses the default Hangfire queue when no queue is specified (a specified queue name is prefixed with `AbpHangfireOptions.DefaultQueuePrefix`, which is empty by default). You can configure both values globally for adapted periodic workers: ````csharp Configure(options => diff --git a/docs/en/framework/infrastructure/json.md b/docs/en/framework/infrastructure/json.md index 28aa279439..54281e40b8 100644 --- a/docs/en/framework/infrastructure/json.md +++ b/docs/en/framework/infrastructure/json.md @@ -47,7 +47,7 @@ public class ProductManager ## IObjectSerializer -`IObjectSerializer` serializes objects to and from `byte[]`. The default implementation uses UTF-8 JSON bytes from `System.Text.Json`: +`IObjectSerializer` (defined in the `Volo.Abp.Serialization` package, independently of the JSON system) serializes objects to and from `byte[]`. The default implementation uses UTF-8 JSON bytes from `System.Text.Json`: ```csharp public interface IObjectSerializer diff --git a/docs/en/framework/ui/angular/commercial-ui.md b/docs/en/framework/ui/angular/commercial-ui.md index e696dec165..c2f24aed34 100644 --- a/docs/en/framework/ui/angular/commercial-ui.md +++ b/docs/en/framework/ui/angular/commercial-ui.md @@ -46,11 +46,11 @@ export class ReportRangeComponent { [(ngModel)]="dateRange" startDateProp="startDate" endDateProp="endDate" - labelText="Reports::DateRange" + labelText="Date Range" /> ``` -Use `abp-datetime-range-picker` with the same inputs when the model also needs start and end times. +Use `abp-datetime-range-picker` with the same inputs when the model also needs start and end times. `labelText` is rendered as-is, so pass an already localized string (for example, a value resolved with the `LocalizationService`) instead of a localization key. ## Standalone Configuration diff --git a/docs/en/framework/ui/angular/lookup-search-component.md b/docs/en/framework/ui/angular/lookup-search-component.md index a60b713510..144f24c1e1 100644 --- a/docs/en/framework/ui/angular/lookup-search-component.md +++ b/docs/en/framework/ui/angular/lookup-search-component.md @@ -25,6 +25,7 @@ import { LookupSearchFn, } from '@abp/ng.components/lookup'; import { map } from 'rxjs'; +import { BookService } from '../services/book.service'; interface BookLookupItem extends LookupItem { authorName: string; diff --git a/docs/en/framework/ui/angular/oauth-module.md b/docs/en/framework/ui/angular/oauth-module.md index 6209f77bb8..b8730806e4 100644 --- a/docs/en/framework/ui/angular/oauth-module.md +++ b/docs/en/framework/ui/angular/oauth-module.md @@ -57,6 +57,6 @@ It also registers the OAuth configuration initializer and the providers from `an ## API Interceptor -For non-external requests, the OAuth API interceptor adds an `Authorization` bearer token, `Accept-Language`, the configured tenant header and `X-Requested-With` when the corresponding values are available. Existing authorization, language and tenant headers are preserved. Requests marked with the `IS_EXTERNAL_REQUEST` HTTP context token are sent without those ABP headers. The interceptor also integrates every request with the HTTP wait service. +For non-external requests, the OAuth API interceptor adds the `X-Requested-With` header and, when the corresponding values are available, an `Authorization` bearer token, `Accept-Language` and the configured tenant header. Existing authorization, language and tenant headers are preserved. Requests marked with the `IS_EXTERNAL_REQUEST` HTTP context token are sent without those ABP headers. The interceptor also integrates every request with the HTTP wait service. To implement another authentication system, provide replacements for the core services and tokens used by the application instead of depending on the OAuth implementations. diff --git a/docs/en/framework/ui/angular/tree-component.md b/docs/en/framework/ui/angular/tree-component.md index bf66cfee12..2a7b446baa 100644 --- a/docs/en/framework/ui/angular/tree-component.md +++ b/docs/en/framework/ui/angular/tree-component.md @@ -19,12 +19,7 @@ Import it into a standalone component and provide nodes in the format expected b ```ts import { Component, signal } from '@angular/core'; -import { - DropEvent, - ExpandedIconTemplateDirective, - TreeComponent, - TreeNodeTemplateDirective, -} from '@abp/ng.components/tree'; +import { DropEvent, TreeComponent } from '@abp/ng.components/tree'; import { of } from 'rxjs'; interface Category { @@ -35,11 +30,7 @@ interface Category { @Component({ selector: 'app-category-tree', templateUrl: './category-tree.component.html', - imports: [ - TreeComponent, - TreeNodeTemplateDirective, - ExpandedIconTemplateDirective, - ], + imports: [TreeComponent], }) export class CategoryTreeComponent { readonly nodes = signal([ @@ -96,7 +87,7 @@ The main inputs are: State changes are exposed through `checkedKeysChange`, `expandedKeysChange`, `selectedNodeChange`, `dropOver` and `nzExpandChange`. -The default `beforeDrop` handler rejects drops. Supply a handler that returns an observable accepted by the underlying tree control when drag-and-drop is enabled. +The default `beforeDrop` handler rejects drops. Supply a handler that returns an observable accepted by the underlying tree control when drag-and-drop is enabled. Note that the default handler is also what records the drop position, so when you replace it, the `pos` property of the `DropEvent` emitted by `dropOver` is not set. ## Templates @@ -116,7 +107,7 @@ Use the `#menu` template, as in the previous example, to add a context menu for ``` }%} -The component example imports `TreeNodeTemplateDirective` and `ExpandedIconTemplateDirective` because Angular must see each directive used by a standalone component template. If you use only one of these templates, import only its corresponding directive. +Import `TreeNodeTemplateDirective` and `ExpandedIconTemplateDirective` from `@abp/ng.components/tree` into the standalone component that uses these templates, because Angular must see each directive used by a component template. If you use only one of these templates, import only its corresponding directive. ## Flat-List Adapter diff --git a/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md b/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md index 436e6b54af..520f650087 100644 --- a/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md +++ b/docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md @@ -32,7 +32,7 @@ const displayValue = abp.clock.normalizeToLocaleString(requestValue); The standard shared MVC theme bundle loads Luxon and replaces the core implementations of `normalizeToString` and `normalizeToLocaleString`. When multiple time zones are supported, this Luxon implementation interprets the input in the configured IANA time zone, converts it to UTC and returns an ISO value ending in `Z`. The output can include milliseconds (for example, `2026-07-17T08:30:00.000Z`), so do not require an exact string length when consuming it. -If an application uses the core scripts without the shared theme's Luxon contributor, the fallback implementation produces a `Z`-suffixed transport value by detecting a numeric browser offset. It is not a full IANA time-zone conversion and does not account for fractional-hour offsets or an offset change between the current date and the input date. Include the Luxon contributor when those cases must be handled. +If an application uses the core scripts without the shared theme's Luxon contributor, the fallback implementation produces a `Z`-suffixed transport value by detecting the numeric offset of the configured time zone (the `abp.clock.timeZone()` value, which falls back to the browser time zone). It is not a full IANA time-zone conversion and does not account for fractional-hour offsets or an offset change between the current date and the input date. Include the Luxon contributor when those cases must be handled. `normalizeToLocaleString` accepts standard `Intl.DateTimeFormat` options. When no options are supplied, it uses `abp.clock.toLocaleStringOptions`. The default options include the numeric year, long month, numeric day, hour, minute and second. You can replace them to define application-wide display defaults: diff --git a/docs/en/modules/ai-management/index.md b/docs/en/modules/ai-management/index.md index 5b01227437..f9e15a7c9a 100644 --- a/docs/en/modules/ai-management/index.md +++ b/docs/en/modules/ai-management/index.md @@ -485,7 +485,7 @@ Configure(options => | `MaxConcurrentIndexingJobs` | `1` | Maximum indexing batches running concurrently across the application | | `DistributedLockTimeoutSeconds` | `0` | Time to wait for an indexing lock; `0` performs an immediate attempt | -Increase concurrency only after checking the embedding provider's rate limits and vector-store capacity. The concurrency limiter is application-wide; the per-data-source lock still prevents two batches from mutating the same data source concurrently. +Increase concurrency only after checking the embedding provider's rate limits and vector-store capacity. The concurrency limiter and the per-data-source lock both use the [distributed lock](../../framework/infrastructure/distributed-locking.md), so they apply across all application instances when a distributed lock provider is configured (with the default in-process implementation, they only cover a single process). The per-data-source lock prevents two batches from mutating the same data source concurrently. ### Configuring Data Source Upload Options diff --git a/docs/en/modules/cms-kit/blogging.md b/docs/en/modules/cms-kit/blogging.md index 1c0ee4713e..d5f6b9b460 100644 --- a/docs/en/modules/cms-kit/blogging.md +++ b/docs/en/modules/cms-kit/blogging.md @@ -51,7 +51,7 @@ A screenshot from the new blog creation modal: #### Blog Features -The blogging feature uses other CMS Kit features. A newly created blog enables comments, reactions, ratings, tags, marked items, the quick navigation bar and XSS prevention by default when the related global features are available. You can enable or disable these features for each blog by clicking the features action. +The blogging feature uses other CMS Kit features. A newly created blog enables comments, reactions, ratings, tags, marked items, the quick navigation bar and XSS prevention by default; where a corresponding global feature exists, it still controls whether the blog feature takes effect. You can enable or disable these features for each blog by clicking the features action. ![blogs-feature-action](../../images/cmskit-module-blogs-feature-action.png) diff --git a/docs/en/modules/cms-kit/comments.md b/docs/en/modules/cms-kit/comments.md index c3a595a7d8..3dade384a6 100644 --- a/docs/en/modules/cms-kit/comments.md +++ b/docs/en/modules/cms-kit/comments.md @@ -45,7 +45,7 @@ Configure(options => - `EntityTypes`: List of defined entity types (`CommentEntityTypeDefinition`) in the comment system. - `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system. -- `AllowedExternalUrls`: Registers the URL values used by the external-link validation for each entity type. +- `AllowedExternalUrls`: The allowed external URLs for each entity type. When it is specified for an entity type, every external URL detected in a comment text is checked against the configured values, and the comment is rejected when a URL doesn't match any of them. `CommentEntityTypeDefinition` properties: diff --git a/docs/en/modules/identity-server.md b/docs/en/modules/identity-server.md index f79f135056..295af79b83 100644 --- a/docs/en/modules/identity-server.md +++ b/docs/en/modules/identity-server.md @@ -265,4 +265,4 @@ The optional `Volo.Abp.PermissionManagement.Domain.IdentityServer` integration l ## Entity Extensions -The module's [module entity extension](../framework/architecture/modularity/extending/module-entity-extensions.md) API supports the `Client`, `ApiResource` and `IdentityResource` aggregate roots. Configure these extensions before application startup, and create an EF Core migration when an extra property is mapped to a database column. +The module's [module entity extension](../framework/architecture/modularity/extending/module-entity-extensions.md) API supports the `Client`, `ApiResource` and `IdentityResource` aggregate roots. The configuration API also exposes a method for `ApiScope`, but the module does not apply that configuration to the entity. Configure these extensions before application startup, and create an EF Core migration when an extra property is mapped to a database column. diff --git a/docs/en/modules/openiddict-pro.md b/docs/en/modules/openiddict-pro.md index fb4eb0b73d..9854807f36 100644 --- a/docs/en/modules/openiddict-pro.md +++ b/docs/en/modules/openiddict-pro.md @@ -99,7 +99,7 @@ The **Token Lifetime** action configures per-application overrides for: - Request token - Issued token -Enter positive values in seconds. Leave a field empty to remove the application override and use the server default. These values change only the selected application; configure server-wide defaults in the [OpenIddict module](./openiddict.md). +Enter token lifetimes in seconds. Leave a field empty to remove the application override and use the server default. These values change only the selected application; configure server-wide defaults in the [OpenIddict module](./openiddict.md). #### Generate an Access Token @@ -137,6 +137,8 @@ Use a client credentials application when a machine-to-machine client, automatio 7. Select the API scopes the client is allowed to request. 8. Save the application. +The screenshot below shows the authorization settings of an existing application (the layout varies by UI): + ![Client Credentials application](../images/openiddict-client-credentials-application.png) If the protected API also uses ABP permissions, open the application's **Actions** menu, select **Permissions**, and grant permissions for the **Client (OpenIddict Applications)** provider. Scope assignment controls OAuth access; ABP permission assignment controls the operations that the client principal can perform. diff --git a/docs/en/modules/payment-custom-gateway.md b/docs/en/modules/payment-custom-gateway.md index 42af40467d..5c83c911e0 100644 --- a/docs/en/modules/payment-custom-gateway.md +++ b/docs/en/modules/payment-custom-gateway.md @@ -9,7 +9,7 @@ > You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module. -This document explains how to create a custom payment gateway that is different from the built-in gateways in the [Payment Module](payment#packages). +This document explains how to create a custom payment gateway that is different from the built-in gateways in the [Payment Module](payment.md#packages). ## Creating Core Operations @@ -147,7 +147,7 @@ This document explains how to create a custom payment gateway that is different } ``` - `IsValid` controls whether the gateway is offered for a specific payment request. `StartAsync` passes the request currency to the provider adapter together with the amount. `CompleteAsync` verifies the provider response and reconciles the request identifier, amount, currency, and provider transaction identifier before changing the request state. `HandleWebhookAsync` must validate the webhook signature or equivalent authenticity proof before processing its payload. + The Payment module does not call `IsValid` when offering or selecting gateways; the gateways offered to the user are determined by the `PaymentOptions.Gateways` and `PaymentWebOptions.Gateways` registrations (see [PaymentOptions](payment.md#paymentoptions)). Some built-in gateways call their own `IsValid` inside `CompleteAsync` to verify the provider response, and a custom gateway can do the same. `StartAsync` passes the request currency to the provider adapter together with the amount. `CompleteAsync` verifies the provider response and reconciles the request identifier, amount, currency, and provider transaction identifier before changing the request state. `HandleWebhookAsync` must validate the webhook signature or equivalent authenticity proof before processing its payload. `IMyGatewayTransactionRepository` is application-owned; it isn't part of the Payment module. Implement `TryBindAsync` as an atomic insert-or-match operation. For this one-time gateway, add unique database constraints for both the provider transaction identifier and the payment request identifier, accept an existing row only when the same pair is retried, and execute the binding and payment-request update in the same unit of work. This persists the provider transaction identifier while rejecting cross-request replay and a different transaction for an already-bound request. diff --git a/docs/en/modules/saas.md b/docs/en/modules/saas.md index c0afe5055a..eb527dc37b 100644 --- a/docs/en/modules/saas.md +++ b/docs/en/modules/saas.md @@ -177,7 +177,7 @@ You can create a new edition or edit an existing edition in this page: ![saas-module-edition-edit-modal](../images/saas-module-edition-edit-modal.png) -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. +`EditionManager` 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. ##### Edition Features diff --git a/docs/en/multi-lingual-entities.md b/docs/en/multi-lingual-entities.md index 3802c39018..f3c8602269 100644 --- a/docs/en/multi-lingual-entities.md +++ b/docs/en/multi-lingual-entities.md @@ -80,7 +80,7 @@ With the default arguments, the manager uses `CultureInfo.CurrentUICulture.Name` 3. A translation for the language configured by `LocalizationSettingNames.DefaultLanguage`. 4. The first available translation. -The method returns `null` when the collection is null or empty. To disable only the parent-culture fallback, pass `culture` and set `fallbackToParentCultures` to `false`. The default-language and first-available fallbacks still apply. +The method returns `null` when the collection is null or empty. To disable only the parent-culture fallback, set `fallbackToParentCultures` to `false`. The default-language and first-available fallbacks still apply. ## Select Translations in Bulk From d87e9279688bb31862e5489a84b884b75f0c1780 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 02:38:04 +0000 Subject: [PATCH 37/41] Optimised images with calibre/image-actions --- ...niddict-client-credentials-application.png | Bin 296948 -> 90493 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/en/images/openiddict-client-credentials-application.png b/docs/en/images/openiddict-client-credentials-application.png index 3e2ce78ae2169f97f1d16540d838b8356ea926df..509f2a99a004db9c80e15c1b64c220e60901c468 100644 GIT binary patch literal 90493 zcmb5VWmKC@_cz)EcXxMpDOOtC9fG^N7b%qB#a#=<-5rVrcPVZy?!~p}N$>l4{^z`B z9sQ7%teMH4+1It@w`C$!m1WS7iILyDd4nb=E2;kG4FdSh8(0BE80g448kZ~di&Irm zQz{@J;O^nk*Z;GRpZ`a1pYy95Cl^;1whv+95$jvq*tmGQ1}4(7@@m=%y#vGfMWq$W z29D7GBDu)wy1#jYg7fzSc$1k$_~s4e8#zfaP4AW8hG-H;Lbw%r}pi}fU@oNZJLe{4nfog{k{dAw$9Njqw=X}Xh9D`}jz&U^tx_B^{ z+j6)qj8uK1)$nNdMuM@;ij#zqEl2;oqb_%vf&Ap3N-MOroo|cWL>EsVr*K>**K%q< zQJ=aPV-WG~UZ;ZP=KKc6N|=YR|2fK_<9$mi;!#y@G4^nOS`OeLl$h2%{OXvYT)x(s zsp{?1hIH%&u79Y`Hq~Yr;#M@l9ZeRcTiv;nSk=<_Hy55^fh@{t&h!WBZF2=agF5rS z?6{)IS6P$+-OA?T7cnVfj6@=s??nI_lmu=y;P-SnTgFcLq?kT$LPtej1OOVs;V@DL zqj~jcxZ8hDt~Nx2+uhq(pD(VrmKy%j;z1#PXqf{B1)+8v)QZ!~=uP8sT9GI} z^_1a(T+K`Q4a6XHDM1Wq#G)z?iPu|GvAeuL6``T{jZY}V^mU4xaSWyr(tXM!;w57EWPo>m@PYHVrrFFZ8o20S_3FkeJ8CV3KwF=h?IIpUNyGvsrWqm?+^mA314ELfp>u|0;} z8eFy~HCV5>LaMxDMmIUXc_|v)4C$nFq!-W0u56??PVoIS#R6#Y%bM35b|m`6o|ubi zOjg9rNv(rD!qH75(D(Iu%+-jKIk&hb%U=OQ5H!9L7NMAF`HjywqanRbJuk`V z4=cUxW;$Wwpq*kq{a2opD2oYKAbD99dKOy4~opD$s(xyCu$8KO1 z^>%7>h>(;sy6e7*`JrBBr@$#aE9Yw!jeqDZ`jahI+qfu<5ml%NEcTF&&van1+tV0J zuW8CUKlp2!w_%3sbGL`s4qQfpeqA~1#~k$e$nLly87)oQ$|^)I6uwI*U|xWw?=%7o zd9w4wh9IpZbj!@8m8amhI7yCf$|vrIFd$pu)%9b8Xb;sP8KjfFNox|KYFJw$h^@yi zB4i^_cksnnu@{-wLD2fvTuw*vo6x!y=WY|~wH~_f#K;N`=WLE_9PI`r+*+BSS|UoX z=fX28dZ#En>47dwbcG{NX%sr90KZdYck(tG2a%*=ihjz@IE*QpL&`}!^mO1Z z)2YVXo(as(3gD!ed+O5XaLjTf@oLQlFKwUsrQtmNJELegmfhu^W%b*PjB z<@o7&0`FxHG&?jQM64r(oJI6xn=X6+p0zPKNgDr1TTS`i#K*PZQcXog6rp$vzKJq> za!y3ynlcdWCg;x8QeUfd0f{*zfl1HA2eae7&|eRvt(uYui~Tz~z=*ZKr<7T?o=bjZ zUhp}vbhSs2oSiG+}_pkcY zP?l9}h|nL8rzxn3F9${J5urPkY$#rIQl2-H&1|fH)Io}B@T-v5G_7}5zmdDOFbg)r zAAS?)Zu{p0le{toyFF73#E$6qSwFps!wPStBkuRTO6E3YPH7|EDqGxF)lcTty&n~` zVn}ViD=;=Z4C-YE-@ZYS`TDQ&4t$0b3gJoa;jWE2x~jDk@ijmUE?w4j)lfTvQ4bO^ zs?NskG(!)kSHg1_LVm}}+K%>@oGYINw-hnj#~clT_v2mU5=TkR{V^u_M)Uno`4p7v z_pzB>Ep9=y?;5CJzcLhwywr;&3t`r4sT;LS3Z)ddCpP@U`Tg!kSzIuy_CQz2h1srr zt(Hck7VE=g7{VcWF6p(j$qyDjanwruk(F^NG=CaCdo|bS>tDC)_*;~w<>h#YI6^Ju z*t~qKb}^iNx?B#Un;+OGXo{lg@|I)c?=&>KO;SHqo7wT%SC&7HE7i)Uh4^4I{DnKx zPwGspj1%KqahPF62J{=UIUf}j<@bFUUCmdOA^sL0(qL9#*}_CJFTyJG2~FOQ+A%xa zO60yXJicklT?WNxn9a1#)etJ;{%}=|+1Cqi-E}QTpsBKGW}BRAsU%Pmnxt8F5Loc2 z!I@G?eK+wnWgj`q@Mr2YRH^PW-&FHDQoRkA)8`;K{lJp9c@kMNiQ}TfSL@3y`%2IL zYcY4yE4Cwb#ZZfA`yLgwN-*#b2Q zthW_cLAcfEDpMwVZ{guG*hoCyq`!R>?QoEPfA;wo=O-+!7i>YR<&C1q72B6RteSViQ}-c2BYh<9QZ|MRE1pgpWL+*h zqa-5#@_Wi*{Eo$JMa4F_j6z+b1HO6kDs{lD0C|odo ziV6(=`%Or1@_#_0rvD(6|A5s0b?QPC!0^w?7r}qw=mid}0}kZxVLn5G&?8|Uhu*(j z#05NU=L9^M|IftV=YzV9u)+1^pIyN~cIcje(Q(N8)z`F`q#u&YJ|F)Mp}0Ot{0ne# z{zYA!|3SS#{(k^8y~%%}v(i6k7-{KW9Q@z$|15*z?0?47!T&D%ACCUT-++HK615N3 z;U7*FA^&Cn-|_!_>wm}pv+RFR`~SN2`wtL7$>$wh9_Qbw)-O|BJL%|YuOETvR4yrC z9xzPx;cuc|A+ZwLDhAdK($LZq3I`j6EKUdR9ZYN$GUtQBljb1;B|$4k!ET?nx?-ci zfVRNQKg3jvm@4Ed#0Wuekt)JH*S+ID)&H#0tIhV?64_9qIP3b>ssBx5qRID-Ibqnv zPgnz#k(NQ^SiB;K2-LT(?{Tq4&eX=!5C!74fpl~UtGX{rTRNt6dY8ET<3A9;We5mG zeEN*Ww*%~*q>GrWmu2suU!uK4yO}$VnfE|(j*b=g`11aS6mV>~9cb}R2?pW!z&Ef) zcsE*Cauedx<{?Ue|d;vbqTTSfi-{X2NmA9w*{K7MC+`%j!lRR~hIw}w5&vDa^W zA5sVQS&C@it(#jRothF?pn;p-3IkrPclH@ZIIPz3MGPLoSu@Nn{%n^gu=Z=2OZa`) z*J zMbKooeIL2s&~Ziv&_+IZ0s(@TB#>w{t0?e}#u_QSm!=+s9pjA^{UYyY`Fuh80C9jSfNgkk+#koq#+fJ;7)()e_w~t|ql;sJW4Hvx7o* z1M!OcKPN&a%nG99l)??suV}|SUy~DaRF0NhfC|YrVNYBY52D|%K>DNvrIajw`bSER zHr}j!ZnI)<#`!1u_zEWd>r2zity^j!Is&ZX>b>L_J_n>$ya@ewHKyrr27dYqDnJ_M zyk$Et-tAh6Zz@x`2*HEKA_xkBTUp8e=Ilb%X!)ZJUdo-n0LgQN*qHvu%z$1(euV00HF}ot@v526r znG)2BCShTs3n4o^>ag3Ec$PDd&b96jwx15!n=dFi+}*S+^dCKUZt?qS<- zkGaLITGLF_%+>M6#cX(^m@~2XP1y+N9$ZfsykQdHP&qjA4{PCjs^FL@<&|5YkTceM zMec8ZDvdJ!7~nMP3%xdvvq_3{25R>lR-|Ra`*0)aCb@D^Q^KSv!x^OC;mO_T{PI5P zxw;?AVrR9ZnH!JiymKr8_q2G2hPLSt^3VAIun3NRen9!2Wcf9JIJAQAB~FKLN(bdP z%9bJJA;INaWj<;3sdR}J}i;d%1rJO*PZs^jOE1l-dTL2MoL$g!hUY?4#j{-%58z!aVtf#-zY-_ z`v_tiM5xGt3fu!cJw-et80GD*L5sjWdWAa}I98NQ-AT+o6a6j?0}>jTT5F67zo8cv zMHLi1-5DLan9XTK;2AFGm!yNWPw|4KO&s16v5SM^(`=u0&VzsPmkLfN54sOed+&zU z=!C281H63`p&`YI8AKg2iXJ9}8ZM`kam(}txg%=q%EXKng2q^@d=zV^bv_$l*?VGW^nY!SJ249rr z91e~97u?fN8kUL^uzR_t+F_Z*1Wr5_oUPh(k*iDS6T9T$z~YUt8v_s=s<8haaylw{ zLNu1lp3wM;-amo9lZ}`LK%-L*>8zL`0lZnoo*pk*A4=G#ML2n_PNY@Hp+xcaSrl$< zk(L#emGjXjwJw^FdGDB&2qsps#d{7y9f7a>i>#@}TfX&Bkh`*+@Vr;Ex>pX8{#}AT z6tM{ICysg{iA0OkLy9JHuQ?(Ciq&)I)}UbjMi|k!FiK{bv#AB$Hs8q3+#Kab2FZ>9 zl5bkX4{yRLn31FnqCU_ry59cUVJn@9B2c=Y2)kJu-ragvmkWN)RxEszjVCWU!hk0n z@jlA0YPgjH#L=2g1OI*^w8f+ttTv^lsT-7HF>8iak}RhRe{_-u z6clb2EydhT)Z`*lY`b^{-l0d0Ok^0byEmF(EPArDYkZT*U{p?bLPZY7&~3MHl5b_! z?8p6)TvE8g>i_l7l0oSva4+m(mnj5PJ?>*g-Q4^}`GggAWnm9NG2rgj>ll3tjrnpaGEyg!=6JN4OEk3CYcrusf@l<(Y~ z8Rl>uxP2Yt&0%v&j(igrs z*k00(Jhx=ar`JOsq`SJ6}f*ES)DOz&- zn$JuoS4H%2&VN#IY5dlD?ThQ&yiVLya=*l(?o6$RK8yF&;MwZ4r-LVRV3$JafXjUl z5WTXh2tKS#(@U5e8khYcbb$g8K`ESb|NK~h`hy5yNL5Cqm^lA0G~XUV@;)KpxTzSY zfX;WB5|a(6=noC^kJ(id{(0M4SP0ekIQ?ne!ju-hC0M_b;iCr3u3RuniKZn#b>m+! z-wFfQG5GoFHWle!biKAlaN5p9;K)1_Td5m*I4@H%`>qPMgU7*4j=RveA}+sbt%w&l zkUF7rckv-NCFjmL{Tqb*-v=B2FR{wMvDW_tA%AJUmxoa8 zV~Xhu4yj-DTqeR$tYVRTZ)?c=G#E*hEBKU6*`GCE+0{QP(IyK)3deI9z9+Mn)6S`l zo>+|L3Ekh?{iW*}4RcRgsi!cg@CU{kO0$D{gA^hjVS%NNl=nL$=9=X4Td5DlwL(4n zA0u3k{uh%)sgEn`3b3SR(?71JT|{nHa0Yq6`=K`jy>KrvDf^KO6+ulBmkg882jX1> z&YQMlufjWA{m!sQi{xi=5(GwGoVY6l*J&i5k&4icKq-xH105IRKXe`10VnaW+rjJM zJm>7siB5G2c|j)%jNVGZTToU9S%B4TVWKeprx~9QQ2(k?xfY18ex5s1 z=ec=(h^b4|=fHF!mgZw5REb&q+DFClA^i5_;R+p4BdLN-uREpl9u%+xjVq&Rfb4mJ zzG6N1>k2=0r`r~&@a`f7m_Eyg=$$c~M6x0Tlg@~!XBttO47$p7#FtE8-awD12Akzh z5io^weqYtOm7Q_?Hel;7Ep6o1Nq3gqb)!*{@MpL4OYRn!3Ibxn*z#85*t4J0HJY5P z9`q3j3>-L|!L2ckt>cFew3VE=F5e{~+g=L+-qSvqwX?PVp)`d??lJ0)fu`A^k8b4T zACQxX&I-&JFS;uLHyEtX*yIk8a#)G+d}Xa_Y-OeEuSlwu?5INrem?U-mR}T?0qE%; zw`{~^LBnF#tPno0MCZ^gDR1{*>{FNnL~dNKgF|L3oTS~ z(!=>FCQ}ku0N~BIA+QI-5^2EuR>)0{E2)mh5V%&cq`F+4J@^o~x`k^uP-ae?RE&I8 zNW_G1ZLW^Xz`&rlL+=1R&oQLGupf!ROQJ)*k4%I_L(v+kmFubf`ev~nQ*4`Q{8@S?LmA~u;*&fMkw?1p z$26f}s;2mvV(ENMUTdHEgmFA9Qngg7bnX5-nm_B(naa4amIG48FJD6riwEvTS9 z6x%%)ty2(Vb0sugSiK{xEYt(nycU5?UQ}hrnue6#p@6+m6I{t69qvOwTsyV%Rn?>j zWMz7T+iN%h1ENm-_~J&#%AIxK>S8enYv#hynwsP%mL1VhDcf(M!%3A^+chK)ff}W% zH2Dl_K~1%C>^fa9m^e2xZN0a#EpPNqW9rn>CAPGCiKp0EIq>DiXuVreS$40!m9P;D70QGf;Rgdv-=(FW$-L6z z4OMQeS1_B_9cgRBH;UM}IVYHk&5QIb8-DUmH=Lb)Q)&!^B7A*R{M9|HoA)&NjDjXjZS|W2TM8uVeqbmJT*^&n{d# z?+R)xVjy?#z&lyrFq_~zk+Dx<`AKk9HdY9RYghmUF1LN@w&AD=!plr5zS6o{S`R#= zW;R=DO23Xm3dRjFNVH%h*~FfqYK983=khwqRhVrY=?r(@m^nwYii1GIV~nQ$G}Lk| z0*$siFF0_Dr6-2(?8MAZ)lN*(xzKD|T1}dLxtt)jz_P=I#-G;L@8=P@D3w;FFsHbx2@y`f~ zI~cBu^!^%g14HKYKXP8-qRd(y5|Aw2exz=tU4f&nOT2KLVL`KBJRvUBFu_$1=gnuO z_`JKF$gY$wk>8EdN~l6-5=nqftj+D{G=;h*y#bOgd3 z6(4@;jiA=Zux)wNcm^M@i?G_TFL&)7sqk!y=>Ky7C5RUzSPt?B2tbhb(fw6k=F(49 z_oI?bi3oSgX#X(!CG;*SqzLI<=Gs_DbG_w_GgWBGF%YWxW%VjSRmf32J$`W=J z!(>IQDZDZF?4M9ty7p9yQi_H5DPss-^qRPuTkQ^h{4_NcS`e}>tI_+46Y@_%sapEG z0&p@7*|oIi#71VA8gH`(htr~72YWUk4A+P;(^6g=zmKr(M+G)|)}V0KQ4(zQT2pVE zRJytcA#Cib4`kfI2+14McQ>So?=}k*(@C?@tgY+a^Q82GjFh6LJFvTPwk!SCqe_-)I^FYLsSheNJw``v^rbWWXP- z{Co^AGr{3;*>hR0o=sc|_SvExbSCgKak#bet`1QiP!~P!yQUA{{1>|1Um@#Ez}$Af zbiie&E`G6hr^P2Kv%kvEwj{L^mV?+^w3g4flkxa08dzU`)c?fL83`!Z168qjb<>+9 z*Y+2$WIpenLDRZ}6xtAM%dCi`u(iiySP{4KRM(Hq8qx58f-mzA2ka={D?{41$H)Nt zO{aLwp3PmmGYQ8cLyn&bA}t+}S}Tc(n4H6Prx9QcRv69OloO$#jkDTO=vo->yxdP~ zQZ32i^*higxW!?3*#z63oL0fPw;-# zP?nMYG;^?EGuI=FCDfVu-vv8=9eb=o_Quut_4;xpr(?e<+i;i&9N8FM15+G`}Jt7afs}z2+sSs zF;A?`7@|7Hcq&JFNIVYqW~UU+?EQazZaX38rL%12O#!Td>QZe7l?g7pWYNBx8kI2! z5+?{!TK`1Pdb3otBN2K3%9S zTCAsFOaYB$n#^nQ^Hd@eCUq~+RvL29I;!uFnB?{qk2w|Ngb?DlWr`fwZrDT$>SDQ`C-c(^BQ{BA}5Y6u55=j^9u zSOerw3!pkqls9R-OP+Z3F_wwn$<5P@8V?5MyQ8(H$yDMB41pZW2gF}~ok*$ln@t*u zQLz}Df5kiBmDf4?g*lUsTRFO5I^WL95OXEp)6VZ8Yc^S_S3nh`XX=Nn5O!#^+b%u> zB4Tr~*}46Lc?;myMsiv*nx4pN$=Hj&sNc`kBWw`$+)>SqTx)Yr{hQr4rr!8m3RaLI zj7KK41oxydc6^|Q!;;AuF5QodeD$rR6OjzVQ`v6Lgd5CHl|rMEPxbp?(dzaot26P# zZ!8Q8mW{EL+8_a!u`V%RcedXm<$9st7b>!nm<^>Sro|gVxinDLbmUgm?qqXQP|N+? zVuN}q9pibL6`#LufVQODq1QF+o$cEM#&MyM2 z>)lYk{!W(D(r2g%y;Fa%@Bp)AKCe$mO(;n*cuzj6w#+J}L~$__xw$kL%Zefb(qQGG zrV_p>7oHN^mU@sABoL9?)YG%0SXA<|JEr^BQTPRdh3VKlPBKH_!a}}>0;R`mW|WnS zw8n9iEYS#NdvO6tDAUB?XwshY?D6kMJA;fCqz0?lB%4(}dOIgAZF(R`4S;OyXz@1$ zGYo@tL4DHOm=PqE-j0sS`?W&eQs$N3y8T8d`&t+tY zT*aI-!#{zV?RWpZ+uR^GlHpaI`-Rsi)Iut7ZJ2waj4DnyggWu48*Pf=tNxa)x3js4 zJ?;DiS2^rbD1lb4ylPO%80_ofh+sVCSzJ%PX~wbPJgFKbY;j@%m6oJ28D^A831?Bv_DJn^7p8z zdc)!Cv!h0dX$G$~WD6we#+wCre6PTm()b?Q_#RtZ#Ti_Rsnz!QybfbC+Q3N=go4hb zh&IlHDP!c(f~|bzDClo27&jAb`u4;tZDy@oJ$(lGJ|7mH=E{y%?j*v8R`4HGAc72N zaa{1IdMzZGDZr_{tfVeMi7_KaSm32_`XgC~OAg`2Kdlm7w`Oz*3@>ZidkwwVqh#%*HV$|uk}}i zfFLdO8|A))1#E7}n;!si<(e-JP?<{yzgK}cGwEPW70O5bR^SwR%1xDA#wJR|U2Z7F z?WvFS%Sxf~;BhuD^!@(1?%a&Hh!sRl+8I>;@Yw$p6Cx~x7?VhpW-rE&I;_ee?jg1% zxjuRtxTb{HZ{mac<5a+w20g2JbF-3|RBfJTj@J~?rYcG(nJHTGs8^3HVP4d`ca(9u zhiFJdjXAUF)&tUYFjvR>#g}2XiBRT;oYm%`8fVBvlng^jZL`%3`_*%#o>WTbJ+{p* zmW-GJS(P$!T+oyTUZTLS*e3Wz#x*`)Dl@Z>{87TLsv4Q4o3P7V} z-fS&MVU(s;T?Ac(y^x7Xiaa@i>%8?ii@A*FEEyk3fe^BVPcv=c&yTt<5mRJoulI(q zm3!ho)7_*yD6r$@<5EZbvmcG{TsGu}D^Cm-_>lz|3UBk#LmdowACR_)mOMeI28%ouO z4{8cyEb)Jo=gT_<4r$a2u_SPFWqw&Ri!|5wjlxFIUQrpRCa40Do6NV(!m?gg4ibct z*6d;G-gFK^^3ji!te~FY6f^Jo6Ws>sj1RE*qxeXiY$789FA?q20%CnSudG6Xcdh1x z|HA6`=%^WVk8149#=xvAuTPVg@!EkO>nJ$s$U^DlYRWT(Zk6M8xniADKh z7WqSkqEgA}Tl{23rfSxnghnOYu6+@(x^tRLam|SW!DODYKO`j#L!}P3hRPSQmhCwE zutJFWU0~W8R&2J5b(ZSs`BJkG#63Qy+czi5OJzT}# zUC8pY7kGHm`d6@V87E(}{P=cE$gVoO*g?{h0qwwOeGeBcisCig?O0(oYYQYNMjO{} zyt}4r>EI`nqhnfXW%y#0LRL8lG__0@YpTFh`cxrEj_F4FaQ*52Eh>&L+Rk1S?3!;$ zDniQmyRi9BX>~^RdVJGvN+LqdZ{X7sj5K~WiRrO;jp3*|_hg0_7#^=wW5$66cWDZS z=^m*sZiG;BJQwoF#YNQO-PY8l=1fnnC_im z=^4}ErGHqIJE|qj-=&6%>dCv1xiHyvYIX&!(3hLyV9-}qMU>4gdr?Q*>a0|KUs+w{ zAB9?vyZ-K0@XtKhQ<}oZZ$MC`_5j;*X}X@Hq4MoW4{VNC$*MM=sj^pGsYKpgY&$KA|^?aO4M*Jm}wxvdLn#+UH zm%T=^X3E#mryd~%l_!*aS=nUMO!*0kBUQDt%eq1Q?z_W`^B1Y{Ai5t!9PzZ|ZWN6Q za#dcXHn3bDd86@_4Gi(>w2>w@cxl~GD=^HUZg~3zVh?H;hId*;t-C`KsJnwnrXs<0 zMry66x2mz@reHdho_eRw!QLP0!JSx7O-|CA&i_$JzIO|oxQwQ3|7#G$HvWl!+FuP& zu*UEQUMa;|T2Ih7_b4fgbk0Ef&bX~bN7<;luL8rL-0EU&Spu^p+^G6nMXkfG$s2T6 z=agU~*6fjs)(D=|bXh4OxFYL#n=+q?m;B)O)VtE}HlFSRaR}2d>JWRH$AqM!8bo4Yy)u){_D$I7!bW%ruOPFirD&I6 zZ=X+ZjqZ~poFWr*Uhkrfl4-2f?Am*{ ze=4zju4&GuF2F{P+3V=55ehvEeeX z8A@L{Qkl|uOxdwiC1$Hlm*RSAwNPUTcivKgAXrOG9c98H`n^jD`-Y~_p9sKubNqyC z`o?4-PhIH`T50Z>yd^FGYkpOs5h_DYT+mw2s%l#{btin7??qNJ2}Yc$DSt^wRR`tU z=q&x=`GyC*pxb4-Olz2egy%5fZ*Tj)Sn*U%PNsT4wtd`yx6XX)ATnX;@?p*tr9vAm zYpIjCAnqTrciIQ=XjyyUj*CAm2OPs@v*2<{4$sw2S{$p zhtCQ;rRoXxR?H0z+`BHb28OhX2~o`RROy7_DA+}8G4u6ig%)OsBu<;C*YuXzI7q2s zAMk_u=|wo!giq-Srh>hR>Pjhn;B0MCwk4UIqA{?PX?*G{qw4E(ttZswGp`G&0Ok-B zI_4^~^MG9C$&JLMs;AqA0GUi0x#^X&&%yMRPxr)32Gk~! zN3N|{RL;Lq=6=iCr4}g?Z}XD<8M$5?aY=6CwYJVU@U}9=Co*p@baP?A<6BH85O}k( zCV(gqF(Y+asK#{GFWf*+WGz3|>+MF=PPsCXoZZAWj;z4lcFf5@7#8XjI?=)ELiQoX zWHa}9UODk3SXT3;&25Ys-7b)fjd^iTP%qKXDuf8FtY_D?{e9GELSq~KXh5~pHtE#R zH@3z}L9QiS2g1R3IJK|`aV#5td;prLFDv-IY5h9Qc=hpyjSsnEf|}VsGlUwNY{v0* zlTkSned^R_`$@T_Xl>1nSSakW2IS4=SmsV5-9XOt;J(bPwS$~+4=L1iNfO%|b=*nR zue~OQ>6Rf}GD*eG`9}4gz*V@ECDkr`ikL0O2x&TbIkDNf7)2lDHQGtCM(4S%i>uFzL6meMk+_jyVcQQKs*Fi;BXV{^ zfjkea#P$YWh>A9Sl`gj3MFQzw zYl8bS1GFies2?rK*y?fN7@|x!@iWpJylnR`NAB09B~$kq7?-00H<-0WV__{YSy%#1 zVjw+{>7*r6u{=6P@PbN@MThhl?x|}^2GWjIPglL$@o*EJDR%~qB*Zi>eSW?hVxx)} zsLWTg>GGfhTi6qfpD?j3<(K0E%3wwivzL@~i!?&H|6xrlw0u<+ilOciu(yc7dcf#b zcsj;xJ`P&5!*$T7&8^brIhqMKKHFyw#cfJ`#U4O zmDAz`>p`N7E(@KRVRJ4MPTLqABSb-Mxrf)n4EZxTB|dMt={I7{Nt>)@O+9jw93lbq zV?ice5Z{#1#&PgKa_0cpIWIE=DT@y|?~Z-93Q19l^=mo3&hfy`&f8OzFdWib*={o& zmylrm!bvREY^M1`Y7GF(t~?A)v8C%e${?_|RH43C8r9%kO$V_Jz0R2p^wxCJEkV2pUkVFTVUt%-%=<%o ze)S!`qdb*&9buZ8`>~SPJW*h5a^cR!ox!^-OSIrYnx4kb5!$%Ub%8{jN<-cCF~%NM zAli^IlWKsLbw_U26ZB4Ak?tr{sz{`!GIEE}VUTHLdx@Lux2Ym07y6!1DG;NlBFm`3$=Lx&Uq(Cwz2RqTY%#Oc1?Yjrp z>QyF1JR30+8KE`(g*vLVa6j}ywp0{<&Wuf3v`Ec)GP)r(0Zz%D_{xW|#)%jP#I)CV zV&IWSf*tzV@(Bj&l6lmg`a``bcJz{{;3{+wHA`vYl4Lw!VNjr_^xRSxBZTd1I#-I| zdmXJ>X`~pn`Y2cNqZh>P4_IL=l!3vddc^y5DWPxJ%NL;e%nr;Dyx)5QNaRZisO5C zYNqW0SJiLquTpSSC^Vs!WCO)wc?Tk;f$9b*TzE=!lW!GzBWX$q+oKez@zgrzqNz$V zFG~&S#yVr!!AHxqZg{qCAH>x)#aLZk3`uTd2((~9^OP0H+!Zl_mr=TP?jCPduO!It zOA)G&$JB1BTS>H1CU)Ko+rh^DFkbEOSR8%@8@`>WDX2XT3$Nn`e+tH=AC_f z_z~eGi(9n$nO^y4uHa02Q*I3!^*HL&6Ym5k(S-bWa%RS5G+9Hl#)I_>t?!eEL2rzf#mgT{ZQ1pYp&}3^b_i`U;-4LAHl` z%EUZetCzZ5#?e_uIQ%GISHEO#sl84~@w!i}A2+A~)xW_z?Apxn{nY`BS7ZRsCQ~=g z6{?+efN;Jd4R6(Q^92KvKp80QL z3>%CnLMuU^B_Iwq#>c{3X5U);`hGJw#+C~iCk(!1$M6Mc20`->B8vEp+_f*!Z@RMK zs-$Cf4a*3fh*{??3_*_7TRl0{CQ4#%*o}W#(Vq@LeMq<5jsL%0!2i$P zzFr?7HXnwt|37yO|6lbF+$lm@apZ?HvbXGG+XrlxVgm}Oo;QyKub6a*(0;~|w{i7U z*EDoev{DvjA>S%??yNIxWW|1+VA$5C-rgoK?o1nmd2{B2$n;2JvR3p) zpOJCCv46nl!=p$~k(Nw_&mU{$j4&@483p#8MIaYKk0@ODuxxOzVSb%J^zEVhlxc)y zS_hF-H$>mmrLFiE@ONzjM(**DDJ@p1%{h@Avgm*LeS2 zv?~#*KZjSO>jR|1X(c<~;Yt3y&q#uu-2FYi{NO72AmcjcDH_71ve85dQtLT2K}FVq z#9)Otvkd_UBp_Teok>P+RHonW6Og>7fr2p_i|ek;t-~b{+``{;jAQl09{rX>Ghmkj zg-?*JjDk~f!ozE$ni0$ z@9s)MCJM~7Hl|Cme_K9bH+I{7)u*MqU(TJbEjbfxlDs(#8#)hw(c8c!VF7dzeP44A zZ)c^X=(+?j9<$EFsu`5ZZ*z!dPZ3}1FCPnQ!$?5V=P@HOl5U=bt@F-xzrNO%wdBs= ztS)Sr$*PtG;m_w@zrt7cGORerjf8Yr&ypJ496I~w_SHYVcKu#FIsVORJ95DveCGp8 zU=HCrG5fMw)A0KG{T1L0y^i{e5F>n_zyMg@-TD#`{^|uIQKuN069+)3HDHecY^d%v zf6Dce{%p>wQ1Qp)JC=H}#@DaKtf4+H{BRxq*)TxRtvdtEegoG=uum^1M@E~cUsv=x z{|xGlCC@u0l|c!B^PjyUcwz0q&J`Y(svR%Bb3RzoRa6j*$?RuYh-U5;Dt5GvX@)+V z`##WRgWF5i;+%sbCxP>aHMjRHawv;yyG}(sXPsy3ayCn_g@53pFA3z^NoLs@2{*1f8Yy_ z8WGN^%{>$k{-9rh5Q!6lDC)4Yw~`7Ea-I!`oH@vU&wKvvER7SyVBfNIqr((s#Bk^tTPzwJq5wBh;)Qp9pA^;I z5nz%oc)#c^3l4n7<2*H@RV69Ld@lvbn+Rk@p?A6fgRy>Tb?|KyBsF|Ur<_vknm~Qs zBA91fGpPiJPB$kH16YwS2cFewzCl#Ek{zvuaB~nm^~;jms^rH=0&My0dAxlo0o$hW z9B{4`GFRXf#n2+A&KmG+mtXLE@@^82jdk11F#5}>;3ttP=ZC$4^*7t6T0!c0cnB{c zKYkRHbj@slWdsr|=S_%tG+3Cd8A|)>bj@c4#{d zr*^A5iS0n9A$OpEwgIZ)*)l>rgo2PEJ+8b7F4X;z0)dxpis zwW!YzQmM(kMkUrmyzUh;NLsPwQjyalGhh{4GI=px==)|S!?M^_I}tIGYI?pSKFyS| zYyOH65l!nGHK>eWs>nc3prA!SLWkN!9?E=yQ~ONT%RpnK-Elud<|F zGK6yE9YCf?j>@{6c@8BXmVhDN&a9F^PDe}3Y=$KQ`})i7_>jB-QlJ556|0RRGd_Ul zmZZpqNPF-Z{ulf*ywE~&uR%#;f4@CumD%X-jfhD{$7S9WmW$?dj3Mk2?9(dJ{YIpN z{jDqM!A%^}(mkB4-LTGRARWc=sLLNoG3w}%^+3^1 ziwd1~(5z-%8-gnM)&KPiuo>{&O5cdI)EY1?CaQsUj134bu(>M#O*01Cd3GyQ+x-zu zg}V?bt%j-zAwDU~`>pYK}o4hyBXx$yB_JRf1 zMRZ#X+6EEOX3XMwcEgt^%)owAlUCbN3KlHngbQCcigaK@6&4D0;195z!EMHp)EVlu}UP2(q3pL4IbPkP4zeDf}kx@lDituxo4V;5Pmhl}3 zu$t?Si|`N+e&*w^1W#d7>Ct@i&2I1`8;NSu0g$ALF0isU2wEx3hfk(ciA=8pnW!MYwqk1kF5mJl7Ss!DK-00B?swEAd87q2+kBuKuV?#P?@W>o9jdDv%d^68a6{V7zlf z#o3tkJ5pMdjcgB|-frVFJDIN8uimfnkQZ{2j6pd3|Mmj(=uUSBf@?G($BQek)vqnz zngaxD``dj;VVd)7Fs%I^XhqefxoYlQntQjn5(XS!*RUS3H z;!o1mkEN;yv4tEB$ZhVc4e(mQ2Y`0BUm)4;k18Mt@1O&C%_el&8D|oiy+^E%f7wkr zj{)+(SRGv{eDZOE8Y<|Wc#<%v+%RtRbVG|$mJ$>9gMJO#`2wF&eUG=AeiF`95Z2n> zV_b3&wSEb}K+qdPULtjXwHAAYgUf<{1O-@$^2`a-nnYWOG8Na%{m3%^oC{u04(>8j zE(`c9g@5ht%rT_<0|W`fJI@&plD?uJTc65}-5g_|{s_q>SfLJN3AdHxfpzZ`4yp*x4t zB({I_o+7*Jt(IYwT>efsmK z-EvpCv8i|D1r@Mu+g3Qmyy34_`F$MCn@AxSEUYWA0b;_3beCcdU3mu^fAd22hHkD1 z3E+m$Qi8mt+K48(aEB$m*_j#uW1Iu0GVqdJQuYQik-cf&f1mu>c)KdZ*`ZwJj=F?6 z8T4YWJ1sy#caQR1q_P44l-#=li=Dc{z$#W^6h^sXkk!zqyN-5!Tj|xH7dB4;fw0V5 zG5;Yj8dSb2!G__NEX>|Tld|YaIW!yBd~h9|iow=zgZ)?MjBYeAipFzt?Cmz6u;+qx%_hN{NcFOa=(R7tjZFbF; z06_z^xRz3iyE_yp?i6>I;_eWfqQzZ`ySoN=DNx*9S_tlN^WL?-fBBX59GRItd-lvh zB`g&D(>&3X@y9l9*z#qEAUm0{{Cx1^;Y;;L6lC3#o|9tbJ5`o zQ4{rwq+uxv%%Y(Aqv*u)hP2^E$~DYYEP1tqYdje(2L@w>@Wq24uzF1Jc~)xBQ>cs< zUQaQr2kb`Xi;F5F(rs_f2WE9%t8;-O4?058O%#B&m={}5$-UPqkJ zE@4FY^)l|=JFV6i`9#Rd@F{_Dq=^wLup#YxmDwhutYjUEBRev!p7JxBfC5B@pUaxC zHDzf_`iCid)|U;M_i-tG+NTCGkW}2-`RWfk2ZwINvs%SlwHHzBwTfYcFyABf(MXT( zSN`BHwUmzi6GF}$a#QaDN3WPaRB6-G7n`0_;=dw&ddk5bF{mW(D%S+1CW;cl@=I$J`%JMJ#o}K zF;v6;D=y(L^shnuYOM^5_q z`vEe5l0SMmhvv*5x4-mfTewz+cKiQC(EsL;hh10e6baV6Yoj1u-KR85aQytB!OAjQ zML)5vR}{c!@ze;PWAJB(aC9ooqmi++kh3dVjMR@I#t!;QjqdHOYd46thSPH_a0?*rg6B{UR;>@{`Jf?OyR);PuE{7Fb4c~ORhzDf@AYlCA z$;qijR~z(GuDd`NI=Y%9lMpij zh3BvgOHD@WnPfNB6jB%g=R`e+i*LOj5o`;n3M;^xcla2y{XOy!Rz*VLW+Y~gE|G47$T2Aq&uH0{Vr4kNI{n0^{O5s5~5vCpeyuf@I^zv1{{oMIWCj zZC=sQ?psx%*X%aL*z;!`2Os}n;N%(72ItlyG%y_*DhKU0{`>0%8S1AsN?4jGG%@=` zrT5hX5hN_;tdS}A1D4by3u?#&>m5J6ScVeln)YeZK0oa<9)Z3!26@uCI$?f4_Y5S> zD|<(1;o2HLPcKPOwk7?-CcuLsBeNSbp?gTAw#G2^NzXEY)yz7bLfu7|KXF6&3xuE{ zuVk_`kV~NhGi3F&z2p5WH}d0Gq!hf`3lx}mF(Z0xAn5hf>#OGbp-d9H6?pvb^T8bd zzRLEcxsgu;nBsjcr3dgYx#gc@-Wz?j?a2SJ6ih<9^|gTLjP6JCotBMeY0LGyW7Nj# zBo@RU;%P$Y$G+Gpk0jfsbraeNjixj8Kf&=yp>NXtefgERRnQ_Yh^9O_mw(BwuLX93 zDP)Ko)&ZWo!>-v|WW<53YmjO&83Lr4iw;vpB$x!YSPShsjO&pg#;kI<81(1@8rV@7 zcVhj5?#Q~!!F-2S@?T{g(=0Oz<&1erPk10Pm@x@eDY1blC`)WxF8G|AL{oG0et^xL z!X3##Ke#a_<8&er5=kN$PxM{~|GH@WPNw;GI=fgz$x>@kd*;8x8IhU6tJjRLA(nuw zsr|Z(-m?13A&tt2+zdQsH?BBgiAY0h{oQ>KggqxL{-}${`pqhxJ`Cb&3QGiF(()`l z8B)A3_hC51ihvLd1Hs+?0odfjGQ$j&cvC7n?ZKCsh zNWL3E8h*;I;##60mY%hjCo=Xy6w!dG_+PH<0Zn$1q1`>8_> zaOtM{%IZ6$k@gbpyT}~h83S;LGWR1#koq$=;7M@`|7H%9Pe4hSjYg{Q#c`>E(P!(HvG``rQ}Q{HNdg-1IiJYzLOpy`R)r#$dA}p6c0omH z&E}73-6lR6JCJaFp|^==tj3-7VA%Ri9)Fcgl#R)ghAb$n7b7fOfV>Tr4_-N}i!~v` zKrM=k;g}@cA;h`Z+r3hCGt6Z+4QOVD77R?n^ty=_&M8^bX6$p$ugFEz4iXRo+**{S*7tx00&`*eIj3VwdD8A6 z@bqF(4s)5_E`isKWg9HZ@C573I9)HR|Je6jd1SH?4-+W>$FShA@^OZJ%WXx6_snif z68V7e8i6dd9MTfMNZBUy1s0X-t))G67!!G&oQN6yEQjuT);u zFRb=w*7NWi$&Pw7!Y@aYYon!Xq-c>g6e+zKg18Dkm-OU=y(%eq%T5}|n;ORr4GQqd zkvFqQyK{gqu9LXmcDwmJ`E%S{D+B+7Kfd_atq}>+LpRqk2-)+F%+Gkh2|#GiRPMoM zS8$w*;gNVin%~U;ln5xOubX6AkMk0B;+U07Vhgl=_5x-#zh2S3Qi;0ITTTT~Aqjrh ze-gibN{=(z8*cp7{WT*OuU2jRpLMm)gh*;95KpoZ;*hOZiz>o zPXDgN7x3m?-sZaozI#o)P=7loUL@NPk!;|LC)zzFqY>%xdcadqO+%jHx4#|x&or4c z9`<5Nmgp*q|DVikszX*!JKh5J0`5Id?u3BP)yP8ni_XKXEoMi@@@|YUG>!w^{013f z67gGn^VI?LaM37fga}IvP;Yk}Y%~;y;ni-#)&T>GJ~X$BXrApRpP*(~k1eg!a~;;F zuv>~%0hU^i@w|g&Aa~?VP*b+8#4DeYJDQK$Z?EhWg8z9%8Hkp_LPioI|J<n+=$r$lf-FY8H#$^2L9P2+brq84ZJDppM!z!QjQq z#3Klte~0d)KHU=9J~gxiTcPtD>WAc56M$C5VTnYNuqftf>yVb}E#;GP#2OxP#bRj%mf`O!B$;K(<(V&=af_!IwH8@}5EeFg1aiGHgENMett*5xjz zWVyI^8MDA^wljGg6W}-$xUd7qm>Pn?4jt zw0QH&ME%;0K$Ukme(YI-oe9e{LXN$usDszlF`3On2>eFYe9!6TWbdvL3EzxTiNT!? z@?%6=afvnm`uCkNgMUMB!oPXZ>mKguv*-~%e`(&xdJEyNw1RiL` z@wsd`S;x4FSiXqsW`xR_78;*|{T-ukMy^`g$|PtBw&C*OC+tpm>K95k$4 z)p4mHN0=^A)b5C^OtED3qmm_&7eCwSp{MT!4%^nT^risJB!?%0HgFt&s6yUj--E$E zb~k3fVzHI#Y)R?Sz1ZT;&^5)9UW$)h1e%p!$ox9I3IoLZj z9oGtTzxJ}-8y^p@e|D5CHVW#WVBGL$+FoHX9UYqy{|7?z(6rdLQhZKrK=-wrt}q?2 z_+exUiqV0ATzMHrc@ez*`)urcfyM<@j5a)-8LcQW!oOtS>|J1T+3zqqD-$TR?z}KD zcR-Xvny_S2`yHg=1~^bfRJQotDaM$uNa!`QZ(eP22Kcl-by;emo9yeNYXp| zc_o@E9WiU97C3EpADSbJ@w|%eNgTEM>Od4w1&*jOc-aB8YI8KCDUSgBG5 znIz86Ku5?qCHHCkT2+y6#&bP2k)_tF;boE`=j+m;Ils{+zlg%LoRFvii`j|zA)G)6 zm0Y(5z;xudVUFP|6QC#C*c@mpsDXcNfPe+`O#th{+g*w*<8rR>V_3h!l!H}U+u1nT ziTA`6w^P@QVL#C4SqOHH*V&AAgRj62Drm&lH?z4O$Lg7OtR7D;KM@&OQW1SqvYv3L z>bV~q)!SaoU4AZi=CU;g^80zMPz#EXb&t)>F>F3IJ?Dxahr4*KvI_~Zuw({@9Oy28nx7NyJfhItots6398~LVEy$!fo&_}A7T3Z@RfTh!NqvIgw z^(6CNIiZ^(W<1xYiSX1^)Tm#X0`_+$K%d981K`tRW8)Yy=Ja_yJhR=`qFgrTA&Xqf zSI3g~&V5dz{;4V4q@In=QAxa(qA%CRqxAxw_ePgdMU5{ID5rWwX0A;vf=P~J(Yl2 zpaNX>3SHtLAND5UAN{B{gV)jm5cxw>f9FE`vF836)Y7gg;Y^Wmp~q?ud1{-N|)(mnZ{smVT4hH76_O~+dEB9^#CF9gy1F~ zxnUQJwV_A9Wzw95$%}_x>o2rvG>5?9Fc($!B%w2ER#v||)%(LXe7h>^KyFcU@FsginEKN~r6 zEsRpOJ&G_G?ZU<+!0HSV))ZfS5H5r`_W0DtG)LA6zKiU^z*d->PuhkGf#?y(e>5>jquwqYbnJs@DnC zb6N^AJZ0-@Ox2wr6eh5$g#|SZRGW8>Zw@U*ow-KWoA3I|Zu4X4DrA>uDAD=Zg^2f} z!oz|3j^QS4PW(83Sc@E~1Auqm z_z&7MnHYXrQ-oHmiKA`P0_eo9i%l<stVp*TJO*OrS#%cc!5G*pC4$=95KGZ&69z%KLxD!6bb8vG$ zePGa$Zk`pm6IE$TB^BOm-(%7+VM@IE9XCP#>U*9J{Y7fbh?i#hE-h-5Z#%_tAB@|z z;B8?;0w(t~3m#)`zKnXR3m&Jl84dYcVv^imMQ$pJ!#nXHRZD@= zf>F#$g*X%qzKpdQOwanGNTOe4U`EDq9;2Y&bs`mIMk;2JDe#A)=ktI)|hgH*BE zLk`1$eB9!I61asy=T7#l!xEZ<=k5Zq6!yaB^zl5pN}bQ*EztTHKnq)m22jLcY~=Jr z;Su}Pygqfom>WlIwEVD+DZ&XC!C<$Eg|hBJ0+Su5MjBF^SBEg-;-A8z0PG}+bzA>< z$hTFdWf1mHkYysMI3#BZhgG#Z&5}>GrQtgI{G31)kqf5E)DFkjKy2T z{{|21>l%&(kYCsMp$NWk9Az2c*Msx|#6!mx*^urKxcmAoL=V?FKf`{WNdc5v)F3@@ zna6I(1r9@alI*DWB#Pazp@4 z#L9*8#OB3oUF@i*^|PTNc2Ohx<6!bsDtX*Md?4?(Z@HUL5E!vyOPcr@@KQYTIKk+tRN^7|72(XM?%RPhq87kzzN61Qb zY(avOqd0ed{kZAsy*?MUaI@@X+^{Tv$G-WHhg&3!_(3MO{b*RHMO4hUcT0N*Tm3=( z&_m=h)6k`BmonlA=VPFzjdGr0hu*?CQjKM663}4Y=_7A*eiUgG_KAMp9dP1@nHc&L3)t`_sTm z2K?|0ZIjOy>MG153^r`9!@<8F{LX}`WmvVm)JG&J9aZfu2*?dOhl+SC$|>~r>1(1f zUIHA|r$~ofYU?6@R-(I7W2B0FLw{VvhQdaR6s?Gf4A!zUZmf%C)4d-h_)Vh|%$e1- z`i<3~rp{~LbF{qW+rZcIKj?Czk;a2&y!H852J4uP?-{l>@}DmCc;79U_y>KfG}RS( zkdWsJQWD@d1X?WE1cWg!+=C0E1SK(-ZZJo4F91Rn;F94C)aPkl&y}@Nvx<@ezFrbN zO$CT$w-#=P?InL&1Y7?_SERYyyh+R*Vm z1#?V5G>0vEQG03?thY)`G85qQI4at>~8g6 zkECsT^t7=`V%b!6oG%6KZc*KD)1M0BIR_P{hmIxUbuQ=cP-^H)*GGmu5Bg%Z#DBE+ zWb^nOY){T-i9G0!+x_Tg3EB+#J=Om0NNt%!*V+wua}g5^k3@54ZeN27J!bs0n__>L z{bzNJ-bjBang{7Dqf{E3A$o0wZH^0(Ah=Kt^$%32VB)f)Px|iPiQf3Bs16I(b8yja z>5QL0Cs`?8DxY47lW#5jTx-Z)UZ2V&OwxeMI;40{2(A^&SI|LYeq-9T+ka9FE`b&3 zXw96=cWx$8(l75{laU3J*n8 z-KB60p_Sc$`ymJXp{|LxuWW!U-uc0jD7NhG`TPYkg%a1_iaVt#VJ!GVr~K!g30bNT z2WQ{O%z&b$k<9CgD)@sNrgX3pgY?i~DcFf^CiF8?YtRjqrM}!s?bz9(C`&8j?>bXC zAyvm^g9&&RuhvdC0~F_8AfenJ=?{{eO~VOp4ela~ z!C+Gy0vo2vsHU!oL^kmcUOO0G9a}#h4tog?Rn} z?;?DQjrs+WAs~EsaTIdHuLclT>>scAywUL0pQE7ZbXcf_KX}4KPx`=V_Gu&rjd5nL zNOsQB>>aC_wngRs?IthXy<3&bxreOT|BYhe?COvWvAh4;Kj%3jt>k=9hFxwYpd+} z30#*n`Ll+HU{5SLMzXKFf@{8CYbz?9->8e@7(FHg$=VFM_&|qGeU(p;@T6c~s^*8L zBPo~8(RD@^9_C3Xz+9&%r9mOm0s3LfQ=r7YbS4!nG(mV8Evtfd^)yeYczzvMxb#gN z8kvY!SSj->M|#CjHRq0|34z2t}- zzJLm5Wz0zK7%d$bTIqZw-*Zs?(15^aV6|DhNnU9ciZI|2qY;eDdYALCf=YyYKVyud z6r_QdP+c2`nT!)G?um!spE0F+A@NDb*jw=&`&?ihL$S(z-9T?65XTR7s`Fa?4CeNF$y-c;4*s5RvxEzS9WME5n7BCKznGXEnpOb3qNp{cq4nN}k zq3t*2u#fz+@^=s7#kTmj$j)=LT+_s)w3~%|Ji6w`sF5)zerhN&)=ln(#Cpq$XVF+88CYBV8Zs@ z0hm+2Ds3#kLQ?)tC{EU`s_@_aWE@4@2~q}z;CJIu5mO;VjZqUaB>j(SBmh+A-TKjB zeXRTQBPo3Z)V%k$g`U%56!Rp?1by3}NlBV$CC6J!z?*y=3y63W2^c4D#+NJ?W$lBu!cGg@c_Ov$o@uIzGbLvwl+iszL08hG@fNE@fZJshgtpm~6PyHrl z2F&NvL9dSZoFXOGD3i$)pDC?33%RsEVl}1o82>CfnH4XbwURM?gbV!yW8B~pIXOov z?)Nnnxt8ud=h>6hspBP5tWDL@j}^^HaTZ2IbZacLnpGT`<8Ve@gYwdQJhO;^{Db-C zzn$>OcIzgsJ09ps9?Y`b+O3Fsq;%)zkavd9txoO;eZ8xsHVa7&&qb9=Vjp~&ua*Um zcyJ&FQP;;znFgI~`U|krc2$9^d2i&OD!ISX?rl-Nf6oy6EB3uOVgF?<(^#$=Nkwdh zlMI?~Y~S<`7Y*VE=^uJK=k0P2&#+eqcpdTo&eP?SH&Q{e0+iu4VJ)C{Z2LwgDC3O8 zTV3r>f&_n_rZ%D5x;94j#mCSAobk$d&gA6Ru47l%8?CMq)qid7T!-&1+F)qy13X&bX&(?e}&nQ9@+S= zeLGch2;I1L_41w>M7efVWXoAQ5K)xtcCD`F$*eKnb}2JXG=;!u{B+Gw)#0C6dIJ~j z-RFGaI_F-*vty0B?F@CwT9p3LGv@LMF(f;!(p5sJ4k48fQ=6)F#^m{sHK8eEvkG!p+LSiE})_I9t7LP3| zQSL}1e={*r^}@gQVbk^(Dn6QGAjm$@?Al7uQ}`k1vfS$Bi$-*-9nCG?BT* zShLXUDS<}pTDp5O00rg&kRopFr(c3C%5e_5b`8AQ%i~pKcS;{QSGVd$31s9gYPD1y zSBZlK6$_s$kI3erM-#Qek)1BVLHnTMfU9&QnnOMbQ@8ga(-fleuS(MJS8F|@c!lm} zMeN6-48L{OO6<6~5OpcRs(u$k+znhW1wHRF9)0`Fo^4)!tcrW<_bPDk z3um4uJ1y8X=0lb+B=Ma4?Zs>|%BBNNiN@2NUnum*?c zhlqJ-pB0%p>zH`pHt`4+om?ku)Xn}!6JCw^=`aN^SxgT@^p)04!BJD-^OV}!?@jmN zp=(&#lpv>{Y+%63d(Qy=Mc0H-7JlTpHo=>Q zvm4jPUQ^&-mU^efZy63s5WZMWUFGFIY=$)7a{I);J31-vjlZOl5Pi(@3{%lmnbUcu zV=pNq+GQyP+O66Bw336&$(&_!KV1&m$ZSux-yf@!JWigyu7vov^c_TSmRxyFSw1+l z3o;#}!HDm`oa)j_C%?06m{HhXU_RyCtg*!68QkcmO(`D1Sc7}J#WIJnpanh3Zn4ts#6xEQQhR0Nk;n;##SsWYuUPj58QQdh%9^EHzmI&Htb-A;{c|0B@9WmNX* z2o;mImKMzUEggBp*pdw}lP3!2c(oxy|L(rQo3+}k^}74(I;9b>moN&CLuaIZ52}7S zy3A#jV$zs541T(A0ui2Y$QfxP1qgln>5FKzpkPKu`Byp$4bv8WP*SM!uXSTG#^Y&y z!@Eqh%3_W!;n%#?sID@gA2LA%)p={V;H~FCNY@xy`_>o_)3`^47f-)C^_`t81vlB7hsMY^#VjFy&x&vhRav(+qJI}_7c0Gg z>P@7G;&bXQe#w$dmV5+njf$x#ZXhLLi(3%a_n~fP1MV(%{mqGxeBmeT^bT>hf#n~9 z;cK(RJDgL>rU@(KrhK!b7q|DLf?m?XbQkidXi!?HuJ?S+iP-hLzg0fSK#N5yXJq7v9dOt2an`sFLF*AkusVD@@ za;vrq{F+wy@2g3J64|zJ^o0;nrHo3DvxO=t>;JR_S0dv&)3oR3J*k)(VExR{qOF>K8+qev~)HrLj3uFyPI*zZ(+BQj+mp#Fr&)AGrN~OH3&wx zBbZgWn1UTn6-euwnYI$DK78YE96$9Oza*~hObiAM4WX;QhEXwFvtnH)!t#0{U&uf( z$^~g{IIg4Uv)+v6)srJ3)`u1n03oX^XO1RMq{v9;cIYebIPs9z25#@g3_#QrJzxF+ z2H^=s&E8qs+6^y%*#ET=@yy)}Z+-~NwyndYNv-T|8L3l2eR0!4SU3tW5 z+dm&rYuqLX;%{;_n4An~IwR=Tynz;+Suhx1c2i6lDV4SXv#;T0Z5IK_f zD|OJ~>CK@0t2V2JGt58kjrvqBJ2R7ytZF*5Lo4I37A)}f*MsML>bsf?G{HbWM;^~) z>2?3lcq%tPyg!)NG|bQ3p&d$Bkf4}$<&UQTuLY zl5i>85ZnSh!-12S_a+5SCb;T@a@_WAxF&3<4fbxNXx>)85{*&Is75~&g>0(MrZW`P zk_aHh4x*8j5mTuicPi^vN`L#L-lr;jrFu>l_;l|FO6&6ftu1Bqgq8YYg(VjG+lR}F zNgLGJFa9$xW?^d!=i|)Js!bLwLYCv*;GwJ{83y_3UBHrTU^=#|8Dy_DIHbQ+B#V&s z+!PCG%jf!Y7gU+t%Yzy`T{r?~6d!`Xzy!gEQ^k&$(^}X?1?s$(!)N{$zTx_Pc5wFejX^tuYaX0=Rq7G;fIEzoGDnzX0Aw*I9%_2s!mVZ4KDvQ!T z`ZpGiwYp7-#S!(Zs5whD&&*{EztxbDFYq?V_nq7!Yjk^az`Oq z`WsoigeHYUZ=~2VlZk-RFD0KEX0Z`d7f#5O(kHl()@rte{Y@z_WoHD^R<^6fQkv0g zy|I7|4>sDwt5^bWf9SWkHGlykNR{D5Z$4K68iR>aNYAAkQ-{Qwsbf=hYeQv^dk=5E z>U@uA>wAyO{3HA5>Uh`z8RZ6?L8*dOqCAy&g%)QCvf`4XodTm`J`V&NY+J82Z%?Uj zwi_7tizblwe!JOo{lVCV=S!i1)1V8!f|!sI-x!mYxYmDq|GAja9cM+&q;_`6&3PYT zm;Hz4?Vy1KtpZ{HzEgDjadpG6@&fIQ2Yp4R6mQ&SCVEDtlReekVik z#YD4R=#So67q_!$GG}ti!tO0yC!3D;GmaA3Bq##2^(HtVD=NQF!4WE|p^$Af} zwc|pD=H5(4!mqNRdmV%B-|72jzb@qcYjOzhszH`{?YK1){qdZQvOi^UC`a?_IB!T9 zm+MX?Xmt}pu5#*-!&~?6C}YmQMkaVf1MZ?){{bph8@{VAZGH&p~%OGk(w5NVYu)U;%^=-L!W?n;6H% zGSGCOB|5MA*n7EO2YX8J6|+aFDW8Z_FOv$3W>e<5Q+&D4l{z;b+DWGAXa0+pr*KQ| zu*e{g{pTPIAeUKmq{RJ`)LRDgCo#MC=Q*=qIhTo3FUi~(-nKJ&6;0VTFFr*p^Y_U4 z0q4kT!%*}mK)}Kg=zh^%T-HUR?4(%Le&rh$Sa!5GiCb3FMog~W%v8bBzntk;^go$C zRr*M(8w1eIm%8}{ye<+|@*Z|9Eu1J@*mdqy5ZUS=tmVY7x4J@*qvu_or_Q5L(x9iM zlL*xra!!1bFWNVWia-pKEy6UjaZWVrn^QHvQdjT(52Q%I*~>C!7> zJaTnLlIUm_*x~um^wIi_e1EUb|-3Fxs-|JP2ILd+Nxc9?~<@>{}c7 z$1a4>t|ATlk{1$&l(lp>D$OX_LamqMZs`>1GC*&(0mI_?w|Zb&5t_gGUO@h+Xd6|2 z(s#XNjtIFpnZSpG%L?!RLf;TLzy`Eb`t*0Ozpd+sPAF@NY5A#nT5l@P>rX*=f%82k$o-pobF zcz9GFcIuvo6&Ujm8C^O8=XDjHVt2=03@#^8zlG8yc+ws>_p@_LRTWtqUkszUHX(nl zgWM=_S{<`(3G*Yoa=W`pdLAQ${t@(p4v(+->0$Ax7|JxWPK0wx(7RiB zRlfaB;1)2r;qQE=C(0aSYo-CoHK|#IBFKey%T{$q%s;}nSkENLiJvSglaqma;sGRf zMOH|6J9XhH`mjwM1fS1bdv*>Di}Q9AOrh{>hPqyj$~ilnBG;+zvmM1IE!`embpJ-?CvL?QijOkOb0HKs9`bVZf|?-SY}*|@fhg^ zixB(Xdo@+(sLHIlyrTS(!xYeE>FOP!BY8F;fCHiVJvdv^?k;n_YaAiYu*_$TYG(be zZFi9QXzt}kawYj@z~~|r!s<|hP5uJ8?yKGAC^a81(lSm4#R!}8P z<+A$3w^+5^L)C^S^3z*#vqj%%wLC^L?|=A(007+ri>};HA`?7&`KX1ubE25$`lECT z@QMo~OJ6+Vp5BAE^&XGfC=#ViB4L2f*(Nm3|=%dV6*irF^lu4ZdDID+mAoQ>-ow~YF_Tp<^& z4XiB!O_Xn2e6%1zvaqhf^ZvQ1u-+W*rFfpdXBQ#pkAgNeyD?8*LhHl-*y&TVF`d_# z^IWED_VQWA3EkmRo|0(1BIsG17rF2FzcVcQVc%E?{-C(Ox#8Sw#(aSq0gb197{xOESU%g!U@yf#$gS#=rK?SHeJ}qkz=VXic zKi7`~7aK;Or9k&c;5$SE4%$jdG&w6ri&{Ro*uUBi57iS5aPodJnlm@aI9K_%gCpTe ztZF;r+h~1-C!Ue(oW{KCONHo<@DNRo8*era1UDv-PCh8jGg|Z`E^16PYpw%qV8LPktC1P!hg6<^rh9B@KjL zniEc4)65ga3Ufj|CF)zRkA5?J(}NeXe#>zUYGW9_vx?kP}(7E_xn*Vd69*GMTsbp%ck9-p@MGUx7pAa_F~o z$bT)-5!VQp*6#!qm-?zMvK8PCSg{WS4jLmAA61 z2%4jWFOJN61HJ%DKfq`_54{OyJcShMImXE6LtPg+m5nEtS4ud{&pG}ppx39-Tq0<* zw`?t9+B@GpQ{hi|Kg#dGHuj1GKO*a$q{k6ez0R=jf9c9sXab0(rPu%h%iPm++7DD# zH8~WWBbE8kTGQAUO_1kI&nVDuPHjrvVj7_~iny^4+Qq#N2RTTJ@a+z%toCs=j_er} zu$;6+cD`xAT8wt2deIK+00*jC+jn#|1K9o=b(lU_FTKsndG*qdqR4<>~{2dqRB$ zDr{0vdkxTtJj@PZ^lJPdFvb!|&`za0!Wh0jS*MmeGbGJ1bPIOjaAG1#sJmm=(wEfgcBWnZV$5tK+eS4444B{lh*>}pR4ZJYQ zWZ|<`%=hZV&9Q_mtQ&}45EI=(VxJrl7tHaMvO#bz@U4A@7*-*!pnLZz!*hQ@xtaL@ zit|`v1OqD1CMNPcQS!dulV&BL7Uu#4*fV>_ydZ&Pm_mw=SUe(# z09JZZza-{)*W_H5spG+Ufj@iKK43U>UBE?)YFMUxMZkOojy?$PF=|7^wKie$?S@!n|_Kv>WsE^)|~uld~E>SrdP zQO659)2AlxlSg?y0Vrbj`r5(DOr;5Lil=A;t^`Q7$T8iZ4pauD06$lws>Bb}^uOhs zoak3F?fSzbd0BG%kp-(@AA9oIZErBJ9VM2^%%9o1)F~V`H-}ynCph_@YpL-KAK{?s zEO~C1SH;WYI~w67HKWjkF{=~cp%L|TNVLvS?zz7dGfPlA*S2@(#&mJeQUoKECcqEF zTd+}TnA`+%*l{YZG3)PO5{NlAjt1FHpGHE)&5?FX=aK)3C>{gy+)&V3l3KdwqHh_z z9oQf&Ge3#XXdkgzzo<7fsx4pk{9Sw8p`7bVIFClnYWzFAiH52I6}_D!*^1AWqw${v zW=?{8{yVqgofI{6(L=q7*%>*uDn~id8bxHMBCV~8sS*z)TQBd*1s1^{K^>?DA%4bp zQGQyQ<97@smh)+T*CQv-KHe*mDUjBTlc}sVIJ8^+cZBE+rNwjZk6!6;bQHc$Ai}y$ zj3D+c4;>Z?BUU3N?v6hHdOoW;Olht2*JH5nL17= z2J{s0c3}jKW_K|}FaXXQPPT7Pg~i;cf5;+2&P`3ad5Z*)fJK#US~Cct8yAbA@NgAu zb3+86TQ7n}Awzrubk5gWU=)Q>?VBqaDaKTqfd1p0BP-?hfcCUVrswiMn{;2MBT_i> z{8S=VQmP|u%Jy{pw(p#0YMej zxXP3t?VM6A>13bq^eib~2C;aD&I_9OTJCL-qT~E7&U{QEGkKa@=ic!6@BXI+IB>~aSUZPf)LVD~|Eft&0!MgmMAe1lgk+gh zD{f1)Hs-U$RJ*xsj?o&Mq=s`^@bZ{D2o~+u7|RdTL7{!AwPl;8x9Qs<#zt~}-MSbS zMul`7o;bT%@e+|bN`Rww`V7iWO=(cL#yq>iRQDB|&CxyFlt0RB0-ziq&nC7StCcHo zs7Zr&A+@;R+MGX>PxlL5u3A7+dmbeMetX^`+e+0(9P)x7`Gy8v>xv9X66U9M%y>te zPzFK#`4{;?qSs0)Wm`;o11Tgy6jR9W&?Xs@VCgD1#J$Vq*u13PrZFNhfF%h>IN}JOR zAwl>nR(zT!^>w!j)WiZ`&RZAwpkS9xT~s*{wn4)X$C^tppvkP9VVThClpFlXw4W9x z!K)~KCf;E#Jglvfb@#F%Qz0>6oUw=`*Xo+}-~yBqjZh%uXdt@cmLu1H?|ip7_Z64? zbyrr)60Z5=ct|(1Sti-z`6xwUE?Vu(*&W%$c$IhL!IK0%8~V0 zzA)EL`_GSS?%A1XQ+~a{Ab?L!DM`E)#L3OQPmBPI1-&83@fq3T2re3f!z%?f7Z~8~ zVxNE&&hQ;M>T*qab$eH`?Lpk@*8x3jVVV*7wD=qDV?JuP& z8miUT@H&GD^oe5^;@m#p7Ty>MAZxvKKgo6?813a}PZ|L0bB=QO?{7%TurfMz)IKFz zR$_QYAQLz-s*j5TA$JpgYT^&wwHbmjaf4-}u*;uv?8fb294bp?eydMlmndyr6PJw7hX6vGS`X1Gf}`J zF;oy!Pzl?>$7TWO=Qxb6Dfk%divuT@`PQbq;M7^Rta2+~wA;bX%_&w+J7dkJho-G| z_X2JqOoB9p+Jpqj)M`<1hy}kX(~$XTZ}aHZMl)}+>Dy}1(3897?@(q%BP=p%{li>c zcR@k%?lzkH^BPbk%E5wWI}GO)h{Xx-Q(e?N=ty74LdbAaOMx3%*4uGj6*A~Z8Npy~ zAs@HhDWt`)6->;C?#jxK?uVC#YCNy1Tr5C{QbJgVo#pfewWyVrR6(}`HR^lQHyzR_ zou3Er{Z?(NsKmLv*g0SRkEXM5h_dQWCnf zbV%nS-Q6hf`h4HtKXBi3&+N{ebFR-^Czq)VYHRkF5cF9^G3V24f9iCvC>T7p51G() zQ2AmGd!omx^CViXl#UvKhO;HPtl#G1v%Jk@Ee&o3K5b1Tbt#ZDejdnnHXd%6C5^mH+ZFKE)|vt>y3|ElmC-XDOdlgO3F&3 zruRa-;X8tS6ni8GB2SWybT;L{wAKCZKqz!7K93oSoofEk3_43kGJ$l;U75b^{Wa?s zs?QcP0hf9KKTiflafheJ1yVmoq(g=ImPRE1Cq}ns+(q7y<|r!GQ(){}II3R}=_kY( zO_667uw(IP_VtHNq|X)|$$7P+5@M6C@4}EvZC14G6sl$MhvF5d%v>dqOkX1gmpGB} z!rGZK^B9gK#K4Yv>>;$oC)bEN@v>$u+-gC$vBPmnoVqfw0gaG0V~H56EDyHmL=4{1 z8jyg1S0VoRfkuMWCt}5{Izoin1Z5&g0Vc0Ox0%QBPoMYJ6U1ToP8`C5yX@X@Rrbgb^DWo5Pu+1-uW{qiQy_znp<4LK! z2VKD)=!M3h07ok=lfQoylEmyJ1p4s>rRIh3app!oU#-jIU|BN(GkJTQD9y5r6UV-! zGDg@il|9O71JW!lb+W2_cy+&>rW4IgDAx)pV`GL)a=}swU#oJNdj0$zS9JH~tLKyz zIn(9YT!CJHejOSo_z)zlHqeAslI$n7d1x*Lo`_A7!3lmqp{9qFIUgJc2~E4ILu1i2 z*g;SDM(yYW&UHEq8c}*{qHrqKMvHO;W+|M>KUrclCWR6O%rmF> z1Ert!&z6SJp%;)eLw#kyTMwcp(`yOakjBNHkOVW08)f8fmm8}TgTC{6k_MvS!OQ9Q z=u6kn1Nw0oizX?#rnq@p$bDW`HMV`*?(w)EVyo0MTuF1XMYo3{WY?_SR9vpTLL3^MDJN_ zm9xu+88-2zo+vr#yRJ{AD$R|XA8CjMmXg9s&!5?)KokwvQL&N3qz?&SEFN)FXv4p{ z(y}$yth>Y^44Ly?N9sSy;{4>P67#@Ja8wAb0RphEPdS*S=rZ={J{6#o>10}5Ymr8J zzudeH`yHBNVn-Jl7dwz*Gji51H2>w0G`iG`r|NyZ%UJNsED~l%3sv_mEt$Dav4&&D zZBRV!ulFt9;+L@~Qq|sHP_lJV^e>iVEV9kC;-zRZVPhRB#-pZ8i9?4Yj*F6BD_?n$ z1?C^q=o9~%aGCPQz0%s7o<=D_pvl#p4sUtL1!{~g9rOME>c?=ErYmA`?*^$sfu^ww zc!AaBUCY-asg2Nu8*rkuR|-@n6_vg<6z;@+GiH{?v=_HvpupxINYyI6ca(rVRAo+) zCS9#+S3zPSFC*GxOm!&#LUOouoGS zdkSlmT0{n3k8BAK7}-LyAFYuc0xfVy1V7p`t*nKYrsF}=Zwbp^DfW@oeG|-d3rKhi zV)b9hu!rhYB6bsB-DB?|O{D2JJ3}lRp1(zanDFV^5O1{o=Ql@{|6 zCjok*a`-J@M5b2IujQ0*3#DSN$6kb~`#GsiLj#BK#eD^GJbOBNHosD*1m2stSRQs7 zVYqqe@EeqQ3|P=$aM&&O1s71)!Me$lVW%!5vZQ$fvBmJ)ruvy^>J zBC|#F>>DHKx{(#lqz88EmH85gRj^;SYbZ zVEDlCtV-HoysO=qfd{U6+qHwuFYK%LEaGbYcQ+9FUqQE}@9p=sv&kVG@=(2B#^X0- zrNa=_mFH#QD}mI8?Q+{Y5N^u_QnJ_Ct_5^Qf%vBF^_Itg;AH?wGO5&z{s(TRSj%~jDz zZOFq5J6hh)u*^;7{-@``m)jaw+(wO zQJquUZ)Z4(tlWEWsV%UwK#E^=Q5h^BGJh%~n`f~2Edp4X2oB;6Xy!Imc6+$H z!pNnTcFhI8@Po%b%MNx?1)pdDR`)VyX&S!;LKpnX=zkg)4a(HQH0r9%KS{A1JSrcv zx^yNx-dH^V!ReT*GU?h1jLvP9Ee=yi{pnmZQg=sND~wpX3;w5_XMkr9;^r01Q4X+a zwN+EsiN=sBBhkkVY~3sk+x&PZ1#%&{zY|ZSh24JpN)D01L+QibXl}4W(2o?)Ex(pp zgqPGV1`aXs^ASK7Q@4-@2A5fsJ*l550Us!XUmTU6p~3i(kjjqc5=g7wVfO+REX95t zRlc6Iq$FDB@nVGM&?5;XP)eVEy{)iEZ-+%-VmQ@06dcqyFj1sx?MfLOL<*XZ?6l^5 z!3zGEC6fkRc_L|OcB>tlh3zE=lvGY>q8gSUd0Q$8AXJr}G~aHqF@oj@_BC%CnmvWa zZp9TAR)0S93xfiiUJuz^ZRjGvK-dBMfM;Zk`{Un1VhFP7X89D63*QPp#Z-LM+s}SO zNr;l{`3oV*u>~!OVrFDV2$#usAn$dZqp{ncpT`p{<#sFqX{^97x?3wX4+~$yYDNhuR}-6-9m0a~c6d zE-q*uV!^ATBVMUvIq3R>B}uGnqJ@pB(;8@=W!2O7$5}M>mWl(&xm8GOa7_}fwPXMt z2r_&g+)+OKXpf#VF@t(v$RzO`bY;(@`feE>l`}Rb^s(8W*5`la$Qcz2-n|N}Hx4Qq zARKa$&d|)NlGDj_m6=Wx{h0PVl;ng<6Q5cI!Nos=k3aG>^shZww8D_W5`J3%H^m#uI_iIT4z`I?nG5br?$SWz6B}U zmcEq;Mk80LyI}cLJmxi|HCY?z4|=fwyG>%^Ub2a9v?m`UjC+KEs28Jz8&|@cRCl}+ z5RjN5G^2iHn6%F{aO|@~C4azj@OX&I($4$=DD)TP0g&9RwL^0o<){ zXdxNB!{Wag`2sX)x%E4E5x43R1^a7EF|WeTUV2GMCW)$i37CA-Cg0vqU$gq|2fl?} ziK`>(NRJeawD2N|fBI|96tj`6Q&3uYDXVu|SL|X0snEVjJPrM)J_V73tXuT1()~7X zFWN}XUz~@S)64%)bc($#d1xPT`?d#dna6ftrQiIVXY;%apK23V-85g}*%3|g!FN|| zDKlhs&D%D7C{yZd>2YkNzh;W0(m%4_{7-lC#<)KVu8-;Jn53wW0ZJ52g8(KNdVxm1 z=wqEoy|T=o_Rdgh*G)#qu0_Ry7tK0K$Oirxi%FP?aZ=i!{8=7VZkzv`tbW{5_*z#X zAni?SqbB95`a%4(1)M-|2izVP6MyVAxC&$5Q2FHdHy}JH6Rz#6j)Fd0bO*bT5C%^I zzQv8$mxJ05wR&gbrC8Q?u82F+auBVNVA10OE=>{a0ai z;5vlaJrfA&+ti$v_T^`RsE*4x09#OE7_auIql7kSRD22kehhfcx;DU3s5kIQl=BfE z^G^Z%lc|%C{UJP~LXS^BwtV6MU8)7Ltlu2hK9v;nUP7_X9HETqbU+_{29Dpok@-mV1{8sXC};ZyiSZoK2)rM@ z1H*kMk2p&7*^bQWyl&WRf66na@eX=@x^xLKR%9P!)tgFd!eX~_@P;e5&+K5Ond)3e zqd0gsqd40Tk#<<*KPIUf<39EUFGxp7pxr1zatR+07KCs#rohe0Qc;`mq1K6V4~o1e zGYBZTUW8vBqig<6LX?K9-g<5Z^@XMl4s|;%K3T&p{~I3MlH_79(EBi4#m1t%FnnZz z(PCeK7AvHS@lP5gq}gDF$KNq@FuJkeAC`aw}W z^mM_E`UeC&_FGx-!034QiWOAmFXr=n1Xr_(_kbH|1&G&FllSmUFSu)`=yAq!fRls9 zi*yfjjD$CJeH)UzlX{0}aFsTu-AZQxMMD;OOC=v=*O1jFybLo>&nPX^cdA)6ZTTx zee#g*e++^ZM#lLkG5h<4Kf%DQR9(XMf6HvpQm{iWHM%KC1viU^Zf9M~v!xWs2laJ4 zWrcyzRFQw!FdrDl)N1e=ZQj`DE{Cem2hgokbRUY?IspW`wLy4ccEB?^L69p3LM zvk8A+$ORrb*a1cGV$$F9OYT)$%3FZl>MFcW0>ugin1srEw40*Y6S_?P$>`}^umWAU z{DaWUuG&5>(ki!02+$m;du|mT9hx6sT6465`OME>QBBxr5~D;-`AV;|4gY zP83#1+CGLaH;iDR98yPrtuR;(QAYF38ZFNz%Oi0`-Vpzu0qlg@a^NK>Ny2UqV}Oqy zx>E51N5rtD5~U?jl1{@8rhmr9cfaM{ zU*r&cVD}|`xR_+(I&~Hx`gnY#e(w>pX~@

d9w<($4nlzk30(>}Xrj7e# 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¥>4c=9` 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 dsgqM=Cve8hvWa`Ga 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

>BUB&Hn`R^mxOd^v)hg)?T5jGh|K(Fx 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�Ox?j;k%pz@yfz(zR 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�zv9VJ7QOSbG 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

-Q?%;sH)!P=l%+J#68iP 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 qZGh4EFB Date: Tue, 21 Jul 2026 11:43:30 +0800 Subject: [PATCH 38/41] Fix code samples and behavior descriptions in documentation --- .../framework/api-development/auto-controllers.md | 2 +- docs/en/framework/fundamentals/localization.md | 4 ++-- docs/en/framework/fundamentals/validation.md | 5 +++-- docs/en/framework/infrastructure/interceptors.md | 4 +++- docs/en/framework/ui/angular/list-service.md | 4 ++-- docs/en/modules/ai-management/index.md | 13 ++++++++++--- docs/en/modules/chat.md | 3 +++ docs/en/modules/cms-kit/comments.md | 2 +- docs/en/modules/docs.md | 4 ++-- docs/en/modules/gdpr.md | 3 +++ docs/en/modules/language-management.md | 1 + docs/en/modules/text-template-management.md | 3 +++ docs/en/multi-lingual-entities.md | 8 ++++++++ 13 files changed, 42 insertions(+), 14 deletions(-) diff --git a/docs/en/framework/api-development/auto-controllers.md b/docs/en/framework/api-development/auto-controllers.md index 3202592056..df5da8ed12 100644 --- a/docs/en/framework/api-development/auto-controllers.md +++ b/docs/en/framework/api-development/auto-controllers.md @@ -93,7 +93,7 @@ Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}* * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. * If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). -The final controller name also removes suffixes configured in `AbpConventionalControllerOptions.IgnoredUrlSuffixesInControllerNames`. The default list contains `Integration`, so `PaymentIntegrationService` uses `payment` as its controller route name. You can replace the list when another suffix convention is required: +When the `UrlControllerNameNormalizer` option is not set, the final controller name also removes suffixes configured in `AbpConventionalControllerOptions.IgnoredUrlSuffixesInControllerNames` (a custom normalizer takes over the whole controller-name calculation and the ignored suffixes are not applied). The default list contains `Integration`, so `PaymentIntegrationService` uses `payment` as its controller route name. You can replace the list when another suffix convention is required: ```csharp Configure(options => diff --git a/docs/en/framework/fundamentals/localization.md b/docs/en/framework/fundamentals/localization.md index 2e0121c172..5d89973504 100644 --- a/docs/en/framework/fundamentals/localization.md +++ b/docs/en/framework/fundamentals/localization.md @@ -296,7 +296,7 @@ Contributors are order-sensitive. A lookup starts with the last registered contr Replace `IExternalLocalizationStore` when localization resources need to be discovered at runtime or loaded from an external system. The default `NullExternalLocalizationStore` does not provide any resources. -The string localizer factory first searches the resources registered in `AbpLocalizationOptions.Resources`. If it cannot find the requested resource name, it queries `IExternalLocalizationStore`. The store exposes synchronous and asynchronous methods for retrieving a resource by name, enumerating resource names and enumerating resources. +The string localizer factory first searches the resources registered in `AbpLocalizationOptions.Resources`. If it cannot find the requested resource name, it queries `IExternalLocalizationStore`. The store exposes synchronous and asynchronous methods for retrieving a resource by name, and asynchronous methods for enumerating resource names and resources. The factory caches the localizer after it resolves a resource name. Changing the resource object returned by the store does not make the factory resolve that name again. Use dynamic contributors when the localization values themselves need to change while the application is running. @@ -363,7 +363,7 @@ var serialized = localizableStringSerializer.Serialize( var localizableString = localizableStringSerializer.Deserialize(serialized!); ```` -The default serializer uses `L:,` for `LocalizableString` and `F:` for `FixedLocalizableString`. A value without a recognized prefix is deserialized as a `FixedLocalizableString`. An invalid `L:` value throws an `AbpException`. Serializing `null` returns `null`; serializing another `ILocalizableString` implementation throws an `AbpException`. +The default serializer uses `L:,` for `LocalizableString` and `F:` for `FixedLocalizableString`. A value without a recognized prefix is deserialized as a `FixedLocalizableString`; values too short to carry a prefix and a content (like the literal `L:`) are treated the same way. An `L:` value without a comma or with an empty key throws an `AbpException`. Serializing `null` returns `null`; serializing another `ILocalizableString` implementation throws an `AbpException`. ### Format Arguments diff --git a/docs/en/framework/fundamentals/validation.md b/docs/en/framework/fundamentals/validation.md index 2a05e60ad0..1c3cc0b265 100644 --- a/docs/en/framework/fundamentals/validation.md +++ b/docs/en/framework/fundamentals/validation.md @@ -142,16 +142,17 @@ public class InputClass } ```` -If a class has `[DisableValidation]`, add `[EnableValidation]` to a method to enable automatic method validation for that method: +If a class that is subject to automatic validation (it implements `IValidationEnabled`, like application services do) has `[DisableValidation]`, add `[EnableValidation]` to a method to re-enable automatic validation for that method (`[EnableValidation]` does not activate validation for a class that isn't intercepted at all): ````csharp [DisableValidation] -public class MyService +public class MyService : IValidationEnabled, ITransientDependency { [EnableValidation] public virtual Task UpdateAsync(MyInput input) { //... + return Task.CompletedTask; } } ```` diff --git a/docs/en/framework/infrastructure/interceptors.md b/docs/en/framework/infrastructure/interceptors.md index 25cf783108..b871ed6a1c 100644 --- a/docs/en/framework/infrastructure/interceptors.md +++ b/docs/en/framework/infrastructure/interceptors.md @@ -209,7 +209,9 @@ You can also disable ABP class interceptors for all registrations or for types s // Disable all class interceptors. context.Services.DisableAbpClassInterceptors(); -// Or disable them only for selected implementation types. +// Or disable them only for selected types. The predicate receives the +// exposed service type, which differs from the implementation type when +// a class is exposed through its interfaces or base classes. context.Services.DisableAbpClassInterceptors( new NamedTypeSelector( "MyHotPathServices", diff --git a/docs/en/framework/ui/angular/list-service.md b/docs/en/framework/ui/angular/list-service.md index bfa66cebb9..6c65ac6efc 100644 --- a/docs/en/framework/ui/angular/list-service.md +++ b/docs/en/framework/ui/angular/list-service.md @@ -16,10 +16,10 @@ `ListService` is **not provided in root**. The reason is, this way, it will clear any subscriptions on component destroy. You may use the optional `LIST_QUERY_DEBOUNCE_TIME` token to adjust the debounce behavior. ```ts -import { ListService } from '@abp/ng.core'; +import { LIST_QUERY_DEBOUNCE_TIME, ListService } from '@abp/ng.core'; import { BookDto } from '../models'; import { BookService } from '../services'; -import { inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; @Component({ /* class metadata here */ diff --git a/docs/en/modules/ai-management/index.md b/docs/en/modules/ai-management/index.md index f9e15a7c9a..9ce3caf865 100644 --- a/docs/en/modules/ai-management/index.md +++ b/docs/en/modules/ai-management/index.md @@ -1013,8 +1013,9 @@ chatComponent.off('messageSent', callbackFunction); In order to configure the application to use the AI Management module, you first need to import `provideAIManagementConfig` from `@volo/abp.ng.ai-management/config` to root application configuration. Then, you will need to append it to the `appConfig` array: -```js +```ts // app.config.ts +import { ApplicationConfig } from '@angular/core'; import { provideAIManagementConfig } from '@volo/abp.ng.ai-management/config'; export const appConfig: ApplicationConfig = { @@ -1027,8 +1028,10 @@ export const appConfig: ApplicationConfig = { The AI Management module should be imported and lazy-loaded in your routing array. It has a `createRoutes` function for configuration and is available from `@volo/abp.ng.ai-management`. -```js +```ts // app.routes.ts +import { Routes } from '@angular/router'; + const APP_ROUTES: Routes = [ // ... { @@ -1207,6 +1210,8 @@ dotnet add package OllamaSharp Create a factory class that implements `IChatClientFactory`: ```csharp +using System; +using System.Threading.Tasks; using Microsoft.Extensions.AI; using OllamaSharp; using Volo.AIManagement.Factory; @@ -1272,9 +1277,11 @@ The `ChatClientCreationConfiguration` object provides the following properties f Here's an example of implementing a factory for Azure OpenAI: -Install the `Azure.AI.OpenAI` NuGet package before adding this factory. +Install the `Azure.AI.OpenAI` and `Microsoft.Extensions.AI.OpenAI` NuGet packages before adding this factory (the `AsIChatClient()` extension method comes from `Microsoft.Extensions.AI.OpenAI`). ```csharp +using System; +using System.Threading.Tasks; using Azure.AI.OpenAI; using Azure; using Microsoft.Extensions.AI; diff --git a/docs/en/modules/chat.md b/docs/en/modules/chat.md index 8c2128d990..f3d30691a7 100644 --- a/docs/en/modules/chat.md +++ b/docs/en/modules/chat.md @@ -237,6 +237,7 @@ In order to configure the application to use the chat module, you first need to ```ts // app.config.ts +import { ApplicationConfig } from '@angular/core'; import { provideChatConfig } from '@volo/abp.ng.chat/config'; export const appConfig: ApplicationConfig = { @@ -252,6 +253,8 @@ The chat module should be imported and lazy-loaded in your routing array. It exp ```ts // app.routes.ts +import { Routes } from '@angular/router'; + const APP_ROUTES: Routes = [ // ... { diff --git a/docs/en/modules/cms-kit/comments.md b/docs/en/modules/cms-kit/comments.md index 3dade384a6..5d1b614570 100644 --- a/docs/en/modules/cms-kit/comments.md +++ b/docs/en/modules/cms-kit/comments.md @@ -45,7 +45,7 @@ Configure(options => - `EntityTypes`: List of defined entity types (`CommentEntityTypeDefinition`) in the comment system. - `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system. -- `AllowedExternalUrls`: The allowed external URLs for each entity type. When it is specified for an entity type, every external URL detected in a comment text is checked against the configured values, and the comment is rejected when a URL doesn't match any of them. +- `AllowedExternalUrls`: The allowed external URLs for each entity type. When it is specified for an entity type, a comment is rejected when it contains an external URL that doesn't include any of the configured values. The check is a case-insensitive substring comparison of the normalized URLs (`www.` prefixes and trailing slashes are ignored), not an exact origin match. `CommentEntityTypeDefinition` properties: diff --git a/docs/en/modules/docs.md b/docs/en/modules/docs.md index a9307733e7..a0a9b4b137 100644 --- a/docs/en/modules/docs.md +++ b/docs/en/modules/docs.md @@ -35,7 +35,7 @@ The Docs module supports Entity Framework Core and MongoDB. From the solution di abp add-module Volo.Docs ``` -For an Entity Framework Core solution, the command adds `builder.ConfigureDocs()` to the migrations `DbContext`, creates a migration and runs the database migrator. Use `--skip-db-migrations` when you want to manage that step yourself. MongoDB does not require an EF Core migration. +For an Entity Framework Core solution with a conventional layered structure, the command adds `builder.ConfigureDocs()` to the `DbContext` in the `.EntityFrameworkCore` (or `.DbMigrations`) project, creates a migration and runs the `DbMigrator` project. When the solution doesn't contain these projects (for example, a single-layer solution), configure the model and apply the migration yourself. Use `--skip-db-migrations` when you want to manage that step yourself. MongoDB does not require an EF Core migration. For a manual installation, add the Docs packages and module dependencies that correspond to each application layer. MVC/Razor Pages hosts also need the `@abp/docs` package. Keep every package on the same version as the rest of your ABP solution, then run `abp install-libs` in the web project. @@ -61,7 +61,7 @@ The main project fields are: * **MainWebsiteUrl**: Target of the project logo. * **LatestVersionBranchName**: Branch used for the latest documentation. -Deleting a project removes only the project record. Before deleting it, remove its cached documents through document administration, verify and remove its Elasticsearch entries when search is enabled, and delete every generated PDF through **Manage PDF Files** so the BLOB objects are deleted. The project delete operation does not perform these cleanup steps automatically. +Deleting a project deletes the project record and its PDF file metadata, but nothing else. Before deleting it, remove its cached documents through document administration, verify and remove its Elasticsearch entries when search is enabled, and delete every generated PDF through **Manage PDF Files** so the BLOB objects are deleted. The project delete operation does not perform these cleanup steps automatically. The public UI starts at `/documents`. You can change this route with `DocsUiOptions.RoutePrefix`, as shown in the [UI options](#ui-options) section. diff --git a/docs/en/modules/gdpr.md b/docs/en/modules/gdpr.md index 8689791963..e385d82442 100644 --- a/docs/en/modules/gdpr.md +++ b/docs/en/modules/gdpr.md @@ -192,6 +192,7 @@ To configure the application to use the GDPR module, import `provideGdprConfig` ```ts // app.config.ts +import { ApplicationConfig } from '@angular/core'; import { provideGdprConfig, withCookieConsentOptions, @@ -215,6 +216,8 @@ The GDPR module should be imported and lazy-loaded in your routing array. It exp ```ts // app.routes.ts +import { Routes } from '@angular/router'; + const APP_ROUTES: Routes = [ // other route definitions { diff --git a/docs/en/modules/language-management.md b/docs/en/modules/language-management.md index 1596afdbd1..22d249290d 100644 --- a/docs/en/modules/language-management.md +++ b/docs/en/modules/language-management.md @@ -235,6 +235,7 @@ The language management module should be imported and lazy-loaded in your routin ```ts // app.routes.ts +import { Routes } from '@angular/router'; const APP_ROUTES: Routes = [ // ... diff --git a/docs/en/modules/text-template-management.md b/docs/en/modules/text-template-management.md index dc9479593a..6941ec34af 100644 --- a/docs/en/modules/text-template-management.md +++ b/docs/en/modules/text-template-management.md @@ -220,6 +220,7 @@ To configure the application to use the text template management module, import ```ts // app.config.ts +import { ApplicationConfig } from '@angular/core'; import { provideTextTemplateManagementConfig } from '@volo/abp.ng.text-template-management/config'; export const appConfig: ApplicationConfig = { @@ -234,6 +235,8 @@ The text template management module should be imported and lazy-loaded in your r ```ts // app.routes.ts +import { Routes } from '@angular/router'; + const APP_ROUTES: Routes = [ // ... { diff --git a/docs/en/multi-lingual-entities.md b/docs/en/multi-lingual-entities.md index f3c8602269..43e93247f4 100644 --- a/docs/en/multi-lingual-entities.md +++ b/docs/en/multi-lingual-entities.md @@ -20,6 +20,9 @@ abp add-package Volo.Abp.MultiLingualObject Add `AbpMultiLingualObjectsModule` as a dependency of that module when the package is installed manually: ````csharp +using Volo.Abp.Modularity; +using Volo.Abp.MultiLingualObjects; + [DependsOn(typeof(AbpMultiLingualObjectsModule))] public class MyApplicationModule : AbpModule { @@ -31,6 +34,7 @@ public class MyApplicationModule : AbpModule Implement `IMultiLingualObject` on the object and `IObjectTranslation` on its translation type: ```csharp +using System.Collections.Generic; using Volo.Abp.MultiLingualObjects; public class Product : IMultiLingualObject @@ -54,6 +58,10 @@ public class ProductTranslation : IObjectTranslation Inject `IMultiLingualObjectManager` and call `GetTranslationAsync`: ```csharp +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiLingualObjects; + public class ProductService : ITransientDependency { From 44216a74a82e7a821aabc954bc2e5a8959842150 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 21 Jul 2026 12:06:59 +0800 Subject: [PATCH 39/41] Refine documentation wording for normalizer, validation and comment checks --- docs/en/framework/api-development/auto-controllers.md | 2 +- docs/en/framework/fundamentals/localization.md | 2 +- docs/en/framework/fundamentals/validation.md | 4 ++++ docs/en/framework/infrastructure/interceptors.md | 7 ++++--- docs/en/modules/cms-kit/comments.md | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/en/framework/api-development/auto-controllers.md b/docs/en/framework/api-development/auto-controllers.md index df5da8ed12..ee6d790e22 100644 --- a/docs/en/framework/api-development/auto-controllers.md +++ b/docs/en/framework/api-development/auto-controllers.md @@ -93,7 +93,7 @@ Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}* * Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. * If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). -When the `UrlControllerNameNormalizer` option is not set, the final controller name also removes suffixes configured in `AbpConventionalControllerOptions.IgnoredUrlSuffixesInControllerNames` (a custom normalizer takes over the whole controller-name calculation and the ignored suffixes are not applied). The default list contains `Integration`, so `PaymentIntegrationService` uses `payment` as its controller route name. You can replace the list when another suffix convention is required: +When the `UrlControllerNameNormalizer` option is not set, the final controller name also removes suffixes configured in `AbpConventionalControllerOptions.IgnoredUrlSuffixesInControllerNames` (a custom normalizer replaces this ignored-suffix step, so the ignored suffixes are not applied). The default list contains `Integration`, so `PaymentIntegrationService` uses `payment` as its controller route name. You can replace the list when another suffix convention is required: ```csharp Configure(options => diff --git a/docs/en/framework/fundamentals/localization.md b/docs/en/framework/fundamentals/localization.md index 5d89973504..4caed371d3 100644 --- a/docs/en/framework/fundamentals/localization.md +++ b/docs/en/framework/fundamentals/localization.md @@ -363,7 +363,7 @@ var serialized = localizableStringSerializer.Serialize( var localizableString = localizableStringSerializer.Deserialize(serialized!); ```` -The default serializer uses `L:,` for `LocalizableString` and `F:` for `FixedLocalizableString`. A value without a recognized prefix is deserialized as a `FixedLocalizableString`; values too short to carry a prefix and a content (like the literal `L:`) are treated the same way. An `L:` value without a comma or with an empty key throws an `AbpException`. Serializing `null` returns `null`; serializing another `ILocalizableString` implementation throws an `AbpException`. +The default serializer uses `L:,` for `LocalizableString` and `F:` for `FixedLocalizableString`. A value without a recognized prefix is deserialized as a `FixedLocalizableString`; values too short to carry both a prefix and content (like the literal `L:`) are treated the same way. An `L:` value without a comma or with an empty or whitespace-only key throws an `AbpException`. Serializing `null` returns `null`; serializing another `ILocalizableString` implementation throws an `AbpException`. ### Format Arguments diff --git a/docs/en/framework/fundamentals/validation.md b/docs/en/framework/fundamentals/validation.md index 1c3cc0b265..3bda1417da 100644 --- a/docs/en/framework/fundamentals/validation.md +++ b/docs/en/framework/fundamentals/validation.md @@ -145,6 +145,10 @@ public class InputClass If a class that is subject to automatic validation (it implements `IValidationEnabled`, like application services do) has `[DisableValidation]`, add `[EnableValidation]` to a method to re-enable automatic validation for that method (`[EnableValidation]` does not activate validation for a class that isn't intercepted at all): ````csharp +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Validation; + [DisableValidation] public class MyService : IValidationEnabled, ITransientDependency { diff --git a/docs/en/framework/infrastructure/interceptors.md b/docs/en/framework/infrastructure/interceptors.md index b871ed6a1c..c6cedf7859 100644 --- a/docs/en/framework/infrastructure/interceptors.md +++ b/docs/en/framework/infrastructure/interceptors.md @@ -209,9 +209,10 @@ You can also disable ABP class interceptors for all registrations or for types s // Disable all class interceptors. context.Services.DisableAbpClassInterceptors(); -// Or disable them only for selected types. The predicate receives the -// exposed service type, which differs from the implementation type when -// a class is exposed through its interfaces or base classes. +// Or disable them only for selected types. The predicate runs for class +// service registrations and receives the exposed class service type, +// which is the exposed base class rather than the implementation type +// when a class is exposed through a base class. context.Services.DisableAbpClassInterceptors( new NamedTypeSelector( "MyHotPathServices", diff --git a/docs/en/modules/cms-kit/comments.md b/docs/en/modules/cms-kit/comments.md index 5d1b614570..ca3a64d960 100644 --- a/docs/en/modules/cms-kit/comments.md +++ b/docs/en/modules/cms-kit/comments.md @@ -45,7 +45,7 @@ Configure(options => - `EntityTypes`: List of defined entity types (`CommentEntityTypeDefinition`) in the comment system. - `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system. -- `AllowedExternalUrls`: The allowed external URLs for each entity type. When it is specified for an entity type, a comment is rejected when it contains an external URL that doesn't include any of the configured values. The check is a case-insensitive substring comparison of the normalized URLs (`www.` prefixes and trailing slashes are ignored), not an exact origin match. +- `AllowedExternalUrls`: The allowed external URLs for each entity type. When it is specified for an entity type, every detected HTTP(S) URL in a comment text is checked, and the comment is rejected when a detected URL doesn't include any of the configured values. The comparison is a case-insensitive substring check on the normalized URLs, not an exact origin match. `CommentEntityTypeDefinition` properties: From d6ce99f36ebe64981529970517c8b4fd5408d5e5 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 21 Jul 2026 16:10:17 +0800 Subject: [PATCH 40/41] Document that Volo.Abp.MultiLingualObject is not published on NuGet --- docs/en/docs-nav.json | 4 ---- docs/en/multi-lingual-entities.md | 12 +++++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index d48bd85612..118e4bb264 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -550,10 +550,6 @@ "text": "Localization", "path": "framework/fundamentals/localization.md" }, - { - "text": "Multi-Lingual Objects", - "path": "multi-lingual-entities.md" - }, { "text": "Logging", "path": "framework/fundamentals/logging.md" diff --git a/docs/en/multi-lingual-entities.md b/docs/en/multi-lingual-entities.md index 43e93247f4..7028d8c6c4 100644 --- a/docs/en/multi-lingual-entities.md +++ b/docs/en/multi-lingual-entities.md @@ -7,17 +7,15 @@ # Multi-Lingual Objects -The `Volo.Abp.MultiLingualObject` package provides a contract and a selection service for objects that store one translation per language. Persistence mapping is application-specific; the package does not create a database relationship for the translations. +> The `Volo.Abp.MultiLingualObject` package is **not published on NuGet**. It was removed from the release pipeline in [#8271](https://github.com/abpframework/abp/pull/8271) and re-designing this feature is tracked in [#11698](https://github.com/abpframework/abp/issues/11698). The source code is still maintained in the framework repository, so you can copy the [Volo.Abp.MultiLingualObjects](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.MultiLingualObjects) project into your solution if you want to use the pattern described below. -## Installation +The `Volo.Abp.MultiLingualObjects` project provides a contract and a selection service for objects that store one translation per language. Persistence mapping is application-specific; it does not create a database relationship for the translations. -Install the package in the project that defines the consuming module: +## Getting the Code -```bash -abp add-package Volo.Abp.MultiLingualObject -``` +Copy the [Volo.Abp.MultiLingualObjects](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.MultiLingualObjects) project into your solution and reference it from the project that defines the consuming module. -Add `AbpMultiLingualObjectsModule` as a dependency of that module when the package is installed manually: +Add `AbpMultiLingualObjectsModule` as a dependency of that module: ````csharp using Volo.Abp.Modularity; From e8b967692eb98fe7ef7f3c5c3ad497e6ebcd4a26 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 21 Jul 2026 16:10:17 +0800 Subject: [PATCH 41/41] Mark TypeScript code blocks with ts in Angular menu document --- .../ui/angular/modifying-the-menu.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/en/framework/ui/angular/modifying-the-menu.md b/docs/en/framework/ui/angular/modifying-the-menu.md index 47cce9c49b..7f381997de 100644 --- a/docs/en/framework/ui/angular/modifying-the-menu.md +++ b/docs/en/framework/ui/angular/modifying-the-menu.md @@ -16,7 +16,7 @@ The `logoUrl` property in the environment variables is the url of the logo. You can add your logo to `src/assets` folder and set the `logoUrl` as shown below: -```js +```ts export const environment = { // other configurations application: { @@ -74,7 +74,7 @@ Add the following to your `src/styles.scss`: You can add routes to the menu by calling the `add` method of `RoutesService`. It is a singleton service, i.e. provided in root, so you can inject and use it immediately. -```js +```ts import { RoutesService, eLayoutType } from '@abp/ng.core'; import { Component, inject } from '@angular/core'; @@ -106,7 +106,7 @@ export class AppComponent { An alternative and probably cleaner way is to use a route provider. First create a provider: -```js +```ts // route.provider.ts import { RoutesService, eLayoutType } from '@abp/ng.core'; import { inject, provideAppInitializer } from '@angular/core'; @@ -142,7 +142,7 @@ function configureRoutes() { We can also define a group for navigation elements. It's an optional property - **Note:** It'll also include groups that were defined at the modules -```js +```ts // route.provider.ts import { RoutesService } from '@abp/ng.core'; import { inject } from '@angular/core'; @@ -168,7 +168,7 @@ function configureRoutes() { To get the route items as grouped we can use the `groupedVisible` (or Observable one `groupedVisible$`) getter methods - It returns `RouteGroup[]` if there is any group in the route tree, otherwise it returns `undefined` -```js +```ts import { ABP, RoutesService, RouteGroup } from "@abp/ng.core"; import { Component, inject } from "@angular/core"; import { Observable } from "rxjs"; @@ -186,7 +186,7 @@ export class AppComponent { ...and then in app.config.ts... - The `groupedVisible` method will return the `Others` group for ungrouped items, the default key is `AbpUi::OthersGroup`, we can change this `key` via the `OTHERS_GROUP` injection token -```js +```ts import { OTHERS_GROUP } from '@abp/ng.core'; import { APP_ROUTE_PROVIDER } from './route.provider'; @@ -240,7 +240,7 @@ You can define your routes by adding `routes` as a child property to `data` prop You can add the `routes` property like below: -```js +```ts { path: 'your-path', data: { @@ -264,7 +264,7 @@ You can add the `routes` property like below: Alternatively, you can do this: -```js +```ts { path: 'your-path', data: { @@ -298,7 +298,7 @@ After adding the `routes` property as described above, the navigation menu looks The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children. Also you can use `removeByParam` method to delete the routes with given properties. -```js +```ts // this.routes is instance of RoutesService // eThemeSharedRouteNames enum can be imported from @abp/ng.theme.shared @@ -344,7 +344,7 @@ After the operations above, the new menu looks like below: You can add elements to the right part of the menu by calling the `addItems` method of `NavItemsService`. It is a singleton service, i.e. provided in root, so you can inject and use it immediately. -```js +```ts import { NavItemsService } from '@abp/ng.theme.shared'; import { Component, inject } from '@angular/core'; @@ -388,7 +388,7 @@ This inserts a search input and a sign out icon to the menu. The final UI looks The `patchItem` method of `NavItemsService` finds an element by its `id` property and replaces its configuration with the new configuration passed as the second parameter. Similarly, `removeItem` method finds an element and removes it. -```js +```ts export class AppComponent { private navItems = inject(NavItemsService);