Browse Source

Improve framework documentation coverage

pull/25826/head
maliming 2 weeks ago
parent
commit
eca1e7e8e7
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 32
      docs/en/docs-nav.json
  2. 12
      docs/en/framework/api-development/auto-controllers.md
  3. 40
      docs/en/framework/api-development/dynamic-csharp-clients.md
  4. 101
      docs/en/framework/api-development/identitymodel-clients.md
  5. 15
      docs/en/framework/api-development/swagger.md
  6. 5
      docs/en/framework/architecture/domain-driven-design/repositories.md
  7. 10
      docs/en/framework/architecture/domain-driven-design/unit-of-work.md
  8. 30
      docs/en/framework/architecture/modularity/basics.md
  9. 27
      docs/en/framework/architecture/modularity/extending/module-entity-extensions.md
  10. 2
      docs/en/framework/data/entity-framework-core/index.md
  11. 77
      docs/en/framework/data/memorydb/index.md
  12. 25
      docs/en/framework/data/mongodb/index.md
  13. 5
      docs/en/framework/fundamentals/application-startup.md
  14. 54
      docs/en/framework/fundamentals/caching.md
  15. 2
      docs/en/framework/fundamentals/dependency-injection.md
  16. 14
      docs/en/framework/fundamentals/exception-handling.md
  17. 159
      docs/en/framework/fundamentals/localization.md
  18. 17
      docs/en/framework/fundamentals/logging.md
  19. 53
      docs/en/framework/fundamentals/options.md
  20. 27
      docs/en/framework/fundamentals/validation.md
  21. 4
      docs/en/framework/infrastructure/artificial-intelligence/microsoft-agent-framework.md
  22. 4
      docs/en/framework/infrastructure/audit-logging.md
  23. 4
      docs/en/framework/infrastructure/background-jobs/index.md
  24. 10
      docs/en/framework/infrastructure/background-workers/hangfire.md
  25. 1
      docs/en/framework/infrastructure/csrf-anti-forgery.md
  26. 27
      docs/en/framework/infrastructure/current-user.md
  27. 5
      docs/en/framework/infrastructure/event-bus/distributed/azure.md
  28. 3
      docs/en/framework/infrastructure/event-bus/distributed/index.md
  29. 5
      docs/en/framework/infrastructure/event-bus/distributed/rabbitmq.md
  30. 2
      docs/en/framework/infrastructure/features.md
  31. 17
      docs/en/framework/infrastructure/interceptors.md
  32. 29
      docs/en/framework/infrastructure/json.md
  33. 4
      docs/en/framework/infrastructure/mail-kit.md
  34. 13
      docs/en/framework/infrastructure/object-to-object-mapping.md
  35. 9
      docs/en/framework/infrastructure/settings.md
  36. 90
      docs/en/framework/infrastructure/sms-sending.md
  37. 8
      docs/en/framework/infrastructure/string-encryption.md
  38. 17
      docs/en/framework/infrastructure/text-templating/index.md
  39. 34
      docs/en/framework/infrastructure/virtual-file-system.md
  40. 11
      docs/en/framework/real-time/signalr.md
  41. 88
      docs/en/framework/ui/angular/commercial-ui.md
  42. 4
      docs/en/framework/ui/angular/component-replacement.md
  43. 100
      docs/en/framework/ui/angular/datetime-format-pipe.md
  44. 42
      docs/en/framework/ui/angular/http-error-handling.md
  45. 4
      docs/en/framework/ui/angular/http-requests.md
  46. 6
      docs/en/framework/ui/angular/list-service.md
  47. 76
      docs/en/framework/ui/angular/localization.md
  48. 94
      docs/en/framework/ui/angular/lookup-search-component.md
  49. 7
      docs/en/framework/ui/angular/modifying-the-menu.md
  50. 59
      docs/en/framework/ui/angular/oauth-module.md
  51. 13
      docs/en/framework/ui/angular/permission-management.md
  52. 31
      docs/en/framework/ui/angular/ssr-configuration.md
  53. 29
      docs/en/framework/ui/angular/testing.md
  54. 96
      docs/en/framework/ui/angular/title-strategy.md
  55. 138
      docs/en/framework/ui/angular/tree-component.md
  56. 16
      docs/en/framework/ui/blazor/authentication.md
  57. 34
      docs/en/framework/ui/blazor/global-scripts-styles.md
  58. 4
      docs/en/framework/ui/maui/index.md
  59. 57
      docs/en/framework/ui/mvc-razor-pages/javascript-api/clock.md
  60. 3
      docs/en/framework/ui/mvc-razor-pages/javascript-api/index.md
  61. 14
      docs/en/framework/ui/mvc-razor-pages/overall.md
  62. 22
      docs/en/framework/ui/react/authorization.md
  63. 36
      docs/en/framework/ui/react/permission-management.md
  64. 14
      docs/en/modules/identity/ldap.md
  65. 8
      docs/en/modules/virtual-file-explorer.md
  66. 101
      docs/en/multi-lingual-entities.md

32
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"

12
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<AbpConventionalControllerOptions>(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<AbpAspNetCoreMvcOptions>(options =>
````
## See Also
* [Video tutorial](https://abp.io/video-courses/essentials/auto-api-controllers)
* [Video tutorial](https://abp.io/video-courses/essentials/auto-api-controllers)

40
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<T>` 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<AbpHttpClientOptions>(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<T>`, `IObjectToFormData<T>` or `IObjectToPath<T>` when a type requires custom serialization, register the implementation in dependency injection, and map the value type to the converter:
````csharp
context.Services.AddTransient<MyFilterToQueryString>();
context.Services.AddTransient<MyUploadMetadataToFormData>();
context.Services.AddTransient<MyStrongIdToPath>();
Configure<AbpHttpClientProxyingOptions>(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.

101
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. `<client-name>.<tenant-id>`
2. `<client-name>.<tenant-name>`
3. `<client-name>`
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<IdentityModelHttpRequestMessageOptions>(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.

15
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.

5
docs/en/framework/architecture/domain-driven-design/repositories.md

@ -249,7 +249,7 @@ public virtual async Task<List<PersonDto>> 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<T>`) 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<Tentity, TKey>` provides the following methods:
- `GetCountAsync()`

10
docs/en/framework/architecture/domain-driven-design/unit-of-work.md

@ -59,7 +59,7 @@ Configure<AbpUnitOfWorkDefaultOptions>(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<T>`) 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<T>`) 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

30
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<AbpModuleLifecycleOptions>(options =>
{
options.Contributors.Add<MyModuleLifecycleContributor>();
});
````
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.

27
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:

2
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()

77
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<Type> GetEntityTypes()
{
return EntityTypes;
}
}
````
Register the context in the `ConfigureServices` method of your module:
````csharp
context.Services.AddMemoryDbContext<MyMemoryDbContext>(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<Utf8JsonMemoryDbSerializerOptions>(options =>
{
options.JsonSerializerOptions.Converters.Add(
new MyEntityJsonConverter()
);
});
````

25
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<AbpMongoDbOptions>(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<AbpMongoDbContextOptions>(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.

5
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 `<FileName>.json` first and then the optional `<FileName>.secrets.json` file. When `EnvironmentName` is set, it loads `<FileName>.<EnvironmentName>.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.

54
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<TCacheItem>` for string keys or `IHybridCache<TCacheItem, TCacheKey>` 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<BookCacheItem, Guid> _cache;
public BookService(IHybridCache<BookCacheItem, Guid> cache)
{
_cache = cache;
}
public Task<BookCacheItem?> GetAsync(Guid bookId)
{
return _cache.GetOrCreateAsync(
bookId,
() => LoadBookAsync(bookId)
);
}
private Task<BookCacheItem> 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<TCacheItem>()` can set the corresponding default for a cache item type.
## Configuration
### AbpDistributedCacheOptions
@ -233,7 +285,7 @@ Configure<AbpDistributedCacheOptions>(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

2
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

14
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<AbpExceptionHandlingOptions>(options =>
{
options.SendExceptionDataToClientTypes.Add(typeof(MyClientVisibleException));
options.ExcludeExceptionFromLoggerSelectors.Add(
exception => exception is MyExpectedException
);
});
````
## See Also

159
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<TResource>`. 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<AbpLocalizationOptions>(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<AbpLocalizationOptions>(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<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<TestResource>("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<AbpLocalizationOptions>(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<AbpLocalizationOptions>(options =>
{
options.TryToGetFromBaseCulture = false;
options.TryToGetFromDefaultCulture = false;
});
````
### Global Resource Contributors
`AbpLocalizationOptions.GlobalContributors` adds an `ILocalizationResourceContributor` implementation to every localization resource:
````csharp
Configure<AbpLocalizationOptions>(options =>
{
options.GlobalContributors.Add<MyLocalizationResourceContributor>();
});
````
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<TestResource>("HelloWorld")
);
var localizableString = localizableStringSerializer.Deserialize(serialized!);
````
The default serializer uses `L:<resource-name>,<key>` for `LocalizableString` and `F:<value>` 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<T>` in razor views/pages;
@ -331,6 +461,29 @@ Configure<AbpLocalizationOptions>(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<AbpLocalizationOptions>(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.

17
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<AbpAspNetCoreSerilogOptions>(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`.

53
docs/en/framework/fundamentals/options.md

@ -123,3 +123,56 @@ public override void ConfigureServices(ServiceConfigurationContext context)
}
````
## Dynamic Options
Standard options are created synchronously. `AbpDynamicOptionsManager<TOptions>` 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<MyOptions>
{
private readonly ISettingProvider _settingProvider;
public MyDynamicOptionsManager(
IOptionsFactory<MyOptions> factory,
ISettingProvider settingProvider)
: base(factory)
{
_settingProvider = settingProvider;
}
protected override async Task OverrideOptionsAsync(
string name,
MyOptions options)
{
options.Value1 = await _settingProvider.GetAsync<int>("MyOptions.Value1");
}
}
````
Register the manager for the option type:
````csharp
context.Services.AddAbpDynamicOptions<MyOptions, MyDynamicOptionsManager>();
````
This replaces `IOptions<MyOptions>` and `IOptionsSnapshot<MyOptions>` with the scoped dynamic manager. Call the `IOptions<T>.SetAsync` extension before reading the value when you need to apply the asynchronous override:
````csharp
public class MyService : ITransientDependency
{
private readonly IOptions<MyOptions> _options;
public MyService(IOptions<MyOptions> options)
{
_options = options;
}
public async Task<int> GetValueAsync()
{
await _options.SetAsync();
return _options.Value.Value1;
}
}
````

27
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<AbpValidationOptions>(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.

4
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<TWorkSpace>.ChatClient` is `null` only when neither the specified workspace nor the default workspace has a configured Chat Client. Resolving `IChatClient<TWorkSpace>` requires one of them to be configured.
`IChatClient<TWorkSpace>` or `IChatClientAccessor<TWorkSpace>` 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/)
- [AI Samples for .NET](https://learn.microsoft.com/en-us/samples/dotnet/ai-samples/ai-samples/)

4
docs/en/framework/infrastructure/audit-logging.md

@ -44,10 +44,10 @@ Configure<AbpAuditingOptions>(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.

4
docs/en/framework/infrastructure/background-jobs/index.md

@ -150,7 +150,7 @@ Configure<AbpBackgroundJobOptions>(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)
* [Background Workers](../background-workers)

10
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<AbpHangfirePeriodicBackgroundWorkerAdapterOptions>(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).

1
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.

27
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.

5
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**

3
docs/en/framework/infrastructure/event-bus/distributed/index.md

@ -648,12 +648,15 @@ Configure<AbpEventBusBoxesOptions>(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

5
docs/en/framework/infrastructure/event-bus/distributed/rabbitmq.md

@ -148,17 +148,20 @@ Configure<AbpRabbitMqOptions>(options =>
});
````
**Example: Configure the client, exchange names and prefetchCount**
**Example: Configure the client, exchange name, exchange type and prefetch count**
````csharp
Configure<AbpRabbitMqEventBusOptions>(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

2
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<T>`) 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.

17
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

29
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>(T? obj);
T? Deserialize<T>(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<T>`. 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<Product>, 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<Product>(bytes);
}
}
```
## Configuration
### AbpJsonOptions

4
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)
* [Email sending](./emailing.md)

13
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<AbpAutoMapperOptions>(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

9
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<AbpSettingOptions>(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.

90
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<string, string>`): Key-value pairs to pass custom arguments
- `Properties` (`IDictionary<string, object>`): 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).

8
docs/en/framework/infrastructure/string-encryption.md

@ -112,8 +112,8 @@ Configure<AbpStringEncryptionOptions>(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<AbpStringEncryptionOptions>(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`

17
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<AbpTextTemplatingOptions>(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).
* [Virtual File System](../../infrastructure/virtual-file-system.md).

34
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)
* [Video tutorial](https://abp.io/video-courses/essentials/virtual-file-system)

11
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<AbpSignalROptions>(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.

88
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
<abp-date-range-picker
[(ngModel)]="dateRange"
startDateProp="startDate"
endDateProp="endDate"
labelText="Reports::DateRange"
/>
```
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();
```

4
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:

100
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
<span>{{today | date 'dd/mm/yy'}}</span>
<span>{{ today | date:'dd/MM/yy' }}</span>
```
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
<span> {{today | shortDate }}</span>
<span>{{ today | shortDate }}</span>
```
### `shortTime`
## ShortTime Pipe
```html
<span>{{ today | shortTime }}</span>
```
### `shortDateTime`
```html
<span> {{today | shortTime }}</span>
<span>{{ today | shortDateTime }}</span>
```
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
<span> {{today | shortDateTime }}</span>
<span>{{ order.creationTime | abpUtcToLocal:'datetime' }}</span>
```
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: `<button type="button" (click)="selectTimezone()">Use Istanbul time</button>`,
})
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. |
}%}

42
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.

4
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) {

6
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.
}

76
docs/en/framework/ui/angular/localization.md

@ -72,6 +72,27 @@ Then, we can use this key like this:
<!-- Output: Showing 20 to 30 of 50 entries -->
```
### 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: `
<h1>{%{{{ 'MyProjectName::Greeting' | abpAsyncLocalization | async }}}%}</h1>
`,
})
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:
<div>{%{{{ 'MyProjectName::HomePage' | abpLocalization }}}%}</div>
```
> **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

94
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<BookLookupItem> = 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
<abp-lookup-search
label="Books::Book"
placeholder="Books::SearchBooks"
[searchFn]="searchBooks"
[minSearchLength]="2"
[selectedValue]="selectedBookId()"
(selectedValueChange)="selectedBookId.set($event)"
[displayValue]="selectedBookName()"
(displayValueChange)="selectedBookName.set($event)"
(itemSelected)="onBookSelected($event)"
>
<ng-template #itemTemplate let-book>
<strong>{{ book.displayName }}</strong>
<small>{{ book.authorName }}</small>
</ng-template>
</abp-lookup-search>
```
}%}
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
<ng-template #noResultsTemplate>
<div class="list-group-item text-muted">No matching books</div>
</ng-template>
```

7
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'

59
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.

13
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);

31
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

29
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

96
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),
),
],
};
```

138
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<string[]>([]);
readonly selectedCategory = signal<Category | null>(null);
readonly allowDrop = () => of(true);
handleDrop(event: DropEvent) {
console.log(event.dragNode?.key);
}
edit(key: string) {
console.log(key);
}
}
```
```html
<abp-tree
[nodes]="nodes()"
[expandedKeys]="expandedKeys()"
[selectedNode]="selectedCategory()"
[draggable]="true"
[beforeDrop]="allowDrop"
(expandedKeysChange)="expandedKeys.set($event)"
(selectedNodeChange)="selectedCategory.set($event)"
(dropOver)="handleDrop($event)"
>
<ng-template #menu let-node>
<button type="button" class="dropdown-item" (click)="edit(node.key)">
Edit
</button>
</ng-template>
</abp-tree>
```
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
<abp-tree [nodes]="nodes()">
<ng-template abpTreeNodeTemplate let-node>
<strong>{{ node.title }}</strong>
</ng-template>
<ng-template abpTreeExpandedIconTemplate let-node>
<span>{{ node.isExpanded ? '−' : '+' }}</span>
</ng-template>
</abp-tree>
```
}%}
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<T>` 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,
},
];
```

16
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}}
{{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<AbpAuthenticationOptions>(options =>
{
options.LoginUrl = "authentication/sign-in";
options.LogoutUrl = "authentication/sign-out";
});
````

34
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<AbpBundlingOptions>(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
<link href="app-global.css" rel="stylesheet" />
<script src="app-global.js"></script>
```
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)

4
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.

57
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.

3
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)
* [UI Notification](notify.md)

14
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).
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<AbpErrorPageOptions>(options =>
{
options.ErrorViewUrls["404"] = "~/Views/Error/NotFound.cshtml";
options.ErrorViewUrls["500"] = "~/Views/Error/InternalError.cshtml";
});
````

22
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:

36
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:

14
docs/en/modules/identity/ldap.md

@ -48,17 +48,17 @@ public class VoloOpenLdapManager : OpenLdapManager
}
protected override async Task<string> NormalizeUserNameAsync(string userName)
protected override Task<string> NormalizeUserNameAsync(string userName)
{
// or "userName@domain
// or "userName@domain"
// await LdapSettingProvider.GetDomainAsync()
return Task.FromResult($"Volo\\{userName}");
}
protected override Task<string> 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<string> GetUserEmailAsync(LdapEntry ldapEntry)
@ -96,10 +96,10 @@ public class VoloLdapExternalLoginProvider : LdapExternalLoginProvider
}
protected override async Task<string> NormalizeUserNameAsync(string userName)
protected override Task<string> 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)

8
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;
});
}
```
```

101
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<TTranslation>` on the object and `IObjectTranslation` on its translation type:
```csharp
using Volo.Abp.MultiLingualObjects;
public class Product : IMultiLingualObject<ProductTranslation>
{
public ICollection<ProductTranslation> Translations { get; set; } =
new List<ProductTranslation>();
}
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<ProductTranslation?> GetTranslationAsync(Product product)
{
return _multiLingualObjectManager
.GetTranslationAsync<Product, ProductTranslation>(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<Product, ProductTranslation>(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.

Loading…
Cancel
Save