<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">WeAreDevelopers World Congress 2026 has come to an end, and we'd like to thank everyone who stopped by the ABP booth in Berlin!</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">WeAreDevelopers World Congress 2026 has come to an end, and we'd like to thank everyone who stopped by the ABP booth in Berlin!</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">We had the opportunity to meet developers, architects, engineering leaders, and technology enthusiasts from around the world. It was a pleasure connecting with so many members of the developer community, hearing about the projects you're building, and discussing the challenges and opportunities shaping modern software development.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">We had the opportunity to meet developers, architects, engineering leaders, and technology enthusiasts from around the world. It was a pleasure connecting with so many members of the developer community, hearing about the projects you're building, and discussing the challenges and opportunities shaping modern software development.</span></span></span></span>


<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">Throughout the event, our team showcased the latest developments across the ABP ecosystem, including ABP Framework, ABP Studio, and our AI-powered development capabilities.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">Throughout the event, our team showcased the latest developments across the ABP ecosystem, including ABP Framework, ABP Studio, and our AI-powered development capabilities.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">We had countless conversations about modular application development, clean architecture, microservices, AI-assisted development, and how teams can build enterprise applications faster while maintaining long-term quality and maintainability.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">We had countless conversations about modular application development, clean architecture, microservices, AI-assisted development, and how teams can build enterprise applications faster while maintaining long-term quality and maintainability.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">Thank you to everyone who shared feedback, asked questions, and explored how ABP can support your development journey.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">Thank you to everyone who shared feedback, asked questions, and explored how ABP can support your development journey.</span></span></span></span>


In addition to connecting with attendees at our booth, we were proud to see our Co-founder, **Halil İbrahim Kalkan**, speak at WeAreDevelopers World Congress 2026.
@ -28,19 +28,19 @@ It was a great opportunity to share the engineering practices and ideas behind A

## **<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 17pt;">More Than Just a Conference</span></span></span></span>**
## **<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Lexend, sans-serif;"><spanstyle="font-size: 17pt;">More Than Just a Conference</span></span></span></span>**
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">WeAreDevelopers World Congress wasn't only about technical sessions. The event also featured interactive experiences, including a lively arcade gaming area, creating plenty of opportunities for attendees to relax, connect, and enjoy the conference between talks.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">WeAreDevelopers World Congress wasn't only about technical sessions. The event also featured interactive experiences, including a lively arcade gaming area, creating plenty of opportunities for attendees to relax, connect, and enjoy the conference between talks.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">This is the approach I'd recommend. It keeps the ABP story focused while giving you a natural place to include photos or videos of the arcade area.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">This is the approach I'd recommend. It keeps the ABP story focused while giving you a natural place to include photos or videos of the arcade area.</span></span></span></span>
[](https://youtu.be/K2WzoMfO76k)

<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">One of the best parts of WeAreDevelopers World Congress is bringing together developers, architects, engineering leaders, and technology experts from around the world. The conference featured inspiring keynotes and technical sessions covering AI, software architecture, cloud, developer productivity, and many other topics that are shaping the future of software development.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">One of the best parts of WeAreDevelopers World Congress is bringing together developers, architects, engineering leaders, and technology experts from around the world. The conference featured inspiring keynotes and technical sessions covering AI, software architecture, cloud, developer productivity, and many other topics that are shaping the future of software development.</span></span></span></span>

@ -48,13 +48,13 @@ It was a great opportunity to share the engineering practices and ideas behind A

<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">A big thank you to the WeAreDevelopers team for organizing another fantastic event and to everyone who visited us at Hall A, Booth A-41.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">A big thank you to the WeAreDevelopers team for organizing another fantastic event and to everyone who visited us at Hall A, Booth A-41.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">If we didn't get the chance to meet in Berlin, you can always explore ABP online, join our community, or reach out to us with your questions and feedback.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">If we didn't get the chance to meet in Berlin, you can always explore ABP online, join our community, or reach out to us with your questions and feedback.</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Arial, sans-serif;"><spanstyle="font-size: 11pt;">We appreciate everyone who made WeAreDevelopers World Congress 2026 such a memorable experience, and we look forward to seeing you again at future events!</span></span></span></span>
<spanstyle="background-color: transparent;"><spanstyle="color: rgb(0, 0, 0);"><spanstyle="font-family: Poppins, sans-serif;"><spanstyle="font-size: 11pt;">We appreciate everyone who made WeAreDevelopers World Congress 2026 such a memorable experience, and we look forward to seeing you again at future events!</span></span></span></span>

@ -95,6 +95,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').
When the `UrlControllerNameNormalizer` option is not set, the final controller name also removes suffixes configured in `AbpConventionalControllerOptions.IgnoredUrlSuffixesInControllerNames` (a custom normalizer replaces this ignored-suffix step, so the ignored suffixes are not applied). The default list contains `Integration`, so `PaymentIntegrationService` uses `payment` as its controller route name. You can replace the list when another suffix convention is required:
`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.
@ -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:
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.
"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:
@ -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()` overloads are obsolete. Use `WithDetailsAsync()` for new code.
Whereas the `IReadOnlyBasicRepository<Tentity, TKey>` provides the following methods:
* `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.
@ -145,6 +145,43 @@ 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. Each phase has a synchronous and an asynchronous method; the application calls one of them depending on whether it is initialized synchronously or asynchronously, so override both to cover the two startup paths:
````csharp
public class MyModuleLifecycleContributor : ModuleLifecycleContributorBase
{
public override Task InitializeAsync(
ApplicationInitializationContext context,
IAbpModule module)
{
// Run initialization logic for the current module.
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.
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:
@ -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:
"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:
`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:
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:
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.
@ -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, user secrets are added from `UserSecretsId` when it is set; otherwise from `UserSecretsAssembly`.
* `BasePath` changes the configuration file base path. The current directory is used by default.
* `EnvironmentVariablesPrefix` filters environment variables, and `CommandLineArgs` adds command-line configuration after environment variables.
* `Environment`: Environment name for the application.
* `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.
@ -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:
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.
* `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.
@ -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.
@ -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.
@ -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.
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:
* 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:
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, and asynchronous methods for enumerating resource names and resources.
The factory caches the localizer after it resolves a resource name. Changing the resource object returned by the store does not make the factory resolve that name again. Use dynamic contributors when the localization values themselves need to change while the application is running.
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:
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(
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`; values too short to carry both a prefix and content (like the literal `L:`) are treated the same way. An `L:` value without a comma or with an empty or whitespace-only key throws an `AbpException`. Serializing `null` returns `null`; serializing another `ILocalizableString` implementation throws an `AbpException`.
### Format Arguments
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.
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.
@ -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:
@ -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>
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:
> 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,25 @@ public class InputClass
}
````
If a class that is subject to automatic validation (it implements `IValidationEnabled`, like application services do) has `[DisableValidation]`, add `[EnableValidation]` to a method to re-enable automatic validation for that method (`[EnableValidation]` does not activate validation for a class that isn't intercepted at all):
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation;
[DisableValidation]
public class MyService : IValidationEnabled, ITransientDependency
{
[EnableValidation]
public virtual Task UpdateAsync(MyInput input)
{
//...
return Task.CompletedTask;
}
}
````
### 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 +199,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 the default data annotation contributor from descending into the properties of matching values during recursive validation. The data annotations on the matching value itself are still validated. Derived and implementing types are also matched.
`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.
@ -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)
* `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`): Safe HTTP methods (GET, HEAD and QUERY) should not make any change in the database normally and the audit log system doesn't save audit log objects for these requests. Set this to `true` to enable it also for the safe 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.
@ -311,6 +311,8 @@ An **audit log object** is created for each **web request** by default. An audit
* **Exception**: An audit log object may contain zero or more exception. In this way, you can get a report of the failed requests.
* **Comment**: An arbitrary string value to add custom messages to the audit log entry. An audit log object may contain zero or more comments.
> When the [Audit Logging Module](../../modules/audit-logging.md) persists exceptions, it uses `AbpExceptionHandlingOptions` to convert them. `SendExceptionsDetailsToClients`, `SendStackTraceToClients` and `SendExceptionDataToClientTypes` therefore also control the exception details stored in audit logs, not only the details sent to clients. Review these options when audit logs may contain sensitive information. See the [Exception Handling](../fundamentals/exception-handling.md#abpexceptionhandlingoptions) document for configuration details.
In addition to the standard properties explained above, `AuditLogInfo`, `AuditLogActionInfo` and `EntityChangeInfo` objects implement the `IHasExtraProperties` interface, so you can add custom properties to these objects.
@ -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 (a specified queue name is prefixed with `AbpHangfireOptions.DefaultQueuePrefix`, which is empty by default). You can configure both values globally for adapted periodic workers:
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).
BLOB Storing Database Storage Provider can store BLOBs in a relational or non-relational database.
The database provider reads the complete input stream into memory before saving a BLOB and returns BLOB content from an in-memory buffer. Database and driver value-size limits still apply. Consider an external object-storage provider for very large BLOBs or workloads that require end-to-end streaming.
There are two database providers implemented;
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) package implements for [EF Core](../../data/entity-framework-core), so it can store BLOBs in [any DBMS supported](https://docs.microsoft.com/en-us/ef/core/providers/) by the EF Core.
@ -32,16 +34,16 @@ This command adds all the NuGet packages to corresponding layers of your solutio
### Manual Installation
Here, all the NuGet packages defined by this provider;
The following NuGet packages are defined by this provider:
You can only install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference) since they depends on the other packages.
You only need to install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference), since they depend on the other packages.
After installation, add `DepenedsOn` attribute to your related [module](../../architecture/modularity/basics.md). Here, the list of module classes defined by the related NuGet packages listed above:
After installation, add the `[DependsOn]` attribute to your related [module](../../architecture/modularity/basics.md). Here is the list of module classes defined by the related NuGet packages listed above:
* `BlobStoringDatabaseDomainModule`
* `BlobStoringDatabaseDomainSharedModule`
@ -52,6 +54,17 @@ Whenever you add a NuGet package to a project, also add the module class depende
If you are using EF Core, you also need to configure your **Migration DbContext** to add BLOB storage tables to your database schema. Call `builder.ConfigureBlobStoring()` extension method inside the `OnModelCreating` method to include mappings to your DbContext. Then you can use the standard `Add-Migration` and `Update-Database` [commands](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create necessary tables in your database.
If you are using MongoDB and combine module collections in a custom `AbpMongoDbContext`, call `modelBuilder.ConfigureBlobStoring()` inside the `CreateModel` method:
@ -60,9 +73,17 @@ If you will use your `Default` connection string, you don't need to any addition
If you want to use a separate database for BLOB storage, use the `AbpBlobStoring` as the [connection string](../../fundamentals/connection-strings.md) name in your configuration file (`appsettings.json`). In this case, also read the [EF Core Migrations](../../data/entity-framework-core/migrations.md) document to learn how to create and use a different database for a desired module.
### Common Database Properties
The `AbpBlobStoringDatabaseDbProperties` class defines the following database settings. Set `DbTablePrefix` and `DbSchema` at application startup, before the database model is created:
* `DbTablePrefix` (`Abp` by default) is the prefix for table and collection names.
* `DbSchema` (`null` by default) is the database schema used by EF Core. MongoDB does not use this property.
* `ConnectionStringName` (`AbpBlobStoring`) is the connection-string name used by both database providers.
### Configuring the Containers
If you are using only the database storage provider, you don't need to manually configure it, since it is automatically done. If you are using multiple storage providers, you may want to configure it.
The database module selects `DatabaseBlobProvider` for the default container when no provider has already been selected. It does not replace an explicitly configured provider. If you use multiple storage providers, configure the database provider for the required default, typed or named containers.
Configuration is done in the `ConfigureServices` method of your [module](../../architecture/modularity/basics.md) class, as explained in the [BLOB Storing document](../blob-storing).
It is expected to use the [BLOB Storing services](../blob-storing) to use the BLOB storing system. However, if you want to work on the database tables/entities, you can use the following information.
### Database Tables and Collections
With the default `Abp` prefix, EF Core maps the entities to the `AbpBlobContainers` and `AbpBlobs` tables. MongoDB uses collections with the same names. Changing `DbTablePrefix` changes both table and collection names, while `DbSchema` only changes the EF Core schema.
### Entities
Entities defined for this module:
* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database.
* `DatabaseBlob` (aggregate root) represents a BLOB in the database.
* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database. It stores the tenant identifier and the container name. Persisted container names have a maximum length of 128 characters.
* `DatabaseBlob` (aggregate root) represents a BLOB in the database. It stores the container identifier, tenant identifier, BLOB name and content. Persisted BLOB names have a maximum length of 256 characters.
The provider creates a container record lazily when the first BLOB is saved to that container. Read, existence-check and delete operations do not create container records, and deleting the last BLOB does not delete its container record.
See the [entities document](../../architecture/domain-driven-design/entities.md) to learn what is an entity and aggregate root.
@ -102,4 +129,4 @@ You can also use `IRepository<DatabaseBlobContainer, Guid>` and `IRepository<Dat
### Other Services
* `DatabaseBlobProvider` is the main service that implements the database BLOB storage provider, if you want to override/replace it via [dependency injection](../../fundamentals/dependency-injection.md) (don't replace `IBlobProvider` interface, but replace `DatabaseBlobProvider` class).
* `DatabaseBlobProvider` is the main service that implements the database BLOB storage provider, if you want to override/replace it via [dependency injection](../../fundamentals/dependency-injection.md) (don't replace `IBlobProvider` interface, but replace `DatabaseBlobProvider` class).
@ -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.
@ -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();
This contributor runs during regular principal creation. Use `IAbpDynamicClaimsPrincipalContributor` when claims need to be refreshed by the [dynamic claims](../fundamentals/dynamic-claims.md) pipeline.
@ -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).
* `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`.
@ -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.
@ -203,6 +203,26 @@ 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 types. The predicate runs for class
// service registrations and receives the exposed class service type,
// which is the exposed base class rather than the implementation type
// when a class is exposed through a base class.
context.Services.DisableAbpClassInterceptors(
new NamedTypeSelector(
"MyHotPathServices",
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.
`IObjectSerializer` (defined in the `Volo.Abp.Serialization` package, independently of the JSON system) serializes objects to and from `byte[]`. The default implementation uses UTF-8 JSON bytes from `System.Text.Json`:
```csharp
public interface IObjectSerializer
{
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);
@ -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
@ -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.
@ -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:
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.
@ -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:
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:
- **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.
@ -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:
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.
@ -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
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:
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.
"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="Date Range"
/>
```
Use `abp-datetime-range-picker` with the same inputs when the model also needs start and end times. `labelText` is rendered as-is, so pass an already localized string (for example, a value resolved with the `LocalizationService`) instead of a localization key.
## Standalone Configuration
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';
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.
"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:
- 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';
| `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. |
@ -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.
@ -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.
`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
import { ListService } from '@abp/ng.core';
```ts
import { LIST_QUERY_DEBOUNCE_TIME, ListService } from '@abp/ng.core';
import { BookDto } from '../models';
import { BookService } from '../services';
import { inject } from '@angular/core';
import { Component, inject } from '@angular/core';
@Component({
/* class metadata here */
@ -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.
@ -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';
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:
> **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:
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

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';
"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';
import { BookService } from '../services/book.service';
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:
@ -16,7 +16,7 @@ The `logoUrl` property in the environment variables is the url of the logo.
You can add your logo to `src/assets` folder and set the `logoUrl` as shown below:
```js
```ts
export const environment = {
// other configurations
application: {
@ -74,7 +74,7 @@ Add the following to your `src/styles.scss`:
You can add routes to the menu by calling the `add` method of `RoutesService`. It is a singleton service, i.e. provided in root, so you can inject and use it immediately.
```js
```ts
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { Component, inject } from '@angular/core';
@ -106,10 +106,10 @@ export class AppComponent {
An alternative and probably cleaner way is to use a route provider. First create a provider:
```js
```ts
// route.provider.ts
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { provideAppInitializer } from '@angular/core';
import { inject, provideAppInitializer } from '@angular/core';
We can also define a group for navigation elements. It's an optional property
- **Note:** It'll also include groups that were defined at the modules
```js
```ts
// route.provider.ts
import { RoutesService } from '@abp/ng.core';
import { inject } from '@angular/core';
function configureRoutes() {
const routesService = inject(RoutesService);
routes.add([
routesService.add([
{
//etc..
group: 'ModuleName::GroupName'
@ -167,7 +168,7 @@ function configureRoutes() {
To get the route items as grouped we can use the `groupedVisible` (or Observable one `groupedVisible$`) getter methods
- It returns `RouteGroup<T>[]` if there is any group in the route tree, otherwise it returns `undefined`
```js
```ts
import { ABP, RoutesService, RouteGroup } from "@abp/ng.core";
import { Component, inject } from "@angular/core";
import { Observable } from "rxjs";
@ -185,7 +186,7 @@ export class AppComponent {
...and then in app.config.ts...
- The `groupedVisible` method will return the `Others` group for ungrouped items, the default key is `AbpUi::OthersGroup`, we can change this `key` via the `OTHERS_GROUP` injection token
```js
```ts
import { OTHERS_GROUP } from '@abp/ng.core';
import { APP_ROUTE_PROVIDER } from './route.provider';
@ -239,7 +240,7 @@ You can define your routes by adding `routes` as a child property to `data` prop
You can add the `routes` property like below:
```js
```ts
{
path: 'your-path',
data: {
@ -263,7 +264,7 @@ You can add the `routes` property like below:
Alternatively, you can do this:
```js
```ts
{
path: 'your-path',
data: {
@ -297,7 +298,7 @@ After adding the `routes` property as described above, the navigation menu looks
The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children. Also you can use `removeByParam` method to delete the routes with given properties.
```js
```ts
// this.routes is instance of RoutesService
// eThemeSharedRouteNames enum can be imported from @abp/ng.theme.shared
@ -343,7 +344,7 @@ After the operations above, the new menu looks like below:
You can add elements to the right part of the menu by calling the `addItems` method of `NavItemsService`. It is a singleton service, i.e. provided in root, so you can inject and use it immediately.
```js
```ts
import { NavItemsService } from '@abp/ng.theme.shared';
import { Component, inject } from '@angular/core';
@ -387,7 +388,7 @@ This inserts a search input and a sign out icon to the menu. The final UI looks
The `patchItem` method of `NavItemsService` finds an element by its `id` property and replaces its configuration with the new configuration passed as the second parameter. Similarly, `removeItem` method finds an element and removes it.
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 the `X-Requested-With` header and, when the corresponding values are available, an `Authorization` bearer token, `Accept-Language` and the configured tenant header. Existing authorization, language and tenant headers are preserved. Requests marked with the `IS_EXTERNAL_REQUEST` HTTP context token are sent without those ABP headers. The interceptor also integrates every request with the HTTP wait service.
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.
`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`:
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', () => {
"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 {
"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, TreeComponent } 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],
})
export class CategoryTreeComponent {
readonly nodes = signal([
{
key: 'books',
title: 'Books',
entity: { id: 'books', name: 'Books' } as Category,
- `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. Note that the default handler is also what records the drop position, so when you replace it, the `pos` property of the `DropEvent` emitted by `dropOver` is not set.
## Templates
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-templateabpTreeNodeTemplatelet-node>
<strong>{{ node.title }}</strong>
</ng-template>
<ng-templateabpTreeExpandedIconTemplatelet-node>
<span>{{ node.isExpanded ? '−' : '+' }}</span>
</ng-template>
</abp-tree>
```
}%}
Import `TreeNodeTemplateDirective` and `ExpandedIconTemplateDirective` from `@abp/ng.components/tree` into the standalone component that uses these templates, because Angular must see each directive used by a component template. If you use only one of these templates, import only its corresponding directive.
## Flat-List Adapter
`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';
@ -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:
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`.
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
<linkhref="app-global.css"rel="stylesheet"/>
<scriptsrc="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.
- [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)
@ -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.
"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:
`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 the numeric offset of the configured time zone (the `abp.clock.timeZone()` value, which falls back to the browser time zone). It is not a full IANA time-zone conversion and does not account for fractional-hour offsets or an offset change between the current date and the input date. Include the Luxon contributor when those cases must be handled.
`normalizeToLocaleString` accepts standard `Intl.DateTimeFormat` options. When no options are supplied, it uses `abp.clock.toLocaleStringOptions`. The default options include the numeric year, long month, numeric day, hour, minute and second. You can replace them to define application-wide display defaults:
````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.
@ -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`:
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:
`@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'
* `TenantAdminUserName` (default: admin): The tenant admin user name.
* `ImpersonationTenantPermission`: The permission name for tenant impersonation.
* `ImpersonationUserPermission`: The permission name for user impersonation.
* `ExternalProviderIconMap`: A dictionary of external provider names and their corresponding font-awesome icon classes. You can add new mapping to this dictionary to change the icon of an external provider.(Popular external provider icons are already defined, such as `Facebook`, `Google`, `Microsoft`, `Twitter`, etc.)
* `SwitchUserDuringImpersonate` (default: `false`): Signs the target user in with the application cookie while an impersonation flow is in progress.
* `ExternalProviderIconMap`: A dictionary of external provider names and their icon asset paths or CSS classes. Common providers such as GitHub, Google, X, Apple, LinkedIn, Facebook and Microsoft are already mapped.
* `IsTenantMultiDomain` (default: `false`): Enables tenant-domain redirects for linked-account and tenant-switching flows.
* `GetTenantDomain`: Resolves the target tenant's origin for impersonation redirects and, when `IsTenantMultiDomain` is enabled, linked-account and tenant-switching redirects. By default, it returns the current request's scheme and host.
* `ExternalProfilePictureDownloadTimeout` (default: 5 seconds): Limits how long external-login registration waits while downloading a profile picture.
* `EnableImageCompression` (default: false): Enables the image compression for the profile picture. When enabled, the selected compression library will compress the profile picture to decrease the image size. For more information see [image manipulation](../framework/infrastructure/image-manipulation.md)
* `EnableImageCompression` (default: `false`): Enables image compression for the profile picture. When enabled, the selected compression library compresses the profile picture to decrease its size. For more information, see [image manipulation](../framework/infrastructure/image-manipulation.md).
* `AllowedFileExtensions` (default: `.jpg`, `.jpeg` and `.png`): Defines the accepted file-name extensions. The extension check runs when the upload includes a file name.
* `MaxFileSizeInBytes` (default: 5 MiB): Rejects larger uploads. Set it to `0` to disable the size limit.
* `MagicBytesVerifiers`: Verifies the file content independently of the file name. The default verifiers accept JPEG and PNG signatures. If you add an allowed extension, add a matching content verifier as well; at least one verifier must accept every uploaded image.
### Registration Email Confirmation Codes
The registration email confirmation code is stored with a 10-minute absolute expiration by default. Configure a different duration with `Account:EmailConfirmation:CodeExpirationTime`:
```json
{
"Account": {
"EmailConfirmation": {
"CodeExpirationTime": "00:15:00"
}
}
}
```
Sending and checking these codes use separate built-in operation rate-limit policies. Sending a new code resets the check rate-limit state for that email address.
## Local login
@ -111,6 +134,20 @@ If you use `Social / External Logins`, It is automatically called for authentica
Email login lets users sign in with a one-time code, a magic link or both. It is disabled by default and also requires **Local login** to remain enabled. Configure it in `Settings > Account > Email Login`.
The available login types are:
* `OtpAndMagicLink` (default): The email contains both a six-digit code and a magic link.
* `MagicLinkOnly`: The email contains only a magic link and the code-verification endpoint is disabled.
* `OtpOnly`: The email contains only a code and direct magic-link verification is disabled.
The token lifespan defaults to 90 seconds and accepts values from 30 to 86,400 seconds. Codes and link tokens are single-use. Completing either path invalidates the outstanding credential for the other path, and sending a new email invalidates the previous credentials.
Email login uses built-in send and verification rate limits. When `AccountSettingNames.PreventEmailEnumeration` is enabled, requests for an unknown or locked-out account return the same expiry-shaped response as a valid request without sending an email. This prevents callers from using the send response to distinguish those accounts.
### Switching users during OAuth login
If you have an OAuth/Auth Server application using the Account Pro module, you can pass the `prompt=select_account` parameter to force the user to select an account.
You can modify the look and behavior of the module pages by passing the following options to `createRoutes` static method:
- **redirectUrl**: Default redirect URL after logging in.
- **entityActionContributors:** Changes grid actions. Please check [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md) for details.
- **toolbarActionContributors:** Changes page toolbar. Please check [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md) for details.
- **entityPropContributors:** Changes table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details.
- **entityActionContributors:** Changes actions on `eAccountComponents.MySecurityLogs`. See [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md).
- **toolbarActionContributors:** Changes the toolbar on `eAccountComponents.MySecurityLogs`. See [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md).
- **entityPropContributors:** Changes columns on `eAccountComponents.MySecurityLogs`. See [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md).
- **personelInfoEntityPropContributors:** Changes the edit-form properties on `eAccountComponents.PersonalSettings`. The public API uses this spelling. See [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md).
- **isPersonalSettingsChangedConfirmationActive:** Deprecated. Personal settings refresh the current user's state without requiring a new login.
#### Services / Models
@ -426,4 +465,3 @@ This module doesn't define any additional distributed event. See the [standard d
New users receive every Identity role marked as `Default`.
### Forgot Password & Reset Password
`/Account/ForgotPassword` page provides a way of sending password reset link to user's email address. The user then clicks to the link and determines a new password.
@ -51,6 +53,34 @@ Social/external login buttons becomes visible if you setup it. See the *Social/E
`IdentitySettingNames.User.IsUserNameUpdateEnabled` and `IdentitySettingNames.User.IsEmailUpdateEnabled` control whether the profile application service accepts changes to those fields. Both settings are `true` by default. External users can't change a local password; the built-in MVC profile page omits the password group for them and the application service rejects a password change.
### Login and Registration Settings
The Account module defines two client-visible settings. Both are `true` by default:
* `AccountSettingNames.IsSelfRegistrationEnabled` controls self-registration. It is enforced by `IAccountAppService.RegisterAsync` as well as the built-in registration pages.
* `AccountSettingNames.EnableLocalLogin` controls the local username/password login UI and handlers in the MVC Account pages, the OpenIddict and IdentityServer integrations, and the Angular Account layout. In Angular, `AuthWrapperService` reads the setting; the Basic Theme's `AuthWrapperComponent` shows the account content when it is enabled and a no-login-schemes warning when it is disabled.
These settings are independent. Disabling local login doesn't disable the registration application service. Set `IsSelfRegistrationEnabled` to `false` as well when users must not create local accounts. Change the values with `ISettingManager` like other [settings](../framework/infrastructure/settings.md). Global or tenant values are normally appropriate because the login and registration requests run before a user is authenticated.
### Extending the MVC Profile Page
The MVC `/Account/Manage` page is built from the contributors in `ProfileManagementPageOptions.Contributors`. Implement `IProfileManagementPageContributor` to add a group backed by a view component, then register it from your module:
Each contributor receives a `ProfileManagementPageCreationContext` and appends `ProfileManagementPageGroup` instances to its `Groups` collection. Contributors run in registration order on both GET and POST requests, and can resolve services through `context.ServiceProvider` when visibility depends on the current user or another runtime condition.
### Angular UI Extensibility
The Angular `createRoutes` function accepts three module-specific options: `redirectUrl`, `isPersonalSettingsChangedConfirmationActive` and `editFormPropContributors`. The form contributor key is `eAccountComponents.PersonalSettings`. The login, register, forgot-password, reset-password and manage-profile routes are also registered with the corresponding `eAccountComponents` keys for [component replacement](../framework/ui/angular/component-replacement.md). See [Dynamic Form Extensions](../framework/ui/angular/dynamic-form-extensions.md) for the contributor pattern.
## OpenIddict Integration
[Volo.Abp.Account.Web.OpenIddict](https://www.nuget.org/packages/Volo.Abp.Account.Web.OpenIddict) package provides integration for the [OpenIddict](https://github.com/openiddict). This package comes as installed with the [application startup template](../solution-templates/layered-web-application). See the [OpenIddict Module](./openiddict.md) documentation.
@ -63,6 +93,15 @@ Social/external login buttons becomes visible if you setup it. See the *Social/E
The Account Module has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application.
The MVC login and registration pages also recognize a Windows authentication scheme. `AbpAccountOptions.WindowsAuthenticationSchemeName` identifies that scheme and defaults to `"Windows"`. Set it when the registered scheme uses another name:
Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application.
Passkeys are disabled by default. The default limit is 10 passkeys per user, and the configured limit must be greater than zero. Registration is rejected when passkeys are disabled or the user has reached the limit.
## Manage Passkeys
You can add/rename/delete your passkeys in the `Account/Manage` page:
Invitation links contain a protected, URL-safe token and expire after 7 days by default. Invalid, modified or expired tokens are rejected. Configure the lifespan with `UserInvitationTokenProviderOptions`:
Inviting the same email address again while an invitation is still pending reuses that invitation, replaces its assigned roles and refreshes its invitation date. Resending is allowed only for a pending invitation; it refreshes the invitation date and sends a newly generated token.
## Accepting an Invitation
If the invited person already has an account, clicking the email link shows a confirmation screen to join the tenant:
@ -150,13 +161,13 @@ When the Shared strategy is enabled, a user is a **global resource** across host
### Host-only operations
The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce this — a direct API call from a tenant context will be rejected with a `UserFriendlyException`:
The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce these restrictions.
- Delete a user
- Activate / deactivate a user (`IsActive`)
- Lock / unlock a user
- Enable or disable two-factor authentication
- Change `LockoutEnabled` or `ShouldChangePasswordOnNextLogin`
Direct tenant API calls for these operations are rejected with a `UserFriendlyException`. When a tenant update request changes `IsActive`, `LockoutEnabled` or `ShouldChangePasswordOnNextLogin`, the application service restores the current host-managed values and continues processing the remaining editable fields.
> `Delete` here means deleting the **global user account**, not removing a user from a single tenant. Removing a member from one tenant is a tenant-level soft operation and is available to tenant administrators — see **Remove from tenant** below.
@ -176,6 +187,4 @@ Users can leave a tenant from their own account menu (`Switch Tenant` → `Leave
If you plan to migrate an existing multi-tenant application from an isolated strategy to Shared User Accounts, keep the following in mind:
1. **Uniqueness check**: Before enabling Shared, ensure all existing usernames and emails are unique globally. ABP performs this check when you switch the strategy and reports conflicts.
2. **Tenants with separate databases**: If some tenants use separate databases, you must ensure the Host database contains matching user records in the `AbpUsers` table (and, if you use social login / passkeys, also sync `AbpUserLogins` and `AbpUserPasskeys`) so the Host-side records match the tenant-side data. After that, the framework can create/manage the user-to-tenant associations.
- **Important — each host-side shadow row must have a new primary key (`Id`) different from the tenant user's `Id`.** Generate a fresh `Guid` for every shadow row instead of reusing the tenant user's primary key. The framework relies on this to distinguish a separate-database tenant from a shared-database one; reusing the Id can mask "Leave Tenant" and external login / passkey synchronization on legacy data. The other identifying fields (`UserName`, `Email`, `PasswordHash`, `TenantId`, etc.) should still match the tenant-side row.
2. **Tenants with separate databases**: The module detects a separate Identity database by comparing the resolved Identity connection string for the tenant with the host connection string. If some tenants use separate databases, ensure that the host database contains the corresponding shadow users in the `AbpUsers` table. Host-side shadow users are located by the tenant identifier and email address. If you use social login or passkeys, also synchronize `AbpUserLogins` and `AbpUserPasskeys`. Existing shadow rows that reuse the tenant user's primary key remain compatible with leaving a tenant.
"Description": "Discover how to implement AI management in your ABP Framework application, enhancing workspace dynamics with easy installation options."
"Description": "Manage persisted AI workspaces, providers, RAG data sources, MCP tools and remote AI clients with the ABP AI Management module."
}
```
@ -13,7 +13,7 @@ This module implements AI (Artificial Intelligence) management capabilities on t
## How to Install
The **AI Management Module** is not included in [the startup templates](../solution-templates/layered-web-application) by default. However, when creating a new application with [ABP Studio](../../tools/abp-studio/index.md), you can easily enable it during setup via the *AI Integration* step in the project creation wizard. Alternatively, you can install it using the ABP CLI or ABP Studio:
The **AI Management Module** is not included in [the startup templates](../../solution-templates/layered-web-application/index.md) by default. However, when creating a new application with [ABP Studio](../../studio/overview.md), you can easily enable it during setup via the *AI Integration* step in the project creation wizard. Alternatively, you can install it using the ABP CLI or ABP Studio:
**Using ABP CLI:**
@ -97,11 +97,13 @@ AI Management module packages are designed for various usage scenarios. Packages
## User Interface
This module provides UI integration for all three officially supported UI frameworks by ABP:
This module provides administration and client UI for the following UI stacks:
* **MVC / Razor Pages** UI
* **Angular** UI
* **Blazor** UI (Server & WebAssembly)
* **Angular** UI
* **Blazorise** UI (Server & WebAssembly)
* **MudBlazor** UI (Server & WebAssembly)
* **React Admin Console** management UI
### Menu Items
@ -129,12 +131,12 @@ You can create a new workspace or edit an existing workspace in this page. The w
* **API Key**: Authentication key (if required by provider)
* **API Base URL**: Custom endpoint URL (optional)
* **System Prompt**: Default system instructions
* **Temperature**: Response randomness (0.0-1.0)
* **Application Name**: Associate with specific application
* **Required Permission**: Permission needed to use this workspace
* **Embedder Provider / Model**: Embedding generator used for RAG
* **Vector Store Provider / Settings**: Storage backend and connection settings for document vectors
The **Application Name** is assigned by the application that creates or synchronizes the workspace. Workspace access is managed from the resource permission action; it is not a field in the create/edit form.
#### Chat Interface
The AI Management module includes a built-in chat interface for testing workspaces. You can:
@ -177,6 +179,21 @@ When a workspace has MCP servers associated, the AI model can invoke tools from
Use `McpClientFactoryOptions` when an MCP server needs more time to initialize or when a stdio connection test starts a slow process:
```csharp
Configure<McpClientFactoryOptions>(options =>
{
options.DefaultTimeoutMs = 180_000;
options.StdioInitializationTimeoutMs = 240_000;
options.StdioConnectionTestTimeoutSeconds = 300;
});
```
`DefaultTimeoutMs` defaults to 120 seconds and applies to HTTP transports and, unless overridden, stdio initialization. `StdioConnectionTestTimeoutSeconds` defaults to 180 seconds and is constrained to 30-600 seconds by the connection-test service. HTTP connection tests use a fixed one-minute timeout.
#### Workspace Data Sources
Workspace Data Sources page is used to upload and manage RAG documents per workspace. Uploaded files are processed and indexed in the background.
@ -207,12 +224,12 @@ When creating or managing a workspace, you can configure the following propertie
| `ApiKey` | No | API authentication key (required by some providers) |
| `ApiBaseUrl` | No | Custom endpoint URL (defaults to provider's default) |
| `SystemPrompt` | No | Default system prompt for all conversations |
| `Temperature` | No | Response randomness (0.0-1.0, defaults to provider default) |
| `Temperature` | No | Provider-specific response randomness |
| `Description` | No | Workspace description |
| `IsActive` | No | Enable/disable the workspace (default: true) |
| `ApplicationName` | No | Associate workspace with specific application |
| `RequiredPermissionName` | No | Permission required to use this workspace |
| `IsSystem` | No | Whether it's a system workspace (read-only) |
| `ApplicationName` | No | Application identity recorded during creation or synchronization |
| `RequiredPermissionName` | No | Optional policy checked by client consumption services |
| `IsSystem` | No | Whether the workspace was synchronized from code |
| `OverrideSystemConfiguration` | No | Allow database configuration to override code-defined settings |
| `EmbedderProvider` | No | Embedding provider name (e.g., "OpenAI", "Ollama") |
| `EmbedderModelName` | No | Embedding model identifier (e.g., "text-embedding-3-small") |
@ -231,27 +248,38 @@ The AI Management module supports two types of workspaces:
* **Defined in code** using `PreConfigure<AbpAIWorkspaceOptions>`
* **Cannot be deleted** through the UI
* **Read-only by default**, but can be overridden when `OverrideSystemConfiguration` is enabled
* **Use the code-defined chat client by default**; persisted provider settings take effect only when `OverrideSystemConfiguration` is enabled
* **Useful for** application-critical AI features that must always be available
* **Created automatically** when the application starts
* **Synchronized automatically** when the application starts
Example:
```csharp
PreConfigure<AbpAIWorkspaceOptions>(options =>
public override void PreConfigureServices(ServiceConfigurationContext context)
Configure the code-defined keyed client with the [Framework AI provider integration](../../framework/infrastructure/artificial-intelligence/index.md) used by your application.
At startup, AI Management records the registered chat, embedding and vector-store providers and synchronizes the code-defined workspace names for the current application. When at least one code-defined workspace remains, names removed from that application's configuration are also removed from its synchronized set. An empty workspace collection skips the update, so removing the last code-defined workspace doesn't delete its persisted record automatically. Synchronization errors are logged without stopping application startup.
You can disable startup synchronization or override the recorded application name:
```csharp
Configure<WorkspaceUpdaterOptions>(options =>
{
options.AutoUpdateAtStartup = false;
options.ApplicationName = "MyAIGateway";
});
```
Disabling synchronization means code-defined workspaces are not added to or updated in the management database automatically. Their code-defined keyed clients can still be resolved by the Framework AI infrastructure.
#### Dynamic Workspaces
* **Created through the UI** or programmatically via `ApplicationWorkspaceManager` and `IWorkspaceRepository`
@ -264,12 +292,16 @@ Example (data seeding):
```csharp
public class WorkspaceDataSeederContributor : IDataSeedContributor, ITransientDependency
> Provider API keys, embedding keys, vector-store connection settings, MCP headers and MCP credentials are sensitive persisted configuration. Do not hard-code them in source control. Restrict workspace and MCP administration permissions, protect the application database and configuration backups, and supply secrets from a secure configuration provider. Duplicating a workspace also duplicates its provider and RAG configuration, including credentials.
### Resolution Precedence
When an application requests a workspace chat client, AI Management resolves it in this order:
1. If no persisted configuration exists, the workspace is inactive, or it is a system workspace with `OverrideSystemConfiguration = false`, resolution first tries the code-defined keyed `IChatClient` and then the default `IChatClient`.
2. An active dynamic workspace, or an overridden system workspace, uses the factory registered for its persisted `Provider` value.
If neither fallback exists, an inactive workspace produces an inactive-workspace error; the other fallback cases produce a provider-not-found error. A configured provider without a registered factory also produces a provider-not-found error.
### Workspace Naming Rules
* Workspace names **must be unique**
* Workspace names **cannot contain spaces** (use underscores or camelCase)
* Workspace names are **case-sensitive**
Workspace names are global identifiers in an AI Management database. Workspaces, MCP server configurations, data sources and the data-source blob container are not tenant-scoped entities. In a multi-tenant application, use workspace resource permissions and application-level design to control access; do not treat these records or blobs as tenant-isolated storage.
## RAG with File Upload
@ -367,7 +413,12 @@ RAG is enabled per workspace when both embedding and vector store settings are c
* `MongoDb`: Standard MongoDB connection string including database name.
* `Pgvector`: Standard PostgreSQL/Npgsql connection string.
* `Qdrant`: Qdrant endpoint string (`http://host:port`, `https://host:port`, or `host:port`).
* `Qdrant`: Qdrant endpoint string (`http://host:port`, `https://host:port`, or `host:port`). The current provider discards the URL scheme and creates a non-TLS client from only the host and port. Do not rely on an `https://` value to enable TLS.
> [!IMPORTANT]
> The `MongoDb` vector-store provider requires MongoDB Atlas or an Atlas CLI local deployment because it uses `$vectorSearch`. Standard MongoDB Docker images do not provide this feature. Create an Atlas Vector Search index named `vector_index` on the `AIVectorEmbeddings` collection, with `Embedding` as the vector field, cosine similarity and dimensions matching the configured embedding model. The provider creates the collection and its regular workspace index, but it does not create the Atlas vector index.
The Pgvector provider creates the target database when its credentials permit it, enables the `vector` extension and creates workspace tables lazily. Qdrant creates a workspace-specific collection when the first vector is stored. The service account therefore needs the corresponding database, extension, table or collection privileges.
#### Document Processing Pipeline
@ -376,9 +427,11 @@ When a file is uploaded as a workspace data source:
1. File is stored in blob storage.
2. `IndexDocumentJob` is queued.
3. `DocumentProcessingManager` extracts text using content-type-specific extractors.
4. Text is chunked (default chunk size: `1000`, overlap: `200`).
5. Embeddings are generated in batches and stored through the configured vector store.
6. Data source is marked as processed (`IsProcessed = true`).
4. Text is chunked with `1000` characters as a target size. The normal paragraph path carries up to `200` characters into the next chunk; oversized paragraphs are split by line with a `50`-character carry. An indivisible line can produce a chunk larger than the target.
5. Embeddings are generated in ordered batches and stored through the configured vector store.
6. The data source is marked as processed (`IsProcessed = true`) after the final batch succeeds.
Each indexing run has an identifier and a per-data-source distributed lock. Stale and duplicate batches are ignored, while an out-of-order batch is retried by the background job system. A failed run can therefore leave partial vector data until a retry succeeds or the data source is re-indexed again.
#### Workspace Data Source HTTP API
@ -395,20 +448,45 @@ The module exposes workspace data source endpoints under `/api/ai-management/wor
#### Chat Integration Behavior
When a workspace has embedder configuration, AI Management wraps the chat client with a document search tool function named `search_workspace_documents`.
When an active dynamic workspace, or a system workspace with `OverrideSystemConfiguration = true`, is resolved through its persisted provider factory and has embedder configuration, AI Management wraps the factory-created client with a document search tool function named `search_documents`. The keyed/default fallback branches described in [Resolution Precedence](#resolution-precedence) return their code-defined client directly and don't add persisted RAG or MCP tools.
* The tool delegates to `IDocumentSearchService` (`DocumentSearchService` by default).
* The search currently uses `TopK = 5` chunks.
* If RAG retrieval fails, chat continues without injected context.
The underlying chat client must be a `FunctionInvokingChatClient`. The built-in OpenAI and Ollama factories add function invocation automatically. A custom chat factory must build its client with `ChatClientBuilder.UseFunctionInvocation()` before RAG or MCP tools can execute.
#### Automatic Reindexing on Configuration Changes
When workspace embedder or vector store configuration changes, AI Management automatically:
* Initializes the new vector store configuration (if needed).
* Deletes existing embeddings when embedder provider/model changes.
* Attempts to delete existing embeddings when the embedder provider or model changes.
* Re-queues all workspace data sources for re-indexing.
Embedding cleanup and automatic re-index queueing are best-effort follow-up operations. Their failures are logged without rolling back the workspace update. Use the data-source page's **Re-index All** action after correcting the configuration if automatic re-indexing could not be queued.
### Configuring Indexing Options
`AIManagementIndexingOptions` controls the work performed by background indexing jobs:
```csharp
Configure<AIManagementIndexingOptions>(options =>
{
options.EmbeddingBatchSize = 100;
options.MaxConcurrentIndexingJobs = 2;
options.DistributedLockTimeoutSeconds = 15;
});
```
| Property | Default | Description |
| -------- | ------- | ----------- |
| `EmbeddingBatchSize` | `50` | Maximum chunks embedded and stored by one batch job |
| `MaxConcurrentIndexingJobs` | `1` | Maximum indexing batches running concurrently across the application |
| `DistributedLockTimeoutSeconds` | `0` | Time to wait for an indexing lock; `0` performs an immediate attempt |
Increase concurrency only after checking the embedding provider's rate limits and vector-store capacity. The concurrency limiter and the per-data-source lock both use the [distributed lock](../../framework/infrastructure/distributed-locking.md), so they apply across all application instances when a distributed lock provider is configured (with the default in-process implementation, they only cover a single process). The per-data-source lock prevents two batches from mutating the same data source concurrently.
### Configuring Data Source Upload Options
The `WorkspaceDataSourceOptions` class allows you to customize the file upload constraints for workspace data sources. You can configure the allowed file extensions, maximum file size, and content type mappings.
@ -418,15 +496,13 @@ public override void ConfigureServices(ServiceConfigurationContext context)
@ -439,6 +515,8 @@ public override void ConfigureServices(ServiceConfigurationContext context)
| `MaxFileSize` | `long` | `10485760` (10 MB) | Maximum file size in bytes |
| `ContentTypeMap` | `Dictionary<string, string>` | `.txt`, `.md`, `.pdf` with their MIME types | Maps file extensions to MIME content types |
Adding an extension and MIME mapping only allows the upload. The mapped content type must also be supported by an `IDocumentTextExtractor`. Implement and register an extractor when adding a format that cannot use the built-in plain-text, Markdown or PDF extractors.
The options class also provides helper methods:
| Method | Description |
@ -447,9 +525,6 @@ The options class also provides helper methods:
| `GetAcceptAttribute()` | Returns a string for the HTML `accept` attribute (e.g., ".txt,.md,.pdf") |
> [!NOTE]
> Adding new file extensions also requires a matching content extractor to be registered for document processing. The built-in extractors support `.txt`, `.md`, and `.pdf` files.
#### Hosting-Level Upload Limits
`WorkspaceDataSourceOptions.MaxFileSize` controls the module-level validation, but your hosting stack may reject large uploads before the request reaches AI Management. If you increase `MaxFileSize`, make sure the underlying server and proxy limits are also updated.
@ -523,6 +598,7 @@ The AI Management module defines the following permissions:
@ -539,9 +615,17 @@ The module also defines workspace data source permissions for RAG document opera
| `AIManagement.WorkspaceDataSources.Download` | Download original uploaded file |
| `AIManagement.WorkspaceDataSources.ReIndex` | Re-index one or all workspace files |
### Workspace-Level Permissions
### Workspace Consumption Authorization
The `IChatCompletionClientAppService` client API, OpenAI-compatible API and MCP tool catalog authorize a workspace when the current user satisfies any one of these conditions:
1. The user has `AIManagement.Workspaces.Playground`.
2. The workspace has a `RequiredPermissionName` and the user has that policy.
3. The user has the `Volo.AIManagement.Workspaces.Workspace.Consume` resource permission for that workspace ID.
In addition to module-level permissions, you can restrict access to individual workspaces by setting the `RequiredPermissionName` property:
Use the **Manage Permissions** action on the workspace page to grant the per-workspace resource permission. `AIManagement.Workspaces.ManagePermissions` controls access to that action.
`RequiredPermissionName` is a programmatic alternative for workspaces tied to an application permission:
```csharp
var workspace = await _applicationWorkspaceManager.CreateAsync(
@ -549,14 +633,11 @@ var workspace = await _applicationWorkspaceManager.CreateAsync(
* Only authorized users with that permission can access the workspace endpoints
* Users without the permission will receive an authorization error
Administrative workspace and data-source endpoints still use the module permissions in the tables above. A workspace consumption grant does not grant permission to edit the workspace, manage its credentials or upload RAG documents.
## Usage Scenarios
@ -569,57 +650,7 @@ The AI Management module is designed to support various usage patterns, from sim
**Use this when:** You want to use AI in your application without any dependency on the AI Management module.
In this scenario, you only use the ABP Framework's AI features directly. You configure AI providers (like OpenAI) in your code and don't need any database or management UI.
**Required Packages:**
- `Volo.Abp.AI`
- Any Microsoft AI extensions (e.g., `Microsoft.Extensions.AI.OpenAI`)
**Configuration:**
```csharp
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
public MyService(IChatClient<TWorkspace> chatClient)
{
_chatClient = chatClient;
}
public async Task<string> GetResponseAsync(string prompt)
{
var response = await _chatClient.CompleteAsync(prompt);
return response.Message.Text;
}
}
```
> See [Artificial Intelligence](../../framework/infrastructure/artificial-intelligence/index.md) documentation for more details about workspace configuration.
In this scenario, use the ABP Framework AI infrastructure and configure provider clients in code. No AI Management database, administration UI or commercial client packages are involved. See [Artificial Intelligence](../../framework/infrastructure/artificial-intelligence/index.md) for the current static workspace registration and consumption APIs.
### Scenario 2: AI Management with Domain Layer Dependency (Local Execution)
@ -646,49 +677,12 @@ In this scenario, you install the AI Management module with its database layer,
> Note: `Volo.AIManagement.EntityFrameworkCore` transitively includes `Volo.AIManagement.Domain` and `Volo.Abp.AI.AIManagement` packages.
**Workspace Definition Options:**
**Option 1 - System Workspace (Code-based):**
```csharp
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
public MyService(IChatClient<MyCustomWorkspace> chatClient)
{
_chatClient = chatClient;
}
}
```
For a system workspace, define the typed workspace and its code client as described in [System Workspaces](#system-workspaces). Enable `OverrideSystemConfiguration` on that workspace only when persisted provider settings should replace the code client.
### Scenario 3: AI Management Client with Remote Execution
@ -714,25 +708,6 @@ Add the remote service endpoint in your `appsettings.json`:
}
```
Optionally define workspace in your module:
```csharp
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpAIWorkspaceOptions>(options =>
{
// Optional: Pre-define workspace type for type safety
@ -832,15 +807,14 @@ Each AI Management **workspace** appears as a selectable model in the client app
| `/v1/models` | GET | List available models (workspaces) |
| `/v1/models/{modelId}` | GET | Retrieve a single model (workspace) |
| `/v1/embeddings` | POST | Generate embeddings |
| `/v1/files` | GET | List uploaded files |
| `/v1/files` | POST | Upload a file |
| `/v1/files/{fileId}` | GET | Get file info |
| `/v1/files/{fileId}` | DELETE | Delete a file |
| `/v1/files/{fileId}/content` | GET | Download file content |
All endpoints require authentication via a **Bearer token** in the `Authorization` header.
All endpoints require an authenticated user. Non-browser API clients normally send an access token as a **Bearer token** in the `Authorization` header.
The value of `model` is an AI Management workspace name, not the provider's model name. `GET /v1/models` returns only workspaces the current user is authorized to consume. The same workspace authorization is applied before chat, completion, model and embedding operations.
#### Usage
The module also exposes file-management routes backed by workspace data sources. They require workspace-specific parameters and are not a drop-in implementation of the OpenAI Files API. Use the [Workspace Data Source HTTP API](#workspace-data-source-http-api) for portable upload, list, download, delete and re-index operations.
### Usage
The general pattern for connecting any OpenAI-compatible client:
@ -877,13 +851,13 @@ curl -X POST https://localhost:44336/v1/chat/completions \
}'
```
> The OpenAI-compatible endpoints are available from both the `Volo.AIManagement.Client.HttpApi` and `Volo.AIManagement.HttpApi` packages, depending on your deployment scenario.
> The OpenAI-compatible `/v1` endpoints are provided by `Volo.AIManagement.Client.HttpApi`. `Volo.AIManagement.HttpApi` instead exposes the management and integration APIs.
## Client Usage
AI Management uses different packages depending on the usage scenario:
- **`Volo.AIManagement.*` packages**: These contain the core AI functionality and are used when your application hosts and manages its own AI operations. These packages don't expose any application service and endpoints to be consumed by default.
- **`Volo.AIManagement.*` packages**: These contain the core AI functionality for applications that host and manage AI operations. The application and HTTP API packages expose management and integration services.
- **`Volo.AIManagement.Client.*` packages**: These are designed for applications that need to consume AI services from a remote application. They provide both server and client side of remote access to the AI services.
@ -923,6 +897,9 @@ You can customize the chat widget with the following properties:
- `Title`: The title of the chat widget.
- `ShowStreamCheckbox`: Whether to show the stream checkbox. Allows user to toggle streaming on and off. Default is `false`.
- `UseStreaming`: Default streaming behavior. Can be overridden by user when `ShowStreamCheckbox` is true.
- `DisableWhenNoConversation`: Disables input until a `ConversationId` is selected. Default is `false`.
- `ShowUsageDetails`: Shows token usage and tool calls for assistant messages. Default is `true`.
- `ShowDetailedSourceInformation`: Shows chunk-level RAG source details instead of grouping sources by file. Default is `false`.
```csharp
@await Component.InvokeAsync(typeof(ChatClientChatViewComponent), new ChatClientChatViewModel
In order to configure the application to use the AI Management module, you first need to import `provideAIManagementConfig` from `@volo/abp.ng.ai-management/config` to root application configuration. Then, you will need to append it to the `appConfig` array:
```js
```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideAIManagementConfig } from '@volo/abp.ng.ai-management/config';
The AI Management module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. It is available for import from `@volo/abp.ng.ai-management`.
The AI Management module should be imported and lazy-loaded in your routing array. It has a `createRoutes` function for configuration and is available from `@volo/abp.ng.ai-management`.
```js
```ts
// app.routes.ts
import { Routes } from '@angular/router';
const APP_ROUTES: Routes = [
// ...
{
path: 'ai-management',
loadChildren: () =>
import('@volo/abp.ng.ai-management').then(m => m.createRoutes(/* options here */)),
`createRoutes` optionally accepts `AIManagementConfigOptions`. It provides entity-action, toolbar-action and entity-property contributors for workspaces and MCP servers, plus create/edit form contributors for workspaces. The Workspaces, Chat Playground, MCP Servers and Workspace Data Sources routes also use keys from `eAIManagementComponents`, so they can be replaced through ABP's replaceable component system.
#### Services / Models
AI Management module services and models are generated via `generate-proxy` command of the [ABP CLI](../../cli). If you need the module's proxies, you can run the following command in the Angular project directory:
The AI Management module remote URL configurations shown above are optional.
Use `AIManagement` for management and integration proxies. Non-streaming requests from the Angular chat widget use the generated `ChatCompletionClientService` proxy with `AIManagementClient`. When streaming is enabled, both the stream-start POST request and the subsequent EventSource connection use `default.url` instead of `AIManagementClient`.
> If you don't set the `AIManagement` property, the `default.url` will be used as fallback.
Both named entries are optional and generated proxy requests fall back independently to `default.url`. `default.url` is also the global fallback for other ABP and application requests that don't specify a configured named API, so changing it redirects those requests too.
For split hosting, configure `AIManagement` and `AIManagementClient` for their respective hosts. Before pointing `default.url` to the client API and enabling streaming, make sure every other request uses an appropriate named API or that the client host provides or forwards all endpoints that still rely on the global fallback.
#### The Chat Widget
@ -1103,12 +1090,19 @@ The `@volo/abp.ng.ai-management` package provides a `ChatInterfaceComponent` (`a
<abp-chat-interface
[workspaceName]="'mylama'"
[conversationId]="'my-conversation-id'"
[showUsageDetails]="true"
[showStreamCheckbox]="true"
/>
```
- `workspaceName` (required): The name of the workspace to use.
- `conversationId`: The unique identifier for persisting and retrieving chat history from client-side storage. When provided, the chat history is stored in the browser and restored when the user revisits the page. If `null`, the chat is ephemeral and will be lost when the component is destroyed.
- `conversationId`: The unique identifier for persisting and retrieving chat history from client-side storage. When provided, the history is restored for the current user and workspace.
- `providerName`: The name of the AI provider. Used for displaying contextual error messages.
- `allowEphemeral`: Allows sending without a `conversationId`. The default is `false`; set it to `true` for an in-memory conversation that is discarded with the component.
- `showUsageDetails`: Shows usage and tool-call details. Default is `true`.
- `showStreamCheckbox`: Shows the streaming toggle. Default is `true`.
The component emits `messageSent` and `messageReceived` events for application-level conversation orchestration.
### Blazor UI
@ -1117,26 +1111,22 @@ The `@volo/abp.ng.ai-management` package provides a `ChatInterfaceComponent` (`a
The AI Management module remote endpoint URLs can be configured in your `appsettings.json`:
```json
"RemoteServices": {
"Default": {
"BaseUrl": "Default url here"
},
"AIManagement": {
"BaseUrl": "AI Management remote url here"
{
"RemoteServices": {
"Default": {
"BaseUrl": "Default url here"
},
"AIManagement": {
"BaseUrl": "AI Management remote url here"
},
"AIManagementClient": {
"BaseUrl": "AI Management client remote url here"
}
}
}
```
For **Blazor WebAssembly**, you can also configure the remote endpoint URL via `AIManagementClientBlazorWebAssemblyOptions`:
> If you don't set the `BaseUrl` for AIManagement, the `Default.BaseUrl` will be used as fallback.
Use the `AIManagement` remote service for management and integration clients. Use `AIManagementClient` for the remote chat and OpenAI-compatible client APIs. Blazor WebAssembly uses the standard ABP remote-service configuration shown above; it doesn't require a module-specific options class. If a named `BaseUrl` isn't set, ABP uses `Default.BaseUrl` as the fallback.
#### The Chat Widget
@ -1152,10 +1142,15 @@ The `Volo.AIManagement.Client.Blazor` package provides a `ChatClientChat` Blazor
```
- `WorkspaceName` (required): The name of the workspace to use.
- `ConversationId`: The unique identifier for persisting and retrieving chat history from client-side storage. When provided, the chat history is stored in the browser's local storage and restored when the user revisits the page. If not provided or `null`, the chat is ephemeral and will be lost when the component is disposed.
- `ConversationId` (required to send messages): The unique identifier for persisting and retrieving chat history from client-side storage. The component disables message input while this value is null or empty.
- `Title`: The title displayed in the chat widget header.
- `ShowStreamCheckbox`: Whether to show a checkbox that allows the user to toggle streaming on and off. Default is `false`.
- `OnFirstMessage`: An `EventCallback<FirstMessageEventArgs>` that is triggered when the first message is sent in a conversation. It can be used to determine the chat title after the first prompt like applied in the chat playground. The event args contain `ConversationId` and `Message` properties.
- `UseStreaming`: Initial streaming mode. Default is `true`.
- `ShowUsageDetails`: Shows token usage and tool calls. Default is `true` in the Blazorise component.
- `ShowDetailedSourceInformation`: Shows chunk-level RAG source information. Default is `true` in the Blazorise component.
- `OnFirstMessage`: An `EventCallback<FirstMessageEventArgs>` that is triggered when the first message is sent in a conversation. It can be used to determine the chat title after the first prompt like applied in the chat playground. The event args contain `ConversationId` and `Message` properties.
The MudBlazor package exposes the same core `WorkspaceName`, `ConversationId`, `Title`, `ShowStreamCheckbox` and `OnFirstMessage` parameters. The detailed usage/source display parameters above are specific to the Blazorise component.
```xml
<ChatClientChatWorkspaceName="mylama"
@ -1165,10 +1160,9 @@ The `Volo.AIManagement.Client.Blazor` package provides a `ChatClientChat` Blazor
OnFirstMessage="@HandleFirstMessage" />
```
## Using Dynamic Workspace Configurations for custom requirements
## Reading Dynamic Workspace Configuration
The AI Management module allows you to access only configuration of a workspace without resolving pre-constructed chat client. This is useful when you want to use a workspace for your own purposes and you don't need to use the chat client.
The `IWorkspaceConfigurationStore` service is used to access the configuration of a workspace. It has multiple implementations according to the usage scenario.
Use `IWorkspaceConfigurationStore` when trusted server-side code needs the persisted workspace configuration without resolving an `IChatClient`. The store has local and remote implementations for the corresponding deployment scenarios.
// Get the configuration of the workspace that can be managed dynamically.
var configuration = await _workspaceConfigurationStore.GetAsync("MyWorkspace");
// Do something with the configuration
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatClient(
config.ModelName!,
new Uri(config.ApiBaseUrl),
config.ApiKey
)
.Build();
var configuration = await _workspaceConfigurationStore
.GetOrNullAsync("MyWorkspace");
return configuration?.ModelName;
}
}
```
The returned configuration can include provider credentials. Keep it inside trusted server-side services and do not serialize it into an application response. Use `IChatClient<TWorkspace>` or `IChatCompletionClientAppService` when you only need to execute a chat request.
## Implementing Custom AI Provider Factories
While the AI Management module provides built-in support for OpenAI through the `Volo.AIManagement.OpenAI`package, you can easily add support for other AI providers by implementing a custom `IChatClientFactory`.
AI Management provides built-in OpenAI and Ollama factories through the `Volo.AIManagement.OpenAI`and `Volo.AIManagement.Ollama` packages. You can add another provider by implementing a custom `IChatClientFactory`.
### Understanding the Factory Pattern
The AI Management module uses a factory pattern to create `IChatClient` instances based on the provider configuration stored in the database. Each provider (OpenAI, Ollama, Azure OpenAI, etc.) needs its own factory implementation.
The AI Management module uses a factory pattern to create `IChatClient` instances based on the provider configuration stored in the database. Each provider name needs one registered factory implementation.
### Creating a Custom Factory
Here's how to implement a factory for Ollama as an example:
The following example registers a separate `CustomOllama` provider to demonstrate the factory contract. Use the built-in `Volo.AIManagement.Ollama` package when you only need the standard Ollama integration.
> For production scenarios, you may want to add validation for the factory configuration.
> [!IMPORTANT]
> Validate every required configuration value in the factory. Build a `FunctionInvokingChatClient` with `UseFunctionInvocation()` when the workspace can use RAG or MCP tools.
### Available Configuration Properties
@ -1270,6 +1259,7 @@ The `ChatClientCreationConfiguration` object provides the following properties f
| `IsActive` | bool | Whether the workspace is active |
| `IsSystem` | bool | Whether it's a system workspace |
| `OverrideSystemConfiguration` | bool | Whether persisted settings override a system workspace |
| `RequiredPermissionName` | string? | Permission required to use this workspace |
| `HasEmbedderConfiguration` | bool | Whether the workspace has embedder/RAG configuration |
@ -1286,7 +1277,11 @@ The `ChatClientCreationConfiguration` object provides the following properties f
Here's an example of implementing a factory for Azure OpenAI:
Install the `Azure.AI.OpenAI` and `Microsoft.Extensions.AI.OpenAI` NuGet packages before adding this factory (the `AsIChatClient()` extension method comes from `Microsoft.Extensions.AI.OpenAI`).
```csharp
using System;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using Azure;
using Microsoft.Extensions.AI;
@ -1306,8 +1301,13 @@ public class AzureOpenAIChatClientFactory : IChatClientFactory, ITransientDepend
new AzureKeyCredential(configuration.ApiKey ?? throw new ArgumentNullException(nameof(configuration.ApiKey)))
);
var chatClient = client.GetChatClient(configuration.ModelName);
The cache is invalidated when workspaces are created, updated or deleted. A rename invalidates both the old and current workspace names, and invalidation participates in the current unit of work.
### HttpApi Client Layer
- `IntegrationWorkspaceConfigurationStore`: Integration service for remote workspace configuration retrieval. Implements `IWorkspaceConfigurationStore` interface.
The cache is automatically invalidated when workspaces are created, updated, or deleted.
## See Also
- [Artificial Intelligence Infrastructure](../../framework/infrastructure/artificial-intelligence/index.md): Learn about the underlying AI workspace infrastructure
@ -25,7 +25,7 @@ See [the module description page](https://abp.io/modules/Volo.AuditLogging.Ui) f
## How to install
Identity is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it.
Audit Logging is pre-installed in [the startup templates](../solution-templates). So, no need to manually install it.
### Packages
@ -41,7 +41,7 @@ Audit logs module adds the following items to the "Main" menu, under the "Admini
* **Audit Logs**: List, view and filter audit logs and entity changes.
`IAbpAuditLoggingMainMenuNames` class has the constants for the menu item names.
`AbpAuditLoggingMainMenuNames` class has the constants for the menu item names.
### Pages
@ -67,7 +67,7 @@ You can view details of an audit log by clicking the magnifier icon on each audi
##### Export to Excel
You can export audit logs to Excel by clicking the "Export to Excel" button in the toolbar. If the result set is small (less than a configurable threshold), the file will be generated and downloaded immediately. For larger result sets, the export will be processed as a background job and you'll receive an email with a download link once the export is completed.
You can export audit logs to Excel by clicking the "Export to Excel" button in the toolbar. The file is generated and downloaded immediately when the result set contains 1,000 records or fewer. If the result set contains more than 1,000 records, the export is processed as a background job and you'll receive an email with a download link once the export is completed.
#### Entity Changes
@ -97,7 +97,7 @@ You can view details of all changes of an entity by clicking the "Full Change Hi
##### Export to Excel
You can export entity changes to Excel by clicking the "Export to Excel" button in the toolbar. Similar to audit logs export, for large datasets the export will be processed as a background job and you'll receive an email notification once completed.
You can export entity changes to Excel by clicking the "Export to Excel" button in the toolbar. As with audit log exports, result sets with 1,000 records or fewer are downloaded immediately. Result sets with more than 1,000 records are processed as a background job, and you'll receive an email notification once the export is completed.
#### Audit Log Settings
@ -113,6 +113,111 @@ To view the audit log settings, you need to enable the feature. For the host sid
> If you don't enable the *Cleanup Service System Wide* from the host side under *Settings* -> *Audit logs* -> *Global*, it won't remove the expired audit logs, even if there are tenant specific settings.
## Reusable widgets
The module provides **Error Rate** and **Average Execution Duration Per Day** widgets. The current user needs the `AuditLogging.AuditLogs` permission to load their data.
### Angular
Import the widget components from `@volo/abp.ng.audit-logging`, add them to your component imports and keep references when you need to refresh their date range:
```ts
import { Component, ViewChild } from '@angular/core';
The `width` and `height` inputs are optional. Both default to `273` and `136`, respectively.
```html
<abp-average-execution-duration-widget
#averageExecutionDurationWidget
[height]="250"
></abp-average-execution-duration-widget>
<abp-error-rate-widget
#errorRateWidget
[height]="250"
></abp-error-rate-widget>
```
### Blazor
The Bootstrap and MudBlazor packages expose components with the same parameters and `RefreshAsync` method. The following example uses the Bootstrap Blazor package. For MudBlazor, use the corresponding `Volo.Abp.AuditLogging.Blazor.MudBlazor` namespaces.
@ -237,16 +342,30 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do
* AbpAuditLogActions
* AbpEntityChanges
* AbpEntityPropertyChanges
* **AbpAuditLogExcelFiles**
#### MongoDB
##### Collections
* **AbpAuditLogs**
* **AbpAuditLogExcelFiles**
### Permissions
See the `AbpAuditLoggingPermissions` class members for all permissions defined for this module.
The module defines the following feature and permission relationships:
* `AuditLogging.Enable` is enabled by default. The `AuditLogging.AuditLogs` permission requires this feature, and the audit log application service also checks it.
* `AuditLogging.SettingManagement` is a child feature of `AuditLogging.Enable` and is disabled by default. The `AuditLogging.AuditLogs.SettingManagement` permission requires this feature.
* `AuditLogging.AuditLogs.Export` is a child permission of `AuditLogging.AuditLogs`. Audit log and entity change export operations require this permission.
See the `AbpAuditLoggingPermissions` and `AbpAuditLoggingFeatures` class members for the complete definitions.
#### Entity-specific change history permissions
You can define a permission for the change history of a specific entity by using the `AuditLogging.ViewChangeHistory:{EntityTypeFullName}` naming convention. For example, the permission name for `Acme.BookStore.Books.Book` is `AuditLogging.ViewChangeHistory:Acme.BookStore.Books.Book`.
When a matching permission is defined and granted, the user can view that entity's change history. If the entity-specific permission is not defined or is not granted, authorization falls back to `AuditLogging.AuditLogs`. Users who have the general audit log permission can therefore still view the entity history.
@ -29,12 +29,37 @@ The source code of this module can be accessed [here](https://github.com/abpfram
- `EntityChange` (collection): Changed entities of audit log.
- `AuditLogAction` (collection): Executed actions of audit log.
#### Extending the Entities
The `AuditLog`, `AuditLogAction` and `EntityChange` entities support the [Module Entity Extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) system. Configure them in the `Domain.Shared` project before the database model is created. The following example adds a property to `AuditLog`:
Use `ConfigureAuditLogAction` or `ConfigureEntityChange` in the same way to extend the other supported entities.
#### Repositories
Following custom repositories are defined for this module:
- `IAuditLogRepository`
#### Audit Log Conversion
The module uses `IAuditLogInfoToAuditLogConverter` to convert the `AuditLogInfo` collected by the auditing system into the persisted `AuditLog` aggregate. You can inject this service when you need the same conversion in a custom persistence flow, or replace its default implementation using the [dependency injection system](../framework/fundamentals/dependency-injection.md#replace-a-service) to customize the mapping.
#### Persistence Limits
Before saving an audit log, the module truncates fields that have maximum lengths defined by `AuditLogConsts`, `AuditLogActionConsts`, `EntityChangeConsts` and `EntityPropertyChangeConsts`. Action parameters are handled differently: if `AuditLogAction.Parameters` exceeds `AuditLogActionConsts.MaxParametersLength` (2,000 by default), it is persisted as an empty string instead of being truncated.
### Database providers
#### Common
@ -55,13 +80,15 @@ This module uses `AbpAuditLogging` for the connection string name. If you don't
@ -41,7 +41,7 @@ Following custom repositories are defined for this module:
##### Table / collection prefix & schema
All tables/collections use the `Abp` prefix by default. Set static properties on the `BackgroundJobsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Abp` prefix by default. Set static properties on the `AbpBackgroundJobsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
@ -61,4 +61,4 @@ This module uses `AbpBackgroundJobs` for the connection string name. If you don'
"Description": "Learn how to install and configure the standalone ABP Blogging module, including its MVC UI, routes, permissions, files and database providers."
}
```
# Blogging Module
The Blogging module is a free and open-source application module for creating one or more blogs. It provides an MVC / Razor Pages user interface, application services, HTTP APIs and Entity Framework Core and MongoDB integrations for blogs, posts, tags, comments and member profiles.
> This page documents the standalone `Volo.Blogging` module. [CMS Kit: Blogging](cms-kit/blogging.md) is a different feature family with different entities, configuration and UI.
## How to Install
Use the ABP CLI to add the module to an existing solution:
```bash
abp add-module Volo.Blogging
```
The command adds the module packages and dependencies to the compatible projects in your solution. Apply the generated database migration after adding the module.
### The Source Code
The source code of this module is available in the [ABP repository](https://github.com/abpframework/abp/tree/dev/modules/blogging). It is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can use and customize it.
## User Interface and Content Workflow
The module provides two MVC / Razor Pages surfaces:
* The public site lists blogs, posts and popular tags, renders post content as Markdown, displays member profiles and allows authenticated users to add comments.
* The administration page manages blogs. Post creation and editing are available from the public blog UI to users with the corresponding permissions.
When the application has one blog, the blog index redirects directly to that blog. With multiple blogs, the index displays the available blogs.
Creating a post makes it available to the public list and reading APIs immediately. The standalone module does not add a draft, review or scheduled-publication state. If a post URL is already used in the same blog, the application service appends a generated suffix and returns the resulting URL.
Tags are entered with a post. The application service normalizes tag names to lowercase, removes duplicates and maintains their usage counts.
### Member Profiles
The module keeps a local `BlogUser` record for post and comment authors. It uses ABP's [user lookup and synchronization](identity/user-synchronization.md) infrastructure to obtain Identity user data. The public member page lists an active user's posts and profile information; the current user can edit the Blogging-specific fields on their own profile.
## Permissions
The administration menu is visible with the `Blogging.Blog.Management` permission. Blog operations use separate child permissions:
* `Blogging.Blog.Create`
* `Blogging.Blog.Update`
* `Blogging.Blog.Delete`
* `Blogging.Blog.ClearCache`
Post creation, update and deletion require `Blogging.Post.Create`, `Blogging.Post.Update` and `Blogging.Post.Delete`, respectively.
Any authenticated user can create a comment. A comment can be updated or deleted by its creator or by a user with `Blogging.Comment.Update` or `Blogging.Comment.Delete`.
See the [Authorization](../framework/fundamentals/authorization/index.md) documentation to learn how to grant permissions to roles and users.
## Routing
`BloggingUrlOptions.RoutePrefix` controls the public URL prefix. Its default value produces URLs under `/blog/`. The following example moves the public blog under `/articles/` and enables single-blog mode for the blog whose short name is `engineering`:
```csharp
Configure<BloggingUrlOptions>(options =>
{
options.RoutePrefix = "articles";
options.SingleBlogMode.Enabled = true;
options.SingleBlogMode.BlogName = "engineering";
});
```
Single-blog mode removes the blog short-name segment from post URLs. If the application contains multiple blogs, set `SingleBlogMode.BlogName` to the short name of the blog to expose. If it contains exactly one blog, the module can select it without setting `BlogName`.
Set `RoutePrefix` to an empty string to serve the blog from `/`. In this mode the route constraint uses `IgnoredPaths` to avoid capturing other top-level application routes. The module already adds its framework endpoints, bundle folder and member route; add application-specific top-level paths when needed:
```csharp
Configure<BloggingUrlOptions>(options =>
{
options.RoutePrefix = "";
options.IgnoredPaths.Add("health");
});
```
## Post Images
Post images are saved through the [BLOB Storing](../framework/infrastructure/blob-storing) system in the `blogging-files` container. Configure a BLOB provider for this container as you would for any other typed container.
The upload service accepts JPEG, PNG, GIF and BMP images. The maximum file size is 5 MiB by default. `BloggingWebConsts.FileUploading.MaxFileSize` is a process-wide static value, so set it once during application startup, before the application begins accepting uploads:
The post page emits Twitter card metadata. Configure the site handle with `BloggingTwitterOptions`:
```csharp
Configure<BloggingTwitterOptions>(options =>
{
options.Site = "@myblog";
});
```
## Post List Cache
The time-ordered post list is cached per blog for one hour. Creating, updating or deleting posts through `IPostAppService` invalidates that cache within the current unit of work. The blog administration page also provides a **Clear Cache** action to users with the `Blogging.Blog.ClearCache` permission.
If custom code changes posts directly through `IPostRepository`, publish the module's `PostChangedEvent` with the affected blog ID. The built-in event handler then removes that blog's cached list as part of the current unit of work.
## Internals
### Domain Layer
The main aggregates are:
* `Blog`: A named blog identified in public URLs by its short name.
* `Post`: Markdown content, cover image, URL, tags and read count for a blog.
* `Comment`: A comment or direct reply attached to a post.
* `Tag`: A normalized tag and its usage count within a blog.
* `BlogUser`: The module-local representation of an Identity user and Blogging profile.
### Application Layer
The public application-service contracts are `IBlogAppService`, `IPostAppService`, `ICommentAppService`, `ITagAppService` and `IFileAppService`. `IBlogManagementAppService` provides blog administration operations.
The HTTP API uses the `Blogging` remote-service name for public operations and `BloggingAdmin` for administration operations. Add `BloggingHttpApiClientModule` or `BloggingAdminHttpApiClientModule` to a client application when these services run remotely.
### Database Providers
#### Common
The module uses `Blogging` as its connection string name and falls back to `Default` when that connection is not configured. See the [Connection Strings](../framework/fundamentals/connection-strings.md) documentation.
Tables and collections use the `Blg` prefix by default. Set `AbpBloggingDbProperties.DbTablePrefix` and, for providers that support schemas, `AbpBloggingDbProperties.DbSchema` before the database model is created:
```csharp
AbpBloggingDbProperties.DbTablePrefix = "MyBlog";
AbpBloggingDbProperties.DbSchema = "blogging";
```
#### Entity Framework Core
The Entity Framework Core provider uses these tables:
* `BlgUsers`
* `BlgBlogs`
* `BlgPosts`
* `BlgComments`
* `BlgTags`
* `BlgPostTags`
Call `ConfigureBlogging()` from your migration DbContext when integrating the module manually.
#### MongoDB
The MongoDB provider uses these collections:
* `BlgUsers`
* `BlgBlogs`
* `BlgPosts`
* `BlgComments`
* `BlgTags`
Post-tag links are stored with the post documents, so MongoDB does not use a separate `BlgPostTags` collection.
@ -36,7 +36,7 @@ If you modified your solution structure, adding module using ABP Suite might not
In order to do that, add packages listed below to matching project on your solution. For example, ```Volo.Chat.Application``` package to your **{ProjectName}.Application.csproj** like below;
@ -49,7 +49,7 @@ After adding the package reference, open the module class of the project (eg: `{
)]
```
> If you are using Blazor Web App, you need to add the `Volo.Chat.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and ad the `Volo.Chat.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
> If you are using Blazor Web App, you need to add the `Volo.Chat.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Chat.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
The `Volo.Chat.SignalR` package must be added according to your project structure:
@ -107,18 +107,38 @@ You can visit [Chat module package list page](https://abp.io/packages?moduleName
## User interface
### Manage chat feature
### Chat feature and permissions
Chat module defines the chat feature, you need to enable the chat feature to use chat.
The `Chat.Enable` feature is disabled by default. Enable it for a tenant or edition before granting chat permissions.

The module defines the following permissions:
* `Chat.Messaging`: Allows a user to open the chat page and exchange messages. A message can only be sent if the target user also has this permission.
* `Chat.Searching`: A child permission of `Chat.Messaging`. It allows a user to start a conversation with a user who is not already in the conversation history. Without this permission, the user can continue existing conversations.
* `Chat.SettingManagement`: Allows a user to manage the chat settings.
### Chat page
This is the page that users send messages to each other.

Message text is required. By default, it must contain between 1 and 4,096 characters. The conversation API returns the newest messages first; the built-in user interfaces reverse that result to display messages in chronological order.
### Deleting messages and conversations
The following settings control deletion behavior:
| Setting | Default | Description |
| --- | --- | --- |
| `Volo.Chat.Messaging.DeletingMessages` | `Enabled` | `Enabled` allows deletion at any time, `Disabled` prevents deletion, and `EnabledWithDeletionPeriod` allows deletion only during the configured period after the message is created. |
| `Volo.Chat.Messaging.MessageDeletionPeriod` | `0` | Deletion period in seconds when message deletion is set to `EnabledWithDeletionPeriod`. |
| `Volo.Chat.Messaging.DeletingConversations` | `Enabled` | Enables conversation deletion. It is effective only when message deletion is set to `Enabled`. |
Deleting a message removes the shared message and both users' message records. Deleting a conversation removes both users' conversation records and all messages between them. These operations are shared deletions, not a "hide for me" operation. Deletion notifications are sent through the real-time channel.
### Chat icon on navigation bar
An icon that shows unread message count of the user and leads to chat page when clicked is added to navigation menu.
@ -213,10 +233,11 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do
#### Installation
In order to configure the application to use the chat module, you first need to import `provideChatConfig` from `@volo/abp.ng.chat/config` to root application confiuration. Then, you will need to append it to the `appConfig` array.
In order to configure the application to use the chat module, you first need to import `provideChatConfig` from `@volo/abp.ng.chat/config` to the root application configuration. Then, you will need to append it to the `providers` array.
```js
```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideChatConfig } from '@volo/abp.ng.chat/config';
The chat module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. It is available for import from `@volo/abp.ng.chat`.
The chat module should be imported and lazy-loaded in your routing array. It exports a `createRoutes` function from `@volo/abp.ng.chat`.
```js
```ts
// app.routes.ts
import { Routes } from '@angular/router';
const APP_ROUTES: Routes = [
// ...
{
path: 'chat',
loadChildren: () =>
import('@volo/abp.ng.chat').then(c => c.createRoutes(/* options here */)),
The Angular chat route uses the `Chat.ChatComponent` replacement key and `ChatComponent` as its default component. See the [Angular component replacement documentation](../framework/ui/angular/component-replacement.md) if you need to replace the page.
#### Services / Models
Chat module services and models are generated via `generate-proxy` command of the [ABP CLI](../cli). If you need the module's proxies, you can run the following command in the Angular project directory:
The Chat module remote endpoint URLs can be configured in the environment files.
```js
```ts
export const environment = {
// other configurations
apis: {
@ -274,12 +301,24 @@ The Chat module remote URL configurations shown above are optional.
> If you don't set the `signalRUrl`, `Chat.url` will be used as fallback. If you don't set the `Chat` property, the `default.url` will be used as fallback.
### Blazor WebAssembly UI
### Blazor and MAUI UIs
#### Remote Endpoint URL
The SignalR base URL can be configured with the option type that matches the UI package:
| UI package | Option type | Fallback when `SignalrUrl` is not set |
| --- | --- | --- |
| Blazor Server | `ChatBlazorServerOptions` | The current application URL. |
This module defines an event for messaging. It is published when a new message is sent from a user to another user, with an Event Transfer Object type of `ChatMessageEto`. See the [standard distributed events](../framework/infrastructure/event-bus/distributed) for more information about distributed events.
The module defines the following Event Transfer Object types for real-time operations:
| `ChatMessageEto` | A message is sent. | `ReceiveMessage` |
| `ChatDeletedMessageEto` | A message is deleted. | `DeleteMessage` |
| `ChatDeletedConversationEto` | A conversation is deleted. | `DeleteConversation` |
The default application-layer implementation of `IRealTimeChatMessageSender` publishes these events to the distributed event bus. The `Volo.Chat.SignalR` package replaces that implementation in the SignalR host, handles the distributed events and sends them to the target user through the corresponding SignalR client method. See the [standard distributed events](../framework/infrastructure/event-bus/distributed) for more information about distributed events.
@ -13,25 +13,25 @@ CMS Kit provides a widget to create a contact form on your website.
## Enabling the Contact Management System
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time.
## Contact Widget
The contact management system provides a contact form [widget](../../framework/ui/mvc-razor-pages/widgets.md) to create contact forms on the UI:
The contact management system allows you to create multiple contact forms. You can define a named contact widget as below:
The contact management system allows you to create multiple contact forms with different receivers. You can define a named contact widget as shown below:
```csharp
@await Component.InvokeAsync(typeof(ContactViewComponent), new
@ -40,7 +40,7 @@ The contact management system allows you to create multiple contact forms. You c
});
```
Then, you need to configure the defined contact widgets in the `ConfigureServices` method of your module class:
Then, configure the receiver for each name in the `ConfigureServices` method of your module class:
When the submitted `contactName` matches a configured entry, that entry's receiver is used. Otherwise, the module uses the receiver email address configured on the CMS settings page. The contact name is also prefixed to the email subject when it is not empty.
## Options
You can configure the `CmsKitContactOptions` to enable/disable recaptcha for contact form in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md).
You can configure `CmsKitContactOptions` to enable or disable reCAPTCHA for the contact form in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md).
Example:
```csharp
Configure<CmsKitContactOptions>(options =>
{
options.IsRecaptchaEnabled = true; //false by default
options.IsRecaptchaEnabled = true;
});
```
`CmsKitContactOptions` properties:
* `IsRecaptchaEnabled` (default: false): This flag enables or disables the reCaptcha for the contact form. You can set it as **true** if you want to use reCaptcha in your contact form.
* `IsRecaptchaEnabled` (default: `false`): Enables reCAPTCHA v3 validation for public contact submissions.
If you set **IsRecaptchaEnabled** as **true**, you also need to specify **SiteKey** and **SiteSecret** options for reCaptcha. To do that, add **CmsKit:Contact** section into your `appsettings.json` file:
If you set `IsRecaptchaEnabled` to `true`, also specify `SiteKey` and `SiteSecret` for reCAPTCHA. Add the `CmsKit:Contact` section to your `appsettings.json` file:
```json
{
@ -85,9 +86,9 @@ If you set **IsRecaptchaEnabled** as **true**, you also need to specify **SiteKe
}
```
## Settings
## Settings
You can configure the receiver (email address) by using the CMS tab in the settings page.
You can configure the fallback receiver email address on the CMS tab of the settings page. This setting is tenant-aware and is used when the form has no matching named receiver. Its default value is `info@mycompanyname.com`; replace it with an address that belongs to your application before deploying to production.
> You must have an [ABP Team or a higher license](https://abp.io/pricing) to use CMS Kit Pro module's features.
The CMS kit provides a **FAQ** system to allow users to create, edit and delete FAQ's. Here is a screenshot of the FAQ widget:
CMS Kit Pro provides an **FAQ** system to organize questions into groups and sections and display them on public pages. Here is a screenshot of the FAQ widget:
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time.
## User Interface
@ -25,21 +25,21 @@ By default, CMS Kit features are disabled. Therefore, you need to enable the fea
CMS Kit module admin side adds the following items to the main menu, under the **CMS** menu item:
**FAQ's**: FAQ management page.
**FAQs**: FAQ group, section and question management page.
`CmsKitProAdminMenus` class has the constants for the menu item names.
### Pages
You can list, create, update and delete sections and their questions FAQ's on the admin side of your solution.
You can list, create, update and delete FAQ groups, sections and questions on the admin side of your solution. A group contains sections, and a section contains questions.
The FAQ system provides a FAQ [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to display FAQ's. You can place the widget on a page like below:
The FAQ system provides an FAQ [widget](../../framework/ui/mvc-razor-pages/widgets.md) for displaying FAQs. You can place the widget on a page as shown below:
```csharp
@await Component.InvokeAsync(
@ -47,30 +47,20 @@ The FAQ system provides a FAQ [widget](../../framework/ui/mvc-razor-pages/widget
new
{
groupName = "Community",
name = "Development"
sectionName = "Development"
})
```
`FaqViewComponent` parameters:
- `groupName` (optional): It allows to specify which FAQ group to show. If not specified, all groups will be shown.
- `sectionName` (optional): It is used to determine which section within the specified group will be shown. If not specified, all sections in the related group will be shown.
The FAQ system can also be used in combination with the [dynamic widget](../cms-kit/dynamic-widget.md) feature.
## Options
The FAQ system provides a mechanism to group sections by group name. For example, if you want to use the FAQ system for community and support page, you need to define two group names named Community and Support and add sections under these groups. So, before using the FAQ system, you need to define groups. For that, you can use `FaqOptions`. `FaqOptions` can be configured at the domain layer, in the `ConfigureServices` method of your [module]../../framework/architecture/modularity/basics.md).
- `groupName` (required): Specifies the FAQ group to show. Create this group on the FAQ administration page before rendering the widget.
- `sectionName` (optional): Specifies a section within the selected group. If it is not set, all sections in the group are shown.
The FAQ system can also be used in combination with the [dynamic widget](../cms-kit/dynamic-widget.md) feature.
`FaqOptions` properties:
## FAQ Groups
- `Groups`: Dictionary of defined groups in the FAQ system. The `options.SetGroups` method is a shortcut to add a new groups to this dictionary.
FAQ groups are persisted data and are managed from the FAQ administration page. Create groups such as `Community` or `Support`, then assign each section to one of those groups. Group names must be unique.
## Internals
@ -82,10 +72,11 @@ This module follows the [Entity Best Practices & Conventions](../../framework/ar
##### FAQ
A FAQ represents a generated FAQ with its questions:
An FAQ represents a generated FAQ with its questions:
- `FaqSection` (aggregate root): Represents the defined FAQ sections related to the FAQ in the system.
- `FaqQuestion` (aggregate root): Represents the defined FAQ questions with section identifier related to the FAQ in the system.
- `FaqGroup` (aggregate root): Represents a named group that contains FAQ sections.
#### Repositories
@ -95,6 +86,7 @@ The following special repositories are defined for these features:
- `IFaqSectionRepository`
- `IFaqQuestionRepository`
- `IFaqGroupRepository`
#### Domain services
@ -108,7 +100,9 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
- `FaqSectionAdminAppService` (implements `IFaqSectionAdminAppService`): Implements the use cases of FAQ section management for admin side.
- `FaqQuestionAdminAppService` (implements `IFaqQuestionAdminAppService`): Implements the use cases of FAQ question management for admin side.
- `FaqSectionPublicAppService` (implements `IFaqSectionPublicAppService`): Implements the use cases of FAQ's for public websites.
- `FaqGroupAdminAppService` (implements `IFaqGroupAdminAppService`): Implements the use cases of FAQ group management for admin side.
- `FaqSectionPublicAppService` (implements `IFaqSectionPublicAppService`): Implements the use cases of FAQs for public websites.
- `FaqGroupPublicAppService` (implements `IFaqGroupPublicAppService`): Finds FAQ groups by name for public widgets.
### Database providers
@ -116,11 +110,11 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it falls back to the `Default` connection string.
See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details.
@ -130,6 +124,7 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
- CmsFaqSections
- CmsFaqQuestions
- CmsFaqGroups
#### MongoDB
@ -137,7 +132,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
- CmsFaqSections
- CmsFaqQuestions
## Entity Extensions
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the FAQ Feature of the CMS Kit Pro module.
This module extends the [open-source CMS Kit module](../cms-kit) and adds additional CMS (Content Management System) capabilities to your application.
> **This module is currently available for MVC / Razor Pages and Blazor UIs**.
The administration UI is available for MVC / Razor Pages, Angular and Blazor (Blazorise and MudBlazor). The public website widgets documented in the feature pages are MVC / Razor Pages components.
The following features are provided by the open-source CMS Kit module:
@ -30,10 +30,10 @@ The following features are provided by the CMS Kit Pro version:
* [**Newsletter**](newsletter.md) It allows users to subscribe to newsletters.
* [**Contact form**](contact-form.md) It allows users to write messages to you.
* [**URL forwarding**](URL-forwarding.md) It allows the creation of URLs that point to other pages or external websites.
* [**Poll**](poll.md) It allows to create simple polls for your visitors.
* [**URL forwarding**](url-forwarding.md) It allows the creation of URLs that point to other pages or external websites.
* [**Poll**](poll.md) Allows you to create simple polls for your visitors.
* [**Page Feedback**](page-feedback.md) It allows users to send feedback for your pages.
* [**Faq**](faq.md) system to create dynamic FAQ.
* [**FAQ**](faq.md) system to create dynamic FAQs.
Click on a feature to understand and learn how to use it. See [the module description page](https://abp.io/modules/Volo.CmsKit.Pro) for an overview of the module features.
@ -41,7 +41,7 @@ Click on a feature to understand and learn how to use it. See [the module descri
### New Solutions
CMS Kit Pro is pre-installed in [the startup templates](../../solution-templates) if you create the solution with the **public website** option. If you are using ABP CLI, you should specify the the `--with-public-website` option as shown below:
CMS Kit Pro is pre-installed in [the startup templates](../../solution-templates) if you create the solution with the **public website** option. If you are using ABP CLI, specify the `--with-public-website` option as shown below:
```bash
abp new Acme.BookStore --with-public-website
@ -49,7 +49,7 @@ abp new Acme.BookStore --with-public-website
### Existing Solutions
If you want to add the CMS kit to your existing solution, you can use the ABP CLI `add-module` command:
If you want to add CMS Kit Pro to your existing solution, you can use the ABP CLI `add-module` command:
```bash
abp add-module Volo.CmsKit.Pro
@ -77,11 +77,47 @@ Alternatively, you can enable features individually, like `cmsKit.Comments.Enabl
> If you are using Entity Framework Core, remember to add a new migration and update your database.
### Angular Administration UI
The Angular package publishes the administration routes and their menu configuration in separate entry points. Register the configuration provider in your application configuration:
```typescript
import { ApplicationConfig } from '@angular/core';
import { provideCmsKitAdminConfig } from '@abp/ng.cms-kit/admin/config';
import { provideCmsKitProAdminConfig } from '@volo/abp.ng.cms-kit-pro/admin/config';
[Module entity extension](../../framework/architecture/modularity/extending/module-entity-extensions.md) system is a **high-level** extension system that allows you to **define new properties** for existing entities of the dependent modules. It automatically **adds properties to the entity**, **database**, **HTTP API and user interface** in a single point.
The [module entity extension](../../framework/architecture/modularity/extending/module-entity-extensions.md) system allows you to define new properties for supported entities of a dependent module from a single configuration point.
To extend entities of the CMS Kit Pro module, open your `YourProjectNameModuleExtensionConfigurator` class inside of your `DomainShared` project and change the `ConfigureExtraProperties` method like shown below.
To extend entities of the CMS Kit Pro module, open your `YourProjectNameModuleExtensionConfigurator` class in the `Domain.Shared` project and change the `ConfigureExtraProperties` method as shown below.
```csharp
public static void ConfigureExtraProperties()
@ -91,49 +127,44 @@ public static void ConfigureExtraProperties()
ObjectExtensionManager.Instance.Modules()
.ConfigureCmsKitPro(cmsKitPro =>
{
cmsKitPro.ConfigurePoll(plan => // extend the Poll entity
property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property
property.Attributes.Add(
new StringLengthAttribute(MyConsts.MaximumDescriptionLength) {
MinimumLength = MyConsts.MinimumDescriptionLength
}
);
//...other configurations for this property
}
newsletterRecord.AddOrUpdateProperty<string>(
"NewsletterRecordDescription",
property =>
{
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(
new StringLengthAttribute(MyConsts.MaximumDescriptionLength)
{
MinimumLength = MyConsts.MinimumDescriptionLength
}
);
}
);
});
});
});
});
}
```
* `ConfigureCmsKitPro` method is used to configure the entities of the CMS Kit Pro module.
* `cmsKit.ConfigurePoll(...)` is used to configure the **Poll** entity of the CMS Kit Pro module. You can add or update the extra properties of the **Poll** entity.
* `cmsKitPro.ConfigurePoll(...)` is used to configure the **Poll** entity of the CMS Kit Pro module. You can add or update the extra properties of the **Poll** entity.
* `cmsKit.ConfigureNewsletterRecord(...)` is used to configure the **NewsletterRecord** entity of the CMS Kit Pro module. You can add or update the extra properties of the **NewsletterRecord** entity.
* `cmsKitPro.ConfigureNewsletterRecord(...)` is used to configure the **NewsletterRecord** entity of the CMS Kit Pro module. You can add or update the extra properties of the **NewsletterRecord** entity.
* You can also set some validation rules for the property that you defined. In the above sample, `RequiredAttribute` and `StringLengthAttribute` were added for the property named **"NewsletterRecord"**.
* You can also set validation rules for the properties you define. In the example above, `RequiredAttribute` and `StringLengthAttribute` are added to the **NewsletterRecordDescription** property.
* When you define the new property, it will automatically add to **Entity**, **HTTP API** and **UI** for you.
* Once you define a property, it appears in the create and update forms of the related entity.
* New properties also appear in the data table on the related page.
* Extra properties are added to the entity and HTTP API. UI integration is entity- and UI-specific: Poll has create and update forms, while Newsletter records are read-only and expose their extra properties through the application DTOs.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time.
## User Interface
@ -29,20 +29,19 @@ By default, CMS Kit features are disabled. Therefore, you need to enable the fea
#### Newsletters
You can then view the subscribers and export the list as CSV file, in the admin side of your solution:
You can view subscribers, edit their preferences, import subscriptions from a CSV file and export the filtered list as a CSV file on the admin side of your solution:
You (and users of your public web application) can manage your email preferences and unsubscribe from newsletters by visiting the **Email Preferences page** (*/cms/newsletter/email-preferences*), in the public side of your solution:
Users can manage their email preferences and unsubscribe from newsletters on the public **Email Preferences** page at `/cms/newsletter/email-preferences`:
The newsletter subscription system provides a newsletter subscription [widget](../../framework/ui/mvc-razor-pages/widgets.md) to allow users to subscribe to a newsletter.
You can simply place the widget on a page like below:
The newsletter subscription system provides a newsletter subscription [widget](../../framework/ui/mvc-razor-pages/widgets.md) to allow users to subscribe to a newsletter. You can place the widget on a page as shown below:
```csharp
@await Component.InvokeAsync(
@ -52,10 +51,12 @@ You can simply place the widget on a page like below:
preference = "TechNewsletter",
source = "Footer",
requestAdditionalPreferencesLater = false
})
})
```
When you're adding the newsletter component, you can the specify `source` parameter to see where users subscribe to newsletters. See the options to understand the preferences.
The `preference` and `source` parameters are required. `preference` must match a registered preference. Use `source` to distinguish where subscriptions originate, such as `Footer` or `Blog`. If `requestAdditionalPreferencesLater` is `true`, the widget requests the additional subscriptions in the success dialog instead of the initial form. You can also pass `privacyPolicyConfirmation` to override the preference's configured privacy-policy text for that widget instance.
New subscriptions require email confirmation. Once confirmed, users can manage all registered preferences from `/cms/newsletter/email-preferences`; disabling every preference removes the subscription record.
## Options
@ -64,25 +65,67 @@ Before using the newsletter system, you need to define the preferences. You can
**Example:**
```csharp
options.AddPreference("TechNewsletter",
new NewsletterPreferenceDefinition(
"Daily Technology Newsletter",
privacyPolicyConfirmation: "I accept the <ahref='/privacy-policy'>Privacy Policy</a>.")
)
);
Configure<NewsletterOptions>(options =>
{
options.AddPreference(
"ProductUpdates",
new NewsletterPreferenceDefinition(
new LocalizableString(
typeof(MyProjectResource),
"Newsletter:ProductUpdates")
)
);
options.AddPreference(
"TechNewsletter",
new NewsletterPreferenceDefinition(
new LocalizableString(
typeof(MyProjectResource),
"Newsletter:TechNewsletter"),
definition: new LocalizableString(
typeof(MyProjectResource),
"Newsletter:TechNewsletterDescription"),
privacyPolicyConfirmation: new LocalizableString(
typeof(MyProjectResource),
"Newsletter:PrivacyPolicyConfirmation"),
additionalPreferences: new List<string> { "ProductUpdates" }
)
);
});
```
`NewsletterOptions` properties:
- `Preferences`: List of defined newsletter preferences (`NewsletterPreferenceDefinition`) in the newsletter system.
- `Preferences`: Dictionary of registered preference names and their `NewsletterPreferenceDefinition` values.
- `WidgetViewPath`: Default view path for all newsletter preferences.
`NewsletterPreferenceDefinition` properties:
- `Preference`: Name of the preference. We will use this field while displaying the newsletter component on the UI.
- `PrivacyPolicyConfirmation`: Privacy policy confirmation text shown in the newsletter subscription widget.
- `AdditionalPreferences`: Additional preference list that will show up after a user subscribes to the newsletter.
- `WidgetPath`: If you want to use a different newsletter widget instead of the default widget, you can specify the newsletter widget path using this field.
- `DisplayPreference`: Localizable display name of the preference.
- `Definition`: Optional localizable description shown on the email preferences page.
- `PrivacyPolicyConfirmation`: Privacy policy confirmation text for the newsletter subscription widget. The preference-level value currently reaches the widget only when the selected definition has a non-empty `AdditionalPreferences` list; otherwise the service returns before localizing this value. Pass `privacyPolicyConfirmation` when invoking the widget if you need an override that is independent of that list.
- `AdditionalPreferences`: Names of other registered preferences that participate in the additional-preference flow.
- `WidgetViewPath`: Optional Razor view path for this preference. It overrides the default `NewsletterOptions.WidgetViewPath`.
The widget uses `~/Pages/Public/Shared/Components/Newsletter/Default.cshtml` when neither view-path option is set.
The current implementation first checks whether the selected preference has a non-empty `AdditionalPreferences` list. If it does, the service collects registered preference names referenced by the `AdditionalPreferences` lists of all registered definitions, excludes the selected preference and removes duplicates. If the selected preference has no additional preferences, the widget does not offer any. Keep this global collection behavior in mind when multiple definitions reference different additional preferences.
### Email Preferences Page Options
Use `NewsletterPreferencesManagementOptions` to set the source recorded for changes made on the email preferences page and an optional privacy-policy confirmation message:
options.PrivacyPolicyConfirmation = new LocalizableString(
typeof(MyProjectResource),
"Newsletter:PrivacyPolicyConfirmation"
);
});
```
## Internals
@ -94,7 +137,7 @@ This module follows the [Entity Best Practices & Conventions](../../framework/ar
##### NewsletterRecord
A newsletter record represents a newsletter subscription for a specific email address
A newsletter record represents a newsletter subscription for a specific email address.
- `NewsletterRecord` (aggregate root): Represents a newsletter subscription in the system.
@ -127,11 +170,11 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it falls back to the `Default` connection string.
See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details.
@ -150,4 +193,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
## Entity Extensions
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Newsletter Feature of the CMS Kit Pro module.
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Newsletter Feature of the CMS Kit Pro module.
@ -15,9 +15,9 @@ The CMS Kit Pro module provides a comprehensive **Page Feedback** system that en
## Enabling the Page Feedback System
All CMS Kit features are disabled bu default. Therefore, you need to enable the features you want before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable the CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime.
All CMS Kit features are disabled by default. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time.
## User Interface
@ -27,13 +27,13 @@ The CMS Kit module admin side adds the following items to the main menu, under t
The `CmsKitProAdminMenus` class has the constants for the menu items names.
The `CmsKitProAdminMenus` class defines the menu item name constants.
### Pages
#### Page Feedbacks
You can list, view, update and delete page feedbacks in the admin side of your solution, and you can also set the email addresses to send notifications.
You can list, view, update and delete page feedback from the administration interface. You can also configure the email addresses that receive notifications.
@ -42,7 +42,16 @@ You can list, view, update and delete page feedbacks in the admin side of your s
## Page Feedback Widget
The page feedback system provides a page feedback [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to send feedback about the current page. You can place the widget on a page like the below:
The page feedback system accepts only registered entity types. Register the entity type in the domain layer before rendering its widget:
You can then place the page feedback [widget](../../framework/ui/mvc-razor-pages/widgets.md) on a page:
```csharp
@(await Component.InvokeAsync(typeof(PageFeedbackViewComponent), new PageFeedbackViewDto
@ -59,8 +68,12 @@ The page feedback system provides a page feedback [widget](../../framework/ui/mv
- `YesButtonText`: Yes button text. Used to change the default text of the yes button. Default value is `Yes`.
- `VeryHelpfulText`: Description shown with the positive feedback choice.
- `NoButtonText`: No button text. Used to change the default text of the no button. Default value is `No`.
- `NeedsImprovementText`: Description shown with the negative feedback choice.
- `UserNotePlaceholder`: User note placeholder. Used to change the default placeholder of the user note input.
- `SubmitButtonText`: Submit button text. Used to change the default text of the submit button. Default value is `Submit`.
@ -77,7 +90,7 @@ The page feedback system provides a page feedback [widget](../../framework/ui/mv
### Page Feedback Modal Widget
The page feedback system provides a page feedback modal [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to send feedback about the current page. You can place the widget on a page like the below:
The page feedback system provides a page feedback modal [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to send feedback about the current page. You can place the widget on a page as shown below:
@ -95,42 +108,38 @@ The page feedback system provides a page feedback modal [widget](../../framework
### PageFeedbackModalViewDto Properties
It inherits from the [PageFeedbackViewDto](#pagefeedbackviewdto-properties) and has the following additional properties:
`PageFeedbackModalViewDto` inherits from [PageFeedbackViewDto](#pagefeedbackviewdto-properties) and adds the following property:
- `ModalId`: Modal id. Used to set the id of the modal. Default value is `page-feedback-modal`.
The current modal component does not forward the inherited `ReverseButtons` and `HeaderVisible` values to its rendered model. These two properties work with `PageFeedbackViewComponent`, but setting them on `PageFeedbackModalViewDto` has no effect. The other inherited widget text and entity properties are forwarded by the modal component.
## Page Feedback Notification
The page feedback system sends an email notification to the configured email addresses when a user sends a feedback. You can configure the email addresses from the admin side of your solution.
The page feedback system sends an email notification to the configured email addresses when feedback includes a user note. You can configure addresses for each entity type and a default fallback from the admin side of your solution.
The CMS settings page also provides these tenant-aware behavior settings:
- **Automatically handle feedback without comments** (default: `true`): Marks new feedback as handled when no user note is provided.
- **Require comments for negative feedback** (default: `false`): When a new negative feedback record is created, rejects it if no user note is provided. This setting is not re-evaluated when an existing record's usefulness value is changed.
## Options
The page feedback system provides a mechanism to group feedbacks by entity types. For example, you can group feedbacks by pages, blog posts, etc.
`CmsKitPageFeedbackOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md) class.
**Example: Adding page feedback support for the post entity type**
- `EntityTypes`: A list of the defined entity types(`PageFeedbackEntityTypeDefinition`) in the page feedback system.
- `EntityTypes`: A list of the defined entity types (`PageFeedbackEntityTypeDefinition`) in the page feedback system.
`PageFeedbackEntityTypeDefinition` properties:
- `EntityType`: Name of the entity type.
- `DisplayName`: Display name of the entity type. You can use a user friendly display name to show the entity type definition on the admin website.
- `CreatePolicies`: List of policy/permission names allowing users to create tags under the entity type.
- `UpdatePolicies`: List of policy/permission names allowing users to update tags under the entity type.
- `DeletePolicies`: List of policy/permission names allowing users to delete tags under the entity type.
The earlier `Page` example registers the same entity type used by both widget examples. Only registered entity types can accept feedback or have entity-specific notification settings.
## Internals
@ -144,13 +153,13 @@ This module follows the [Entity Best Practices & Conventions](../../framework/ar
A page feedback is a feedback sent by a user about a page.
- `PageFeedback`(Aggregate Root): Represents a page feedback.
- `PageFeedback`(Aggregate Root): Represents a page feedback.
##### PageFeedbackSetting
A page feedback setting is a setting to configure the page feedback system.
- `PageFeedbackSetting`(Aggregate Root): Represents a page feedback setting.
- `PageFeedbackSetting`(Aggregate Root): Represents a page feedback setting.
#### Repositories
@ -173,8 +182,8 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
#### Application services
- `PageFeedbackAdminAppService`(implements `IPageFeedbackAdminAppService`): Used to manage page feedbacks in the admin side of your solution.
- `PageFeedbackPublicAppService`(implements `IPageFeedbackPublicAppService`): Used to manage page feedbacks in the public side of your solution.
- `PageFeedbackAdminAppService`(implements `IPageFeedbackAdminAppService`): Manages page feedback from the administration interface.
- `PageFeedbackPublicAppService`(implements `IPageFeedbackPublicAppService`): Implements the public page feedback use cases.
### Database providers
@ -182,11 +191,11 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties in the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it falls back to the `Default` connection string.
See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details.
@ -203,7 +212,3 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
- **CmsPageFeedbacks**
- **CmsPageFeedbackSettings**
## Entity Extensions
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Poll Feature of the CMS Kit Pro module.
@ -15,9 +15,9 @@ CMS Kit provides a **poll** system to allow users to create, edit and delete pol
## Enabling the Poll System
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time.
## User Interface
@ -41,7 +41,7 @@ You can list, create, update and delete polls on the admin side of your solution
## Poll Widget
The poll system provides a poll [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to vote and show the result. You can place the widget on a page like the below:
The poll system provides a poll [widget](../../framework/ui/mvc-razor-pages/widgets.md) for users to vote and view the result. You can place the widget on a page as shown below:
```csharp
@await Component.InvokeAsync(
@ -49,9 +49,26 @@ The poll system provides a poll [widget](../../framework/ui/mvc-razor-pages/widg
new
{
widgetName = "my-poll-1"
})
```
`PollViewComponent` selects a poll assigned to `widgetName` through the available-widget lookup. The repository returns a poll when either its voting window is open (`StartDate` has passed and `EndDate` has not passed) or its `ResultShowingEndDate` has not passed. The component then prevents rendering before `StartDate` and after `ResultShowingEndDate`, when that value is set. Consequently, the named widget can remain visible after `EndDate` during the result-showing period. The widget name must first be registered with `CmsKitPollingOptions`, and polls are assigned to widget names from the administration page.
To render one specific poll without registering a widget name, use its unique code:
```csharp
@await Component.InvokeAsync(
typeof(PollByCodeViewComponent),
new
{
code = "developer-survey"
})
```
`PollByCodeViewComponent` performs a direct lookup by code. It does not use the named widget's repository availability condition or component date checks, so it can render a poll before `StartDate` or after `ResultShowingEndDate`. Use it only when the hosting page applies the required availability rules.
Submitting a vote requires an authenticated user. The public vote service currently does not enforce the poll's start date, end date or result-showing end date on the server, so the host must prevent submissions outside the intended voting period. A user can submit only once for a poll; the poll's **Allow multiple vote** option controls whether that single submission can contain multiple options.
## Options
Before using the poll system, you need to define the widgets. You can use the `CmsKitPollingOptions`. `CmsKitPollingOptions` can be configured in the domain layer, in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md).
@ -60,14 +77,14 @@ Before using the poll system, you need to define the widgets. You can use the `C
```csharp
Configure<CmsKitPollingOptions>(options =>
{
options.AddWidget("my-poll-1");
});
{
options.AddWidget("my-poll-1");
});
```
`CmsKitPollingOptions` properties:
- `WidgetNames`: List of defined widgets in the poll system. `options.AddWidget` method was a shortcut to add a new widget to this list.
- `WidgetNames`: List of defined widget names in the poll system. Use `options.AddWidget` to add a unique name; adding the same name twice throws an exception.
## Internals
@ -79,14 +96,14 @@ This module follows the [Entity Best Practices & Conventions](../../framework/ar
##### Poll
A poll represents a created poll with its options:
A poll represents a created poll with its options:
- `Poll` (aggregate root): Represents a poll by including the options in the system.
- `PollOption` (entity): Represents the defined poll options related to the poll in the system.
##### PollUserVote
A poll user vote represents voted poll from a user:
A poll user vote represents a user's vote in a poll:
- `PollUserVote` (aggregate root): Represents poll user votes in the system.
@ -120,11 +137,11 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it falls back to the `Default` connection string.
See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details.
@ -145,4 +162,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
## Entity Extensions
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Poll Feature of the CMS Kit Pro module.
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Poll Feature of the CMS Kit Pro module.
@ -13,23 +13,32 @@ CMS Kit provides a **URL forwarding** system to create URLs that redirect to oth
## Enabling the URL Forwarding System
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature on runtime.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before starting to use them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use ABP's [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
In addition to enabling [Url Shorting global feature](index.md), you need to add `UrlShortingMiddleware` to your final application.
In addition to enabling the [URL Forwarding global feature](index.md#how-to-install), add `UrlShortingMiddleware` to the final web application. Register it before middleware that may produce a `404 Not Found` response so it can evaluate unmatched request paths after the rest of the pipeline runs.
```csharp
using Volo.CmsKit.Pro.Public.Web.Middlewares;
.
.
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseMiddleware<UrlShortingMiddleware>();
.
.
public override void OnApplicationInitialization(
ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseMiddleware<UrlShortingMiddleware>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseConfiguredEndpoints();
}
```
This example focuses on the relative middleware order. Keep the other middleware required by your application in the pipeline.
The middleware looks up the decoded request path and query string when the downstream pipeline returns `404`. Matching exact or regular-expression rules return a permanent redirect. Same-host relative targets must start with `/`, for example `/products/current`; the middleware expands them using the current request scheme and host. Absolute targets are used as absolute destinations.
- `PreventRegexLoop` (default: `true`): Adds and checks a tracking query-string value when a regular-expression rule redirects to the same host.
- `TrackingQueryStringParameter` (default: `__redirect`): Name of that tracking query-string parameter.
- `OnConflict`: Selects a rule when more than one regular expression matches. The default selects the first matching rule.
## Internals
## Domain Layer
### Domain Layer
#### Aggregates
@ -91,11 +124,11 @@ Following custom repositories are defined for this feature:
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it falls back to the `Default` connection string.
See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details.
@ -15,6 +15,8 @@ By default, CMS Kit features are disabled. Therefore, you need to enable the fea
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> The built-in MVC blog routes are registered by the Pages global feature. Enable both `Blogs` and `Pages` when you use the built-in public blog pages.
## User Interface
### Menu Items
@ -38,7 +40,7 @@ A screenshot from the new blog creation modal:
**Slug** is the URL part of the blog. For this example, the root URL of the blog becomes `your-domain.com/blogs/technical-blog/`.
- You can change the default slug by using `CmsBlogsWebConsts.BlogRoutePrefix` constant. For example, if you set it to `foo`, the root URL of the blog becomes `your-domain.com/foo/technical-blog/`.
- You can change the default route prefix by using the `CmsBlogsWebConsts.BlogsRoutePrefix` property. For example, if you set it to `foo`, the root URL of the blog becomes `your-domain.com/foo/technical-blog/`.
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
@ -49,7 +51,7 @@ A screenshot from the new blog creation modal:
#### Blog Features
Blog feature uses some of the other CMS Kit features. You can enable or disable the features by clicking the features action for a blog.
The blogging feature uses other CMS Kit features. A newly created blog enables comments, reactions, ratings, tags, marked items, the quick navigation bar and XSS prevention by default; where a corresponding global feature exists, it still controls whether the blog feature takes effect. You can enable or disable these features for each blog by clicking the features action.
Blog posts have three statuses: `Draft`, `WaitingForReview` and `Published`. Creating, updating, deleting and publishing posts use separate permissions. The public blog list requests only published posts and can filter them by author, tag or the current user's marked items.
The built-in renderer allows HTML in blog post Markdown. The per-blog **Prevent XSS** feature controls whether the Markdown renderer sanitizes that HTML and is enabled for newly created blogs. Keep it enabled when post authors are not trusted to submit arbitrary HTML.
## Internals
### Domain Layer
@ -138,4 +144,4 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
## Entity Extensions
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Blogging Feature of the CMS Kit module.
Check the ["Entity Extensions" section of the CMS Kit Module documentation](index.md#entity-extensions) to see how to extend entities of the Blogging Feature of the CMS Kit module.
- `EntityTypes`: List of defined entity types(`CmsKitCommentOptions`) in the comment system.
- `EntityTypes`: List of defined entity types (`CommentEntityTypeDefinition`) in the comment system.
- `IsRecaptchaEnabled`: This flag enables or disables the reCaptcha for the comment system. You can set it as **true** if you want to use reCaptcha in your comment system.
- `AllowedExternalUrls`: Indicates the allowed external URLs by entity types, which can be included in a comment. If it's specified for a certain entity type, then only the specified external URLs are allowed in the comments.
- `AllowedExternalUrls`: The allowed external URLs for each entity type. When it is specified for an entity type, every detected HTTP(S) URL in a comment text is checked, and the comment is rejected when a detected URL doesn't include any of the configured values. The comparison is a case-insensitive substring check on the normalized URLs, not an exact origin match.
`CommentEntityTypeDefinition` properties:
@ -67,6 +67,17 @@ The comment system provides a commenting [widget](../../framework/ui/mvc-razor-p
`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here. `referralLinks` is an optional parameter. You can use this parameter to add values (such as "nofollow", "noreferrer", or any other values) to the [rel attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel) of links.
Creating, updating and deleting a comment requires an authenticated user. Users can update only their own comments. They can delete their own comments, while the `CmsKitPublic.Comments.DeleteAll` permission allows deleting comments created by other users. Deleting a comment also deletes its direct replies.
Reactions are enabled inside the MVC comments widget by default when the Reactions global feature is available. Disable them without disabling reactions for other entity types by configuring the UI options in the web project:
@ -89,7 +100,7 @@ You can also view and manage replies on this page.
## Settings
You can configure the approval status of comments using the "Comment" tab under the "Cms" section on the Settings page. When this feature is enabled, you can approve and reject comments. In this way, users can only see the comments that you approve. By default, this feature is set to "false."
You can configure the approval status of comments using the **Comment** tab under the **Cms** section on the Settings page. When approval is required, new comments wait for an administrator and public queries return only approved comments. When approval is disabled, waiting comments are also visible. The setting is stored globally, not per tenant, and is `false` by default. Changing it requires the `CmsKit.Comments.SettingManagement` permission.
@ -136,7 +147,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
@ -155,4 +166,3 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
In this image, after choosing your widget (on the other case, it changes automatically up to your configuration, mine is `Today`. Its parameter name `parameterWidgetName` and its value is `Format`) you will see the next widget. Enter input values or choose them and click `Add`. You will see the underlined output in the editor. Right of the image, also you can see its previewed output.
You can edit this output manually if do any wrong coding for that (wrong value or typo) you won't see the widget, even so, your page will be viewed successfully.
The stored widget markup is parsed when a page or blog post is rendered. When at least one widget has been registered, an unknown widget type is omitted from the rendered fragments. If no widgets have been registered, the stored markup remains in a Markdown fragment. If a registered view component throws while rendering, CMS Kit keeps the rest of the page visible, renders a localized error alert for that fragment and writes the exception to the application log.
## Options
@ -146,4 +146,6 @@ The `CmsKitContentWidgetOptions` provides two methods for registering widgets:
- `parameterWidgetName` (optional): The name of the parameter widget that will be displayed in the "Add Widget" modal to collect parameter values from users. This is only required when your widget needs parameters.
- **AddWidgetIfFeatureEnabled:** Registers a widget conditionally, only if a specified [global feature](../../framework/infrastructure/global-features.md) is enabled. It accepts the same parameters as `AddWidget`, plus an additional first parameter:
- `featureType` (required): The type of the global feature that must be enabled for the widget to be available (e.g., `typeof(PagesFeature)`).
- `featureType` (required): The type of the global feature that must be enabled for the widget to be available (e.g., `typeof(PagesFeature)`).
The registration is shared by the administration editor and the public content parser. `AddWidgetIfFeatureEnabled` evaluates the global feature while the module configures its services; it does not use the runtime tenant feature system.
The built-in public web module adds the style resource at `LayoutHooks.Head.Last` and the script resource at `LayoutHooks.Body.Last`. The resources are served from `/cms-kit/global-resources/style` and `/cms-kit/global-resources/script`. A cache miss stores the loaded resource in the distributed cache with a two-minute absolute expiration. Updating an existing resource refreshes its cached value through a local entity event and uses the configured default cache options.
> Global resources are trusted administrator input. The style is returned as CSS and the script is returned as executable JavaScript on every public page that uses the layout hooks. Grant `CmsKit.GlobalResources` only to users who are allowed to execute code in visitors' browsers, and apply your Content Security Policy accordingly.
# Internals
## Domain Layer
@ -76,4 +80,4 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
@ -11,7 +11,7 @@ This module provides CMS (Content Management System) capabilities for your appli
> You can see the live demo at [cms-kit-demo.abpdemo.com](https://cms-kit-demo.abpdemo.com/).
> **This module currently available only for the MVC / Razor Pages UI**. While there is no official Blazor package, it can also work in a Blazor Server UI since a Blazor Server UI is actually a hybrid application that runs in an ASP.NET Core MVC / Razor Pages application.
> CMS Kit provides MVC / Razor Pages packages for both the administration and public websites. The `@abp/ng.cms-kit` package provides an Angular administration UI. There is no official Blazor package; a Blazor Server application can host the MVC / Razor Pages UI because it runs on ASP.NET Core.
The following features are currently available:
@ -28,7 +28,12 @@ The following features are currently available:
> You can click on the any feature links above to understand and learn how to use it.
All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, with the help of the [Global Features](../../framework/infrastructure/global-features.md) system.
CMS Kit uses two feature layers:
* [Global Features](../../framework/infrastructure/global-features.md) select the CMS Kit subsystems included in the application model. When using Entity Framework Core, changing these features requires a new migration because disabled entities are excluded from the EF Core model.
* The [Feature System](../../framework/infrastructure/features.md) can enable or disable the corresponding subsystem at runtime for a tenant or another feature value provider. Runtime feature changes do not change the database model.
Most subsystems can be selected independently. The built-in MVC blog pages are currently registered together with the Pages global feature, so enable both `Blogs` and `Pages` when you use the built-in public blog UI.
## Pre Requirements
@ -38,6 +43,23 @@ All features are individually usable. If you disable a feature, it completely di
- CMS Kit uses [distributed cache](../../framework/fundamentals/caching.md) for responding faster.
> Using a distributed cache, such as [Redis](../../framework/fundamentals/redis-cache.md), is highly recommended for data consistency in distributed/clustered deployments.
## Media Storage and Entity Types
When the Media global feature is enabled, CMS Kit registers media definitions for blog posts and pages. To upload media for another entity type, register a `MediaDescriptorDefinition` and specify the permissions that can create and delete its media:
```csharp
Configure<CmsKitMediaOptions>(options =>
{
options.EntityTypes.Add(
new MediaDescriptorDefinition(
"Product",
createPolicies: new[] { "Products.Update" },
deletePolicies: new[] { "Products.Update" }));
});
```
The administration service grants an operation when the current user has any policy in the corresponding list. An empty list grants no access. Media files are downloaded from the anonymous `GET /api/cms-kit/media/{id}` endpoint, so this facility is for public media; use a separately authorized BLOB endpoint for private files.
## Identity Integration for User Lookup
CMS Kit uses `ICmsUserLookupService` when it needs user information for features such as comments, ratings, blog post management and user synchronization.
@ -129,6 +151,31 @@ CMS kit packages are designed for various usage scenarios. If you check the [CMS
- `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments.
- `Volo.CmsKit.*` (without Admin/Public suffix) packages are called as unified packages. Unified packages are shortcuts for adding Admin & Public packages (of the related layer) separately. If you have a single application for administration and public web site, you can use these packages.
### Angular Administration UI
The `@abp/ng.cms-kit` package contains the Angular administration components, routes, configuration providers and generated proxies. Register the administration menu configuration in the application configuration and lazy-load the administration routes:
```typescript
import { ApplicationConfig } from '@angular/core';
import { Routes } from '@angular/router';
import { provideCmsKitAdminConfig } from '@abp/ng.cms-kit/admin/config';
The Angular administration routes include comments, tags, pages, blogs, blog posts, menus and global resources. The built-in public page and blog UI documented in this guide uses the MVC / Razor Pages packages.
`createRoutes` accepts a `CmsKitAdminConfigOptions` object for Angular UI extensions. It supports entity-action, entity-property, toolbar-action, create-form-property and edit-form-property contributors. Key the contributor dictionaries with `eCmsKitAdminComponents`; the supported screens cover comment lists/details, tags, pages and page forms, blogs, blog posts and blog post forms, and menus. Each contributor type exposes only the keys supported by that screen.
## Integrating Public and Admin Packages in a Unified Application
If you are using a single application for both admin and public web site, it's important to configure the global layout settings appropriately. By default, the layout is set for a **Public Website**, which is suitable for public-facing pages. However, when your application serves both admin and public pages, you should explicitly set the global layout for all CMS Kit pages.
@ -150,7 +197,7 @@ To do this, add a `_ViewStart.cshtml` file to your web project at `/Pages/Public
### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
### Connection string
@ -209,13 +256,10 @@ public static void ConfigureExtraProperties()
* `ConfigureCmsKit(...)` method is used to configure the entities of the CMS Kit module.
* `cmsKit.ConfigureBlog(...)` is used to configure the **Blog** entity of the CMS Kit module. You can add or update your extra properties on the **Blog** entity.
* CMS Kit provides configuration methods for `Blog`, `BlogPost`, `BlogFeature`, `MediaDescriptor`, `Page`, `Tag`, `Comment`, `MenuItem`, `CmsUser` and `GlobalResource`.
* `cmsKit.ConfigureBlogPost(...)` is used to configure the **BlogPost** entity of the CMS Kit module. You can add or update your extra properties of the **BlogPost** entity.
* The built-in MVC create and update forms consume extensions for blogs, blog posts, menu items, pages and tags. The Angular administration UI consumes object extensions and contributor callbacks for its comments, tags, pages, blogs, blog posts and menu screens.
* You can also set some validation rules for the property that you defined. In the above sample, `RequiredAttribute` and `StringLengthAttribute` were added for the property named **"BlogPostDescription"**.
* When you define the new property, it will automatically add to **Entity**, **HTTP API**, and **UI** for you.
* Once you define a property, it appears in the create and update forms of the related entity.
* New properties also appear in the datatable of the related page.
* Each helper exposes the module entity extension configuration for that type. Persistence and DTO propagation depend on the mappings registered by the installed CMS Kit packages. Automatic form and table rendering is available only on the MVC and Angular screens listed above. For other entities or custom screens, read the extra property from the DTO and render it explicitly.
@ -18,15 +18,15 @@ you can also customize the marking icons shown in the toggling components.
## Enabling the Marked Item Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want before using them. You can use the [Global Feature](../../framework/infrastructure/global-features.md) system to enable or disable CMS Kit features at development time. Alternatively, you can use the ABP [Feature System](../../framework/infrastructure/features.md) to disable a CMS Kit feature at runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
> Check the ["How to Install" section of the CMS Kit Module documentation](index.md#how-to-install) to see how to enable or disable CMS Kit features at development time.
## Options
Marking system provides a simple approach to define your entity type with mark types like favorite or starred. For example, if you want to use the marking system for products, you need to define an entity type named `product` with the icon name.
`CmsKitMarkedItemOptions` can be configured in YourModule.cs, in the `ConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example:
`CmsKitMarkedItemOptions` can be configured in `YourModule.cs`, in the `ConfigureServices` method of your [module](../../framework/architecture/modularity/basics.md). Example:
The marking system provides a toggle widget to allow users to add/remove the marks from an item. You can place the widget with the item as shown below:
```csharp
@await Component.InvokeAsync(typeof(MarkedItemToggleViewComponent), new
```csharp
@await Component.InvokeAsync(typeof(MarkedItemToggleViewComponent), new
{
entityId = "...",
entityType = "product",
@ -65,6 +65,20 @@ The marking system provides a toggle widget to allow users to add/remove the mar
* `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here.
* `needsConfirmation` An optional parameter to let the user confirm when removing the mark.
The widget can be rendered for anonymous visitors, but toggling a mark requires an authenticated user. The mark state is stored for the current user, entity type and entity ID.
### Customizing MVC Marked Item Icons
The MVC widget resolves the configured icon name through `CmsKitUiOptions.MarkedItemIcons`:
new LocalizableIconDictionary("fa fa-heart text-danger");
});
```
### Filtering on Marked Items
Users can filter their marked items to easily find their favorites. Here's how to utilize the `GetEntityIdsFilteredByUserAsync` method to filter the user's marked items within your repository queries:
@ -97,7 +111,7 @@ var queryable = (await GetDbSetAsync())
#### Aggregates
This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide.
This module follows the [Entity Best Practices & Conventions](../../framework/architecture/best-practices/entities.md) guide.
##### UserMarkedItem
@ -107,7 +121,7 @@ A user markedItem represents a user has marking on the item.
#### Repositories
This module follows the [Repository Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Repositories) guide.
This module follows the [Repository Best Practices & Conventions](../../framework/architecture/best-practices/repositories.md) guide.
Following custom repositories are defined for this feature:
@ -116,7 +130,7 @@ Following custom repositories are defined for this feature:
#### Domain services
This module follows the [Domain Services Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Domain-Services) guide.
This module follows the [Domain Services Best Practices & Conventions](../../framework/architecture/best-practices/domain-services.md) guide.
##### Marked Item Manager
@ -134,13 +148,13 @@ This module follows the [Domain Services Best Practices & Conventions](https://d
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details.
See the [connection strings](../../framework/fundamentals/connection-strings.md) documentation for details.
#### Entity Framework Core
@ -153,4 +167,3 @@ See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-String
Menu items form an ordered tree. Moving an item changes its parent and position, and CMS Kit normalizes the sibling order. Inactive root or child items are omitted from the public menu.
A menu item can target either a URL or a CMS Kit page. When it targets a page, CMS Kit stores the page relationship and updates the menu URL after the page slug changes. It can also define an icon, link target, element ID, CSS class and a required permission. The public menu contributor omits permission-protected items for users who do not have the configured permission.
The public contributor builds the named `CmsKit.Public` menu. CMS Kit registers that name as a main menu and caches the ordered menu-item DTOs in the distributed cache. Creating, updating, moving or deleting a menu item invalidates the cache.
## Internals
@ -76,7 +84,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
@ -94,4 +102,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
After you have created pages, you can set one of them as a *home page*. Then, whenever anyone navigates to your application's homepage, they see the dynamic content of the page that you have defined on this page.
After you have created pages, you can set one of them as the *home page*. CMS Kit keeps at most one home page for the current tenant. Setting or clearing it requires the `CmsKit.Pages.SetAsHomePage` permission.
Also when you create a page, you can access the created page via `/{slug}` URL.
Each page has a `Draft` or `Publish` status. Only published pages are returned by the public application service. A published home page is rendered at `/`, while any other published page is rendered at `/{slug}`. Draft pages return a not-found result on these public routes.
The public page lookup is cached. The home page has a one-hour absolute cache lifetime, and CMS Kit invalidates the relevant entries when an administrator creates, updates, deletes or changes the home page.
### Layout and Custom Resources
The optional **Layout Name** selects a layout from the current theme. A page can also contain CSS in its **Style** field and JavaScript in its **Script** field. CMS Kit adds the style to the page's style section and the script to its script section.
> Page content, style and script are trusted administrator input. The built-in public page renders the content with HTML enabled and XSS prevention disabled, and writes the style and script without sanitization. Grant the page create and update permissions only to users who are allowed to publish executable content.
## Internals
### Domain Layer
`Page` is a multi-tenant aggregate root. `PageManager` normalizes and checks slugs, changes publication status and enforces the single-home-page rule.
### Application Layer
`PageAdminAppService` provides permission-gated management operations. `PagePublicAppService` exposes only published pages and manages the distributed page cache.
### Database Providers
The Entity Framework Core table and MongoDB collection are named `CmsPages` by default. Use `AbpCmsKitDbProperties` to change the common prefix or the relational schema.
@ -55,6 +55,8 @@ The ratings system provides a rating widget to allow users send ratings to resou
`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here.
Reading grouped rating counts is anonymous. Creating, changing or deleting a rating requires an authenticated user. A user has at most one rating for an entity; submitting another value updates that rating. The built-in range is one through five stars.
# Internals
## Domain Layer
@ -81,7 +83,7 @@ Following custom repositories are defined for this feature:
This module follows the [Domain Services Best Practices & Conventions](../../framework/architecture/best-practices/domain-services.md) guide.
##### Reaction Manager
##### Rating Manager
`RatingManager` is used to perform some operations for the `Rating` aggregate root.
@ -97,7 +99,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
##### Connection string
@ -115,4 +117,4 @@ See the [connection strings](../../framework/fundamentals/connection-strings.md)
- `EntityTypes`: List of defined entity types (`CmsKitReactionOptions`) in the reaction system.
- `EntityTypes`: List of defined entity types (`ReactionEntityTypeDefinition`) in the reaction system.
`ReactionEntityTypeDefinition` properties:
@ -70,6 +70,20 @@ The reaction system provides a reaction widget to allow users to send reactions
`entityType` was explained in the previous section. `entityId` should be the unique id of the product, in this example. If you have a Product entity, you can use its Id here.
The summary can be read anonymously. Creating or removing a reaction requires an authenticated user. A user can select each configured reaction at most once for the same entity; repeating the create operation doesn't create a duplicate record.
### Customizing MVC Reaction Icons
The MVC widget resolves each reaction name through `CmsKitUiOptions.ReactionIcons`. Replace an existing icon or register an icon for a custom reaction in the web project:
```csharp
Configure<CmsKitUiOptions>(options =>
{
options.ReactionIcons[StandardReactions.Heart] =
new LocalizableIconDictionary("fa fa-heart text-danger");
});
```
# Internals
## Domain Layer
@ -112,7 +126,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
@ -149,7 +149,7 @@ This module follows the [Domain Services Best Practices & Conventions](../../fra
##### Table / Collection prefix & schema
All tables/collections use the `Cms` prefix by default. Set static properties on the `CmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
All tables/collections use the `Cms` prefix by default. Set static properties on the `AbpCmsKitDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
Docs module is an application module for ABP. It simplifies software documentation. This module is free and open-source.
The Docs module is a free and open-source application module for publishing software documentation in an ABP application.
### Integration
Currently docs module provides you to store your docs both on GitHub and file system.
The module can load documentation from GitHub or the local file system. You can also add a custom document source.
### Hosting
Docs module is an application module and does not offer any hosting solution. You can host your docs on-premise or on cloud.
The module renders documentation inside your application. It does not provide a separate hosting service, so the application can be hosted on-premises or in the cloud.
### Versioning
When you use GitHub to store your docs, Docs Module supports versioning. If you have multiple versions for your docs, there will be a combo-box on the UI to switch between versions. If you choose file system to store your docs, it does not support multiple versions.
GitHub sources can expose releases or branches as document versions. The UI displays a version selector when multiple versions are available. File-system sources expose a single internal version.
[The documents](../modules) for ABP is also using this module.
The [ABP documentation](../) also uses this module.
> Docs module follows the [module architecture best practices](../framework/architecture/best-practices/module-architecture.md) guide.
## Installation
This document covers `Entity Framework Core` provider but you can also select `MongoDB` as your database provider.
### 1- Creating an application
If you do not have an existing ABP project, you can either [generate a CLI command from the get started page of the abp.io website](../get-started) and runs it or run the command below:
```bash
abp new Acme.MyProject
```
### 2- Running The Empty Application
After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**.

The database connection string is located in `appsettings.json` of your `Acme.MyProject.Web` project. If you have a different database configuration, change the connection string.
Run `Acme.MyProject.DbMigrator` project, it will be responsible for applying database migration and seed data. The database `MyProject` will be created in your database server.
Now an empty ABP project has been created! You can now run your project and see the empty website.
To login your website enter `admin` as the username and `1q2w3E*` as the password.
### 3- Installation Module
Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project.
#### 3.1- Use ABP CLI
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
The Docs module supports Entity Framework Core and MongoDB. From the solution directory, use the ABP CLI to add the module packages, module dependencies, client-side package and database integration that match your solution:
```bash
abp add-module Volo.Docs
```
#### 3.2- Manually install
Or you can also manually install nuget package to each project:
* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project.
```bash
dotnet add package Volo.Docs.Domain
```
* Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project.
```bash
dotnet add package Volo.Docs.EntityFrameworkCore
```
* Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project.
```bash
dotnet add package Volo.Docs.Application
```
* Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project.
```bash
dotnet add package Volo.Docs.Web
```
##### 3.2.1- Adding Module Dependencies
An ABP module must declare `[DependsOn]` attribute if it has a dependency upon another module. Each module has to be added in`[DependsOn]` attribute to the relevant project.
* Open `MyProjectDomainModule.cs`and add `typeof(DocsDomainModule)` as shown below;
```csharp
[DependsOn(
typeof(DocsDomainModule),
typeof(AbpIdentityDomainModule),
typeof(AbpAuditingModule),
typeof(BackgroundJobsDomainModule),
typeof(AbpAuditLoggingDomainModule)
)]
public class MyProjectDomainModule : AbpModule
{
//...
}
```
* Open `MyProjectEntityFrameworkCoreModule.cs`and add `typeof(DocsEntityFrameworkCoreModule)` as shown below;
* Open `MyProjectWebModule.cs`and add `typeof(DocsWebModule)` as shown below;
```csharp
[DependsOn(
typeof(DocsWebModule),
typeof(MyProjectApplicationModule),
typeof(MyProjectEntityFrameworkCoreModule),
typeof(AbpAutofacModule),
typeof(AbpIdentityWebModule),
typeof(AbpAccountWebModule),
typeof(AbpAspNetCoreMvcUiBasicThemeModule)
)]
public class MyProjectWebModule : AbpModule
{
//...
}
```
##### 3.2.2- Adding NPM Package
Open `package.json` and add `@abp/docs": "^5.0.0` as shown below:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0",
"@abp/docs": "^5.0.0"
}
}
```
Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command:
For an Entity Framework Core solution with a conventional layered structure, the command adds `builder.ConfigureDocs()` to the `DbContext` in the `.EntityFrameworkCore` (or `.DbMigrations`) project, creates a migration and runs the `DbMigrator` project. When the solution doesn't contain these projects (for example, a single-layer solution), configure the model and apply the migration yourself. Use `--skip-db-migrations` when you want to manage that step yourself. MongoDB does not require an EF Core migration.
````bash
abp install-libs
````
For a manual installation, add the Docs packages and module dependencies that correspond to each application layer. MVC/Razor Pages hosts also need the `@abp/docs` package. Keep every package on the same version as the rest of your ABP solution, then run `abp install-libs` in the web project.
### 4- Database Integration
### Database configuration
#### 4.1- Entity Framework Integration
The module uses the `Docs` connection string name and falls back to `Default` when a dedicated connection string is not configured. `AbpDocsDbProperties.DbTablePrefix` and `AbpDocsDbProperties.DbSchema` control the EF Core table names; the prefix also controls the MongoDB collection names. Set these static properties before the persistence model is configured.
If you choose Entity Framework as your database provider, you need to configure the Docs Module. To do this;
Both built-in persistence providers mark the Docs database context with `IgnoreMultiTenancy`. Projects, cached documents and generated PDF metadata are application-wide data and are not partitioned by the current tenant. Do not expose the administration permissions to tenant administrators unless this application-wide behavior is intended.
- Open `MyProjectMigrationsDbContext.cs` and add `builder.ConfigureDocs()` to the `OnModelCreating()`.
## Creating a Docs Project
```csharp
public class MyProjectMigrationsDbContext : AbpDbContext<MyProjectMigrationsDbContext>
{
public MyProjectMigrationsDbContext(DbContextOptions<MyProjectMigrationsDbContext> options)
After installation, users with the `Docs.Admin.Projects` permission can open **Administration → Documents → Projects**. The built-in administration UI creates and edits GitHub projects. `Docs.Admin.Projects.Create`, `.Update` and `.Delete` control the corresponding actions. `Docs.Admin.Documents` provides the cached-document administration screen.
/* Include modules to your migration db context */
The main project fields are:
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureBackgroundJobs();
builder.ConfigureAuditLogging();
builder.ConfigureIdentity();
builder.ConfigureIdentityServer();
builder.ConfigureFeatureManagement();
builder.ConfigureTenantManagement();
builder.ConfigureDocs(); //Add this line to configure the Docs Module
* **Name**: Display name of the project.
* **ShortName**: URL-friendly identifier. It is normalized to lowercase when the project is created and cannot be changed later.
* **Format**: The built-in web converter supports Markdown (`md`). Register a custom document converter before using another format.
* **DefaultDocumentName**: Initial document name. The default is `Index`.
* **NavigationDocumentName**: Navigation file name. The default is `docs-nav.json`.
* **ParametersDocumentName**: Scriban parameter file name. The default is `docs-params.json`.
* **LatestVersionBranchName**: Branch used for the latest documentation.
/* Configure customizations for entities from the modules included */
Deleting a project deletes the project record and its PDF file metadata, but nothing else. Before deleting it, remove its cached documents through document administration, verify and remove its Elasticsearch entries when search is enabled, and delete every generated PDF through **Manage PDF Files** so the BLOB objects are deleted. The project delete operation does not perform these cleanup steps automatically.
builder.Entity<IdentityUser>(b =>
{
b.ConfigureCustomUserProperties();
});
The public UI starts at `/documents`. You can change this route with `DocsUiOptions.RoutePrefix`, as shown in the [UI options](#ui-options) section.
/* Configure your own tables/entities inside the ConfigureQaDoc method */
builder.ConfigureMyProject();
}
}
```
### GitHub source
* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore` as default project. Then write the below command to add the migration for Docs Module.
Set **GitHub Root URL** to a tree URL that contains the `{version}` placeholder and points to the directory above the language folders. For example:
```csharp
add-migration Added_Docs_Module
```
When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore\Migrations`.
Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore` is still default project.
GitHub projects use releases as the version source by default. Select branches to list repository branches instead. In branch mode, **Version Branch Prefix** filters the branches and removes the prefix from displayed version names. **Latest Version Branch Name** must contain the real branch name, including the prefix when one is used.
Finally, you can check your database to see the newly created tables. For example you can see `DocsProjects` table must be added to your database.
The module builds the edit link from the GitHub tree URL and loads relative images and other resources from the same repository and version. An access token is optional for public repositories and is needed for private repositories or higher API limits. The token is stored in the project's extra properties; public project APIs remove it from their responses, but administrators can retrieve it. Protect the database and use a token with only the repository permissions that the Docs host needs.
### 5- Linking Docs Module
### File-system source
The default route for Docs module is;
The `FileSystem` source loads documents from a local root directory stored in the project's `Path` extra property. Its directory layout is the same as the GitHub source:
```txt
/Documents
```text
<project-root>/docs-langs.json
<project-root>/<language-code>/<document-name>
```
To add Docs module link to your application menu;
File-system projects always use the internal version `1.0.0` and do not provide a version list or an edit link. The source rejects document and resource paths outside the configured project root.
* Open `MyProjectMenuContributor.cs` and add the below line to the method `ConfigureMainMenuAsync()`.
The built-in project create/edit pages currently expose only GitHub fields. Create a file-system project through `IProjectAdminAppService`, a data seeder or another administration UI by setting `DocumentStoreType` to `FileSystem` and the `Path` extra property.
The `Menu:Docs` keyword is a localization key. To localize the menu text, open `Localization\MyProject\en.json` in the project `Acme.MyProject.Domain`. And add the below line
```json
"Menu:Docs": "Documents"
```
Final look of **en.json**
Place `docs-langs.json` at the project root, outside the language directories:
```json
{
"culture": "en",
"texts": {
"Menu:Home": "Home",
"Welcome": "Welcome",
"LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP. For more information, visit abp.io.",
"Menu:Docs": "Documents"
}
"languages": [
{
"displayName": "English",
"code": "en",
"isDefault": true
},
{
"displayName": "Türkçe",
"code": "tr",
"isDefault": false
}
]
}
```
The new menu item for Docs Module is added to the menu. Run your web application and browse to `http://localhost:YOUR_PORT_NUMBER/documents` URL.
You will see a warning says;
```txt
There are no projects yet!
```
As we have not added any projects yet, this warning is normal.
### 6- Adding New Docs Project
Open `DocsProjects` in your database, and insert a new record with the following field information;
* **Name**: The display name of the document name which will be shown on the web page.
* **ShortName**: A short and URL friendly name that will be used in your docs URL.
* **Format**: The format of the document (for Markdown: `md`, for HTML: `html`)
* **DefaultDocumentName**: The document for the initial page.
* **NavigationDocumentName**: The document to be used for the navigation menu (Index).
* **MinimumVersion**: The minimum version to show the docs. Below version will not be listed.
* **DocumentStoreType**: The source of the documents (for GitHub:`GitHub`, for file system`FileSystem`)
* **ExtraProperties**: A serialized `JSON` that stores special configuration for the selected `DocumentStoreType`.
* **MainWebsiteUrl**: The URL when user clicks to the logo of the Docs module page. You can simply set as `/` to link to your website root address.
* **LatestVersionBranchName**: This is a config for GitHub. It's the branch name which to retrieve the docs. You can set it as `master`.
#### Sample Project Record for "GitHub"
You can use [ABP](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store.
Note that `GitHubAccessToken` is masked with `***`. It's a private token that you must get it from GitHub. See https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
- MainWebsiteUrl: `/`
- LatestVersionBranchName: `dev`
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string.
Now you can run the application and navigate to `/Documents`.
#### Sample Project Record for "FileSystem"
You can use [ABP](https://github.com/abpframework/abp/) GitHub documents to configure your GitHub document store.
- Name: `ABP (FileSystem)`
- ShortName: `abp`
- Format: `md`
- DefaultDocumentName: `Index`
When a GitHub source cannot load this file, it falls back to the language configured by `DocsGithubLanguageOptions.DefaultLanguage`, which is English by default. The file-system source requires a valid `docs-langs.json` file.
- NavigationDocumentName: `docs-nav.json`
The language list is cached for 24 hours using the project short name, not the requested version. Keep `docs-langs.json` consistent across all versions. Clear the project cache after changing the manifest; the first version requested after a clear repopulates the project-wide language cache.
- MinimumVersion: `<NULL>` (no minimum version)
### Adding a custom document source
- DocumentStoreType: `FileSystem`
Implement `IDocumentSource`, register the implementation in dependency injection and map a unique source name to it:
- ExtraProperties:
```json
{"Path":"C:\\Github\\abp\\docs"}
```
Note that `Path` must be replaced with your local docs directory. You can fetch the ABP's documents from https://github.com/abpframework/abp/tree/master/docs and copy to the directory `C:\\Github\\abp\\docs` to get it work.
- MainWebsiteUrl: `/`
- LatestVersionBranchName: `<NULL>`
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
Add one of the sample projects above and run the application. In the menu you will see `Documents` link, click the menu link to open the documents page.
So far, we have created a new application from abp.io website and made it up and ready for Docs module.
Use the same source name as the project's `DocumentStoreType`. The factory resolves the mapped type from dependency injection when it loads documents, versions, resources and languages.
### 7- Creating a New Document
## Creating a New Document
In the sample Project records, you see that `Format` is specified as `md` which refers to [Mark Down](https://en.wikipedia.org/wiki/Markdown). You can see the mark down cheat sheet following the below link;
Now let's have a look a sample document in markdown format.
The built-in converter renders [Markdown](https://en.wikipedia.org/wiki/Markdown) documents as HTML. The following example demonstrates headings, links, images and code blocks:
~~~markdown
# This is a header
@ -449,15 +157,13 @@ public class Person
```
~~~
As an example you can see ABP documentation:
You can also browse the [ABP documentation sources](https://github.com/abpframework/abp/tree/dev/docs/en) for complete examples.
The Docs module uses [Scriban](https://scriban.github.io/docs/) to conditionally show or hide parts of a document. Create one parameter document for each language. It contains the available parameters, their values and their display names.
Docs module uses [Scriban](https://github.com/lunet-io/scriban/tree/master/doc) for conditionally show or hide some parts of a document. In order to use that feature, you have to create a JSON file as **Parameter document** per every language. It will contain all the key-values, as well as their display names.
For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json):
For example, `en/docs-params.json` can contain:
```json
{
@ -488,9 +194,9 @@ For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/
}
```
Since not every single document in your projects may not have sections or may not need all of those parameters, you have to declare which of those parameters will be used for sectioning the document, as a JSON block anywhere on the document.
Each document declares the parameters it uses in a JSON block anywhere in the document.
For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md):
For example:
```json
//[doc-params]
@ -501,7 +207,7 @@ For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/bl
}
```
This section will be automatically deleted during render. And f course, those key values must match with the ones in **Parameter document**.
This block is removed while the document is rendered. Its keys and values must match the parameter document.

@ -538,13 +244,13 @@ You can also use variables in a text, adding **_Value** postfix to its key:
This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider.
```
Also, **Document_Language_Code** and **Document_Version** keys are pre-defined if you want to get the language code or the version of the current document (This may be useful for creating links that redirects to another documentation system in another domain).
`Document_Language_Code` and `Document_Version` are predefined keys. They can be used, for example, to build links to another documentation system.
------
**IMPORTANT NOTICE**: Scriban uses "{{" and "}}" for syntax. Therefore, you must use escape blocks if you are going to use those in your document (an Angular document, for example). See [Scriban docs](https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block) for more information.
> Scriban uses `{{` and `}}` as delimiters. Use the escape block described in the [Scriban language reference](https://scriban.github.io/docs/language/) when the document contains these delimiters as literal text, such as in an Angular example.
### 8- Creating the Navigation Document
## Creating the Navigation Document
Navigation document is the main menu of the documents page. It is located on the left side of the page. It is a `JSON` file. Take a look at the below sample navigation document to understand the structure.
@ -596,38 +302,60 @@ Navigation document is the main menu of the documents page. It is located on the
}
```
The upper sample `JSON` file renders the below navigation menu as `HTML`.
The sample JSON file renders the navigation menu shown below.
Finally a new Docs Module is added to your project which is feeded with GitHub.
Implement `INavigationTreePostProcessor` to modify the deserialized navigation tree before it is returned to the UI. The default implementation makes no changes, so a custom implementation can be registered by replacing that service.
## Full-Text Search(Elastic Search)
## Full-Text Search with Elasticsearch
The Docs module supports full-text search using Elastic Search. It is not enabled by default. You can configure `DocsElasticSearchOptions` to enable it.
Elasticsearch integration is disabled by default. Configure the Elasticsearch URL and enable `DocsElasticSearchOptions`:
```json
{
"ElasticSearch": {
"Url": "http://localhost:9200"
}
}
```
```csharp
Configure<DocsElasticSearchOptions>(options =>
{
options.Enable = true;
options.IndexName = "your_index_name"; //default IndexName is abp_documents
The `Index` is automatically created after the application starts if the `Index` does not exist.
The default index name is `abp_documents`. Use `UseBasicAuthentication(username, password)` instead of `UseApiKeyAuthentication` when the Elasticsearch server uses basic authentication.
The module creates the index during application initialization when it does not exist. Creating, updating or deleting a cached document updates the index. The administration UI can reindex one project or all projects from the documents already stored in the Docs database.
`DefaultElasticClientProvider` is responsible for creating `IElasticClient`. By default, it reads Elastic Search's `Url` from `IConfiguration`.
If your `IElasticClient` needs additional configuration, please use override `IElasticClientProvider` service and replace it in the [dependency injection](../framework/fundamentals/dependency-injection.md) system.
`DefaultElasticClientProvider` creates the Elasticsearch client from these settings. Replace `IElasticClientProvider` in the [dependency injection](../framework/fundamentals/dependency-injection.md) system when the client needs additional configuration.
## Document Caching
The module stores downloaded documents in the Docs database, caches document update metadata and uses the distributed cache for document resources. These configuration values control when that data is refreshed:
The values shown above are the defaults. `DocumentCacheTimeoutInterval` controls how often a stored document is checked against its source. The resource settings control the absolute and sliding expirations for images and other document resources. Resource caching is bypassed in the Development environment.
Users with the `Docs.Admin.Documents` permission can clear a project's cache from the administration UI. This removes the cached language and version metadata, invalidates document update information and causes the stored documents to be refreshed on subsequent requests.
Clearing the project cache does not remove document-resource cache entries. Cached images and other resources remain available until their absolute or sliding cache expiration is reached.
## Row Highlighting
@ -688,34 +416,75 @@ After you specify the next & previous documents, they will appear at the end of

## Single Project Mode
## UI Options
The **single project mode** allows you to use a single name as a project name in your application. If you are not considering supporting multiple projects with their multiple docs and instead if you have a single project and want to have documentation only for it, it's especially useful for you.
You just need to configure the `DocsUiOptions`, set the single project mode as **enabled** and also define a constant project name:
Configure `DocsUiOptions` to customize routes and document-page behavior:
```csharp
Configure<DocsUiOptions>(options =>
Configure<DocsUiOptions>(options =>
{
options.RoutePrefix = "docs";
options.ShowProjectsCombobox = false;
options.ShowProjectsComboboxLabel = false;
options.SectionRendering = true;
options.MultiLanguageMode = true;
options.EnableEnlargeImage = true;
options.SingleProjectMode.Enable = true;
options.SingleProjectMode.ProjectName = "abp";
});
```
## Multi Language Mode
The defaults are:
| Option | Default | Description |
| --- | --- | --- |
| `RoutePrefix` | `documents` | Route prefix for the public document pages. |
| `ShowProjectsCombobox` | `true` | Shows the project selector when more than one project is available. |
| `ShowProjectsComboboxLabel` | `true` | Shows the label for the project selector. |
| `MultiLanguageMode` | `true` | Adds the language to routes and displays the language selector. |
| `EnableEnlargeImage` | `true` | Allows document images to be enlarged in the UI. |
| `SingleProjectMode.Enable` | `false` | Removes the project name from document routes. |
When single-project mode is enabled, `SingleProjectMode.ProjectName` selects the project by short name. If it is empty, the module uses the project automatically only when exactly one project exists.
`DocumentLinksNormalizer` transforms generated links to other documents. Its default removes a trailing `/Index`. `RedirectUrlResolver` applies the corresponding redirect when an incoming URL ends in `/Index`. Replace either delegate when the application needs a different canonical URL convention.
## Google Translate and Programmable Search
The MVC UI can add Google Translate and Google Programmable Search to document pages. Both integrations are disabled by default:
`GetCultureLanguageCode` maps the current UI culture to the code passed to Google Translate. The default maps `zh-Hans` to `zh-CN`, `zh-Hant` to `zh-TW` and other cultures to their two-letter ISO language name.
The **multi language mode** allows you to show a combobox that lists and shows all documentation languages and configures the related languages in routes.
## Generating PDF Files
It's enabled by default and supports multiple languages, but if you are considering only supporting a single language, and don't want to show the language combobox in the sidebar of your docs system, you can configure the `DocsUiOptions` and set the multi language mode support as **false** to disable it:
The administration module can generate a PDF archive for a project, version and language. Generation runs as a background job and stores the archive through ABP's BLOB storing system. Configure a BLOB provider for the application, then configure the generator when its defaults do not match the host:
`HtmlLayout` and `HtmlStyle` customize the generated content. `BaseUrl` is used to resolve relative images for local sources. `IndexPagePath` inserts an additional document at the beginning of the archive without adding it to the PDF outline. `CalculatePdfFileName`, `CalculatePdfFileTitle`, `HtmlContentNormalizer` and `DocumentContentNormalizer` provide additional extension points.
`Docs.Admin.Projects.ManagePdfFiles` allows administrators to generate, list and delete archives. Generation creates a ZIP file that contains the rendered PDF documents. Users need `Docs.Common.PdfDownload` to download it, and the public UI displays the download action only when that permission is granted and an archive exists for the selected project, version and language.
PDF generation failures are written to the application logs. The background job does not rethrow failures to the job system, including errors already handled by the generator. A failed run does not make a new archive available, and the background-job system does not automatically retry it as a failed job. Check the host logs and manually start generation again after fixing the error.
## See Also
Docs Module is also available as a standalone application. Check out [VoloDocs](../apps/volo-docs.md).
> You must have an ABP Team or a higher license to use this module.
> You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module.
This module integrates [Elsa Workflows](https://docs.elsaworkflows.io/) into ABP Framework applications and is designed to make it easy for developers to use Elsa's capabilities within their ABP-based projects. For creating, managing, and customizing workflows themselves, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/).
@ -17,7 +17,7 @@ The Elsa module is not installed in [the startup templates](../solution-template
### Using ABP CLI
ABP CLI allows adding a module to a solution using the ```add-module``` command. You can check its [documentation](../cli#add-module) for more information. So, the Elsa module can be added using the following command:
Use the ABP CLI `add-module` command to add the Elsa module to an existing solution:
```bash
abp add-module Volo.Elsa
@ -27,7 +27,7 @@ abp add-module Volo.Elsa
If you modified your solution structure, adding the module using ABP CLI might not work for you. In such cases, you can add the Elsa module into your solution manually.
In order to do that, add packages listed below to the matching project in your solution. For example,`Volo.Abp.Elsa.Application` package to your **{ProjectName}.Application.csproj** as shown below:
To do this, add the packages listed below to the matching projects in your solution. For example, add the`Volo.Abp.Elsa.Application` package to your **{ProjectName}.Application.csproj** as shown below:
@ -42,11 +42,9 @@ After adding the package references, open the module class of the project (e.g.:
)]
```
> If you are using Blazor Web App, you need to add the `Volo.Elsa.Admin.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Elsa.Admin.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
### `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule`
These two modules generally will be added to your authentication project. Please add`Volo.Abp.Elsa.AspNetCore` and `Volo.Abp.Elsa.Identity` packages to your project and add the`AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of your module class based on your project structure:
Add these two modules to the project that hosts authentication. Add the`Volo.Abp.Elsa.AspNetCore` and `Volo.Abp.Elsa.Identity` packages to that project, then add`AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of its module class:
@ -63,22 +61,15 @@ These two modules generally will be added to your authentication project. Please
## The Elsa Module
The Elsa Workflows has its own database provider, and also has a Tenant/Role/User system. They are under active development, so the ABP Elsa module is not yet fully integrated. Below is the current status of each module in the ABP's Elsa Module:
The ABP Elsa module provides the following integration points:
- `AbpElsaAspNetCoreModule(Volo.Abp.Elsa.AspNetCore)` module is used to integrate Elsa authentication.
- `AbpElsaIdentityModule(Volo.Abp.Elsa.Identity)` module is used to integrate ABP Identity authentication.
- `AbpElsaApplicationModule(Volo.Abp.Elsa.Application)` and `AbpElsaApplicationContractsModule(Volo.Abp.Elsa.Application.Contracts)` modules are used to define the Elsa permissions.
- `AbpElsaAspNetCoreModule` maps ABP permissions to the `permissions` claims expected by Elsa.
- Calling `UseAbpIdentity` replaces Elsa's user credential validator and access token issuer with implementations backed by the ABP Identity module. `AbpElsaIdentityModule` provides the required ABP module dependencies.
- `AbpElsaApplicationContractsModule` defines the permissions used by the Elsa Workflow API.
The rest of the projects/modules are basically empty and will be implemented in the future based on the Elsa features:
The module does not add its own workflow aggregates, application services or HTTP API controllers. Workflow definitions, instances and runtime data are managed by Elsa and must be configured through Elsa's own persistence providers. The ABP Entity Framework Core and MongoDB packages register empty module `DbContext` shells; they do not store workflow data or replace Elsa's workflow management and runtime stores.
The MVC, Blazor and MudBlazor packages do not embed Elsa Studio. Use the standalone Elsa Studio application described in the [Elsa Studio](#elsa-studio) section.
## Configure the Elsa Server
@ -86,7 +77,7 @@ You need to configure Elsa in your ABP application to use its features. You can
> For more information about configuring Elsa, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/).
var connectionString = configuration.GetConnectionString("Default")!;
@ -110,38 +101,80 @@ private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration c
}
```
The example binds Elsa's HTTP activity options from the `Http` configuration section. `BaseUrl` is the public URL of the workflow server and `BasePath` is the path used for HTTP endpoint activities:
```json
{
"Http": {
"BaseUrl": "https://localhost:5001",
"BasePath": "/api/workflows"
}
}
```
Do not hard-code the signing key in production. Store a sufficiently long key in a secure configuration source and assign it to `identity.TokenOptions`.
### Configure Authentication
An ABP host normally accepts OpenIddict access tokens, while Elsa Identity issues its own access tokens for the Elsa Studio login. Register the composite authentication scheme so the same workflow API can accept both token types:
`ForwardIdentityAuthenticationForBearer` forwards bearer requests from the application cookie to the composite Elsa scheme. The argument passed to `AddElsaJwtBearer` is the existing bearer scheme that the composite handler tries in addition to Elsa's own scheme. Use the scheme configured by your application if it is different from `OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme`.
Enable the Elsa endpoints and workflow middleware after authentication and authorization in the application initialization pipeline:
```csharp
app.UseAuthentication();
app.UseAbpOpenIddictValidation();
app.UseAuthorization();
app.UseWorkflowsApi();
app.UseWorkflows();
```
## Elsa Database Migration
Elsa module uses its own database context and migration system, ABP Elsa module doesn't contain any `aggregate root/entity` at the moment. So, **you don't need to create any initial migration for Elsa module**. You just need to configure the Elsa Services as follows:
Elsa uses its own database contexts and migration system. The ABP Elsa module does not define workflow aggregate roots or entities, so you don't add Elsa tables to your application's ABP migration `DbContext`. Configure the Elsa workflow management and runtime stores instead:
When you run your application, Elsa will create its own database tables if they do not exist.
With the Entity Framework Core configuration shown above, Elsa runs its embedded migrations by default. Automatic migrations are convenient during development. For production, review Elsa's migration options and use a controlled deployment strategy before disabling automatic migrations.
> See [how to configure Elsa Workflows to use different database providers for persistence, including SQL Server, PostgreSQL, and MongoDB](https://docs.elsaworkflows.io/getting-started/database-configuration) for more information.
> See [Elsa's database configuration guide](https://docs.elsaworkflows.io/getting-started/database-configuration) and [EF Core migrations guide](https://docs.elsaworkflows.io/guides/persistence/ef-migrations) for provider and migration options.
### Elsa Module Permissions
## Elsa Module Permissions
The Elsa Workflow API endpoints check permissions. Also, it has a `*` wildcard permission to allow all permissions.
The Elsa Workflow API endpoints check permissions. The ABP Elsa module defines these permissions for the host side. They are not available to tenant users.
The ABP Elsa module defines all permissions that are used in the Elsa workflow. You can use ABP Permission Management module to manage the permissions.
You can use the ABP Permission Management module to grant individual Elsa permissions or the `*` wildcard permission. When `*` is granted, the claims contributor emits only the wildcard instead of adding every individual permission.
`AbpElsaAspNetCoreModule(Volo.Abp.Elsa.AspNetCore)` module will check and add these permissions to the current user's claims:
`AbpElsaAspNetCoreModule` checks the granted permissions and adds them to the current user's `permissions` claims:
You can also grant parts of the permissions to a role or user. It will add the `permissions` claims to the current user's `Cookies` or `Token`. Elsa Server will read the claims and allow or deny access:
You can also grant individual permissions to a host role or user. Elsa Server reads the resulting claims and allows or denies access:

### Elsa Studio
## Elsa Studio
[Elsa Studio](https://docs.elsaworkflows.io/application-types/elsa-studio) is a **standalone** web application that allows you to design, manage, and execute workflows. It is built using **Blazor Server/WebAssembly**.
`ElsaDemoApp.Studio.WASM` is a sample Blazor WebAssembly project that demonstrates how to use Elsa Studio with ELSA Server with ABP Framework.
`ElsaDemoApp.Studio.WASM` is a sample Blazor WebAssembly project that demonstrates how to use Elsa Studio with an Elsa Server hosted in an ABP application.
> Elsa Studio has its own layout and theme, and you can't integrate it into an ABP Blazor project for now.
@ -149,16 +182,36 @@ You can also grant parts of the permissions to a role or user. It will add the `
Please check the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) document to download its source code for review.
#### Elsa Studio Authentication
Configure Studio with the workflow server's Elsa API URL. The default Elsa API base path is `/elsa/api`:
Elsa Studio requires authentication and there are two ways to authenticate Elsa Studio:
```json
{
"Backend": {
"Url": "https://localhost:5001/elsa/api"
}
}
```
### Elsa Studio Authentication
Elsa Studio supports two authentication methods:
* Password Flow Authentication
* Code Flow Authentication
##### Elsa Studio - Password Flow Authentication
Configure the claim types used by Elsa Studio before choosing an authentication flow:
The `AbpElsaIdentityModule(Volo.Abp.Elsa.Identity)` module is used to integrate with [ABP Identity module](./identity-pro.md) to check Elsa Studio *username* and *password* against ABP Identity.
`AbpElsaIdentityModule` integrates with the [ABP Identity module](./identity-pro.md) to check the Elsa Studio *username* and *password* against host-side ABP Identity users.
You need to replace `UseIdentity` with `UseAbpIdentity` when configuring Elsa in your Elsa server project as follows:
Then, you can log in to the Elsa Studio application with the default credentials (`admin` as the username, and `1q2w3E*` as the password):
Then, you can log in to the Elsa Studio application with the host admin credentials:

Once, you logged in to the application, you can start defining workflows, manage them and see their execution instances and more:
Once you have logged in, you can define and manage workflows and view their execution instances:

##### Elsa Studio - Code Flow Authentication
#### Elsa Studio - Code Flow Authentication
ABP applications use [OpenIddict](./openiddict-pro.md) for authentication. So, you can use the [Authorization Code Flow](https://oauth.net/2/grant-types/authorization-code/) to authenticate Elsa Studio.
After that, Elsa Studio will redirect to your ABP application's login page, then redirect back to Elsa Studio after the successful login.
### Elsa Workflows - Sample Workflow Demo
The ABP authentication server must also contain a public OpenIddict application for Studio. The sample's data seed reads the following configuration and registers `https://localhost:5003/signin-oidc` as the redirect URI:
```json
{
"OpenIddict": {
"Applications": {
"ElsaStudio_BlazorWasm": {
"ClientId": "ElsaStudio_BlazorWasm",
"RootUrl": "https://localhost:5003"
}
}
}
}
```
If your solution uses a different OpenIddict data-seeding implementation, register an equivalent public client with the authorization-code and refresh-token grants, including the Studio redirect URI, post-logout redirect URI and API scope.
## Elsa Workflows - Sample Workflow Demo
ABP provides a complete demo application that shows how to use the Elsa module in your ABP application. You can download the demo application and see the integration points, if you stuck at any point. Please see the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) page for more information.
ABP provides a complete demo application that shows how to use the Elsa module in an ABP application. If you get stuck, download the demo to review the integration points. See the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) page for more information.
The Feature Management module implements the `IFeatureManagementStore` interface defined by the [Feature System](../framework/infrastructure/features.md).
The Feature Management module persists feature values and implements the `IFeatureStore` interface defined by the [Feature System](../framework/infrastructure/features.md). It also provides management services and reusable user interfaces for reading, changing and resetting values for a feature provider.
> This document covers only the feature management module which persists feature values to a database. See [the features](../framework/infrastructure/features.md) document for more about the feature system.
@ -33,9 +33,97 @@ When you click *Actions* -> *Features* for a tenant, the feature management dial
In this dialog, you can enable, disable or set values for the features for a tenant.
### Host Feature Management
The MVC, Blazor and MudBlazor packages add a **Feature Management** group to the Setting Management page. The group is available on the host side to users granted the `FeatureManagement.ManageHostFeatures` permission. It opens the same reusable dialog with the tenant provider (`T`) and an empty provider key, which represents host feature values.
For Angular applications, register the setting-tab contributor in the application configuration:
````ts
import { ApplicationConfig } from '@angular/core';
import { provideFeatureManagementConfig } from '@abp/ng.feature-management';
export const appConfig: ApplicationConfig = {
providers: [provideFeatureManagementConfig()],
};
````
`provideFeatureManagementConfig` adds the host Feature Management tab to Setting Management and protects it with the same `FeatureManagement.ManageHostFeatures` policy. The current application templates already register this provider.
### Reusing the Feature Management Dialog
All UI implementations accept a provider name and an optional provider key. The built-in provider names are `D` for default values, `C` for configuration values, `E` for editions and `T` for tenants. Default and configuration values are read-only, while edition, tenant and custom providers can persist values.
#### MVC
Create an `abp.ModalManager` for the module page and pass the provider information when opening it:
````js
const featureManagementModal = new abp.ModalManager(
For a MudBlazor application, use the `Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components` namespace. The component has the same `OpenAsync(providerName, providerKey, providerKeyDisplayName)` contract.
#### Angular
`FeatureManagementComponent` is a standalone component exported from `@abp/ng.feature-management`:
````ts
import { Component, signal } from '@angular/core';
import { FeatureManagementComponent } from '@abp/ng.feature-management';
@Component({
selector: 'app-tenant-features',
imports: [FeatureManagementComponent],
templateUrl: './tenant-features.component.html',
})
export class TenantFeaturesComponent {
readonly visible = signal(false);
tenantId = '';
}
````
````html
<abp-feature-management
[visible]="visible()"
(visibleChange)="visible.set($event)"
providerName="T"
[providerKey]="tenantId"
/>
````
Use `eFeatureManagementComponents.FeatureManagement` as the component key when replacing the dialog through the Angular component replacement system.
## IFeatureManager
`IFeatureManager` is the main service provided by this module. It is used to read and change the setting values for the tenants in a multi-tenant application. `IFeatureManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a feature value.
`IFeatureManager` is the main service provided by this module. It reads and changes feature values for registered feature management providers. `IFeatureManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a feature value directly.
> If you just want to read feature values, use the `IFeatureChecker` as explained in the [Features document](../framework/infrastructure/features.md).
@ -70,6 +158,8 @@ namespace Demo
}
````
`SetAsync` and the provider-specific extension methods validate the value against the feature definition's value validator. By default, setting a value equal to the fallback value clears the explicit provider value; pass `forceToSet: true` when an explicit value must be kept even if it currently matches the fallback. Use `DeleteAsync(providerName, providerKey)` to reset all values for a provider object to their fallbacks.
## Feature Management Providers
Features Management Module is extensible, just like the [features system](../framework/infrastructure/features.md). You can extend it by defining feature management providers. There are 4 pre-built feature management providers registered in the following order:
@ -86,7 +176,9 @@ If you want to create your own provider, implement the `IFeatureManagementProvid
````csharp
public class CustomFeatureProvider : FeatureManagementProvider
{
public override string Name => "Custom";
public const string ProviderName = "Custom";
public override string Name => ProviderName;
public CustomFeatureProvider(IFeatureManagementStore store)
: base(store)
@ -103,12 +195,77 @@ Once you create your provider class, you should register it using the `FeatureMa
The order of the providers are important. Providers are executed in the reverse order. That means the `CustomFeatureProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list.
The `ProviderPolicies` entry is required when the custom provider is managed through `IFeatureAppService` or one of the reusable dialogs. Map the provider name to an authorization policy that grants access to the corresponding provider object. The application service rejects get, update and reset operations when no policy is mapped.
The management application service exposes get, update and reset operations through `IFeatureAppService`. The HTTP API maps the same operations to `GET`, `PUT` and `DELETE` requests at `/api/feature-management/features`, using `providerName` and `providerKey` to identify the managed object.
## Custom Value Validators
Feature definitions can use custom `IValueValidator` implementations. When those definitions are persisted or returned by the management API, the module must be able to reconstruct the validator from its serialized name. Define a parameterless validator and register a matching factory during pre-configuration:
````csharp
[Serializable]
[ValueValidator("URL")]
public class UrlValueValidator : ValueValidatorBase
{
public override bool IsValid(object? value)
{
return Uri.TryCreate(value?.ToString(), UriKind.Absolute, out _);
}
}
````
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
new ValueValidatorFactory<UrlValueValidator>("URL")
);
});
}
````
The factory name must match the name supplied by `ValueValidatorAttribute`. The module registers factories for the built-in `NULL`, `BOOLEAN`, `NUMERIC` and `STRING` validators.
## Database Providers
The Entity Framework Core and MongoDB packages persist the same three record types: feature groups, feature definitions and feature values.
### Common
#### Table / Collection Prefix and Schema
All tables and collections use the `Abp` prefix by default. Set the static `AbpFeatureManagementDbProperties.DbTablePrefix` property to change the prefix. `AbpFeatureManagementDbProperties.DbSchema` changes the schema for database providers that support schemas.
#### Connection String
The module uses `AbpFeatureManagement` as the connection string name. If this connection string is not configured, it falls back to the `Default` connection string. See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details.
### Entity Framework Core
The Entity Framework Core provider maps the following tables:
* **AbpFeatureGroups**
* **AbpFeatures**
* **AbpFeatureValues**
### MongoDB
The MongoDB provider maps the following collections:
@ -21,7 +21,7 @@ File Management module is not installed in [the startup templates](../solution-t
### 1. Using ABP CLI
ABP CLI allows adding a module to a solution using `add-module` command. You can check its [documentation](../cli#add-module) for more information. So, file management module can be added using the command below;
Use the ABP CLI `add-module` command to add the File Management module to an existing solution:
```bash
abp add-module Volo.FileManagement
@ -33,7 +33,7 @@ If you modified your solution structure, adding module using ABP CLI might not w
In order to do that, add packages listed below to matching project on your solution. For example, `Volo.FileManagement.Application` package to your **{ProjectName}.Application.csproj** like below;
@ -46,7 +46,7 @@ After adding the package reference, open the module class of the project (eg: `{
)]
```
> If you are using Blazor Web App, you need to add the `Volo.FileManagement.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and ad the `Volo.Chat.FileManagement.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
> If you are using Blazor Web App, add the `Volo.FileManagement.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and the `Volo.FileManagement.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project.
If your project is using `EntityFrameworkCore`, you need to add following configuration to `OnModelCreating` method at your `DbContext`.
File Management module's MVC user interface depends on following npm packages. add `@volo/file-management` npm package to your `package.json` file.
```json
"dependencies": {
...
"@volo/file-management": "^2.9.0"
{
"dependencies": {
"@volo/file-management": "~x.x.x"
}
}
```
> After adding packages, you need to run `abp install-libs` command in the folder of your `Web` project.
#### Angular UI
For user interface, an Angular module called `FileManagementModule` is included in the `@volo/abp.ng.file-management` library.
For a standalone Angular application, register the File Management menu configuration in `app.config.ts`:
Please visit [document on feature libraries](../framework/ui/angular/feature-libraries.md) to learn how you can install and set it up in your Angular application.
```ts
import { ApplicationConfig } from '@angular/core';
import { provideFileManagementConfig } from '@volo/abp.ng.file-management/config';
#### Blazor & Blazor Server
export const appConfig: ApplicationConfig = {
providers: [provideFileManagementConfig()],
};
```
[There is a known problem with ASP NET Core](https://github.com/dotnet/aspnetcore/issues/38842#issuecomment-1342540950), You have to set `DisableImplicitFromServicesParameters` of `HubOptions` to `true`.
Lazy-load the File Management routes in `app.routes.ts`:
The `createRoutes` function accepts `entityActionContributors`, `toolbarActionContributors`, `entityPropContributors` and `xsrfHeaderName`. These contributors target `eFileManagementComponents.FolderContent`. See the Angular guides for [entity actions](../framework/ui/angular/entity-action-extensions.md), [page toolbars](../framework/ui/angular/page-toolbar-extensions.md) and [table columns](../framework/ui/angular/data-table-column-extensions.md).
Use `withUppyOptions` with `provideFileManagementConfig` when you need to customize the Angular uploader's Uppy options. The startup templates already register the provider and lazy route when the module is selected.
Please visit [document on feature libraries](../framework/ui/angular/feature-libraries.md) to learn how you can install and set it up in your Angular application.
## Setting BLOB Provider
File Management module is based on the [BLOB Storing](../framework/infrastructure/blob-storing) system as defined before, and it uses `FileManagementContainer` as a BLOB container.
Please check the [BLOB Storage Providers documentation](../framework/infrastructure/blob-storing#blob-storage-providers) for more information about providers and how to use them.
File contents are stored with the file descriptor's ID as the BLOB name. Renaming or moving a file only changes its descriptor. Deleting a file removes both the descriptor and its BLOB. Deleting a directory recursively deletes its subdirectories, file descriptors and file BLOBs, so treat directory deletion as a destructive operation.
The descriptor database and the configured BLOB provider are separate resources. A custom workflow that calls the domain services directly should handle failures between metadata and BLOB operations; a database transaction cannot roll back an external BLOB provider.
## Packages
This module follows the [module development best practices guide](../framework/architecture/best-practices) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them.
@ -144,12 +165,52 @@ You can move files by clicking `Actions -> Move` on the table.
You can rename a file by clicking `Actions -> Rename` on the table.
The **Download** action first requests a short-lived download token and then navigates to the download endpoint. The **Preview** action is available for supported image files. The **Delete** action removes the stored content in addition to its file descriptor.
The built-in UIs require an explicit overwrite choice when a file with the same name already exists in the current directory. Their upload pre-checks validate names, detect duplicates and check the current storage quota before the file content is sent. The upload HTTP API does not require this pre-check, and `CreateFileInputWithStream.OverrideExisting` defaults to `true`. A direct API client should call the pre-check endpoint and set `OverrideExisting` explicitly when it needs the same protection.
###### File Sharing
To share a file, click `Actions -> Share` in the table. Once sharing is enabled, you can copy the shared link directly from the table.
> Anyone with the shared link will be able to access the file while sharing is enabled.
Shared links use a protected token that identifies the tenant and file. The token has no built-in expiration time; disabling sharing or deleting the file makes the link unavailable. Disabling sharing is not permanent link revocation: re-enabling the same file makes previously issued links valid again, and individual links cannot be revoked. Treat the URL as a secret. Persist the ASP.NET Core Data Protection key ring across restarts and deployments, and configure every application instance that serves these links to use the same key store and application name.
### Share URL Origin in Tiered Applications
MVC, Blazor and MudBlazor use a UI-specific option when they build copied share URLs. Configure the option when the HTTP API is hosted at a different public origin than the UI:
Use `FileManagementBlazorOptions` for the standard Blazor UI or `FileManagementBlazorMudBlazorOptions` for the MudBlazor UI. For copied share URLs, MVC falls back to the browser origin, while the Blazor UIs fall back to `NavigationManager.BaseUri`. These options only change copied share URLs. MVC authenticated downloads use the same-origin download endpoint; the Blazor UIs resolve the download origin from the File Management remote service configuration.
### Resource-Based Permissions
The module supports both module-wide permissions and resource permissions for individual directories and files. Users with a module-wide create, update, delete or view permission can perform that operation throughout File Management. A user without the corresponding module-wide permission can perform it when a matching resource permission is granted.
Directory resource permissions are inherited by descendants. For example, `View` on a directory grants view access to its descendant directories and files, while `Add` grants creation in that directory and its descendants. File-level `View`, `Edit`, `Move` and `Delete` grants apply to the selected file. Creating an item at the root still requires the module-wide create permission because the root is not a directory resource.
The `ManagePermissions` permissions show a **Permissions** action in the MVC, standard Blazor and Angular UIs. This action opens the Resource Permission Management UI for the selected directory or file. The MudBlazor UI does not currently provide this action. File sharing is controlled separately by `FileManagement.FileDescriptor.Share`; a resource permission does not grant sharing access.
### HTTP API and Download Tokens
The directory API is rooted at `/api/file-management/directory-descriptor` and exposes get, list, content, create, rename, move and delete operations. The file API is rooted at `/api/file-management/file-descriptor` and exposes list, upload pre-check, upload, content, rename, move, delete, storage information, download and sharing operations.
Rename and move inputs carry the descriptor's concurrency stamp. API clients should return the latest stamp received from a descriptor or directory-content response so concurrent changes are detected instead of silently overwritten.
Authenticated clients download a private file in two steps:
1. Call `GET /api/file-management/file-descriptor/download/{id}/token`. This operation checks the module-wide or resource `View` permission.
2. Navigate to `GET /api/file-management/file-descriptor/download/{id}?token=...`.
The download endpoint is anonymous because the token is the credential. A token is bound to one file and tenant, expires after 60 seconds and can be reused during that interval. Do not log or expose it. Public shared files use the separate anonymous `GET /api/file-management/file-descriptor/share?shareToken=...` endpoint.
## Data Seed
This module doesn't seed any data.
@ -162,7 +223,7 @@ This module doesn't seed any data.
This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide.
##### TextTemplateContent
##### Directory and File Descriptors
- `DirectoryDescriptor` (aggregate root): Represents a folder.
- `FileDescriptor` (aggregate root): Represents a file.
@ -194,8 +255,15 @@ This module doesn't define any setting.
### Features
You can enable or disable this module for each tenant, also you can set maximum storage size for each tenant.
See the `FileManagementFeatures` class members for all features defined for this module.
You can enable or disable this module and set a maximum storage size for each tenant. The module defines these features:
- `FileManagement.Enable`: Enables the module. The default is `true`.
- `FileManagement.StorageSize`: Sets the numeric quota from `1` through `8000`. The default is `1`.
- `FileManagement.StorageSizeUnit`: Selects `Byte`, `Kilobyte`, `Megabyte`, `Gigabyte` or `Terabyte`. The default is `Terabyte`.
The default quota is therefore 1 TB. Usage is calculated from file descriptors in the current tenant context. An upload is rejected when the existing usage plus the incoming content reaches or exceeds the configured maximum.
Quota checking reads the current total before storing a file; it does not reserve capacity or lock concurrent uploads. If the quota is a strict security or billing boundary, serialize uploads for a tenant or add an application-level reservation mechanism around the upload operation.
### Application Layer
@ -204,6 +272,45 @@ See the `FileManagementFeatures` class members for all features defined for this
- `DirectoryDescriptorAppService` (implements `IDirectoryDescriptorAppService`): Implements the use cases of the file management UI.
- `FileDescriptorAppService` (implements `IFileDescriptorAppService`): Implements the use cases of the file management UI.
### File Icon Configuration
`FileIconOption` maps file extensions to Font Awesome classes or image URLs. Configure it in a module to add an extension, replace a built-in mapping or change the default icon:
```csharp
Configure<FileIconOption>(options =>
{
options.SetFileIcon(
"cad",
new FileIconInfo("fa-solid fa-cube", FileIconType.FontAwesome));
options.SetDefaultIcon(
new FileIconInfo("/images/file.svg", FileIconType.Url));
});
```
The MVC, standard Blazor and Angular UIs render the `IconInfo` returned by the application service and therefore use this configuration. The MudBlazor UI currently selects Material icons from the file extension and is not affected by `FileIconOption`.
### Extending the Entities
`DirectoryDescriptor` and `FileDescriptor` support the [Module Entity Extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) system. Configure extra properties in the `Domain.Shared` project before the database model is created:
The module maps these extra properties through its extensible input and output DTOs and applies the corresponding EF Core object-extension mappings. Add the matching UI extension when the property must be editable or visible in a built-in UI.
### Database Providers
#### Common
@ -238,4 +345,14 @@ See the `FileManagementPermissions` class members for all permissions defined fo
## Distributed Events
This module doesn't define any additional distributed event. See the [standard distributed events](../framework/infrastructure/event-bus/distributed).
This module doesn't explicitly publish a custom distributed event. It defines `DirectoryDescriptorEto` and `FileDescriptorEto` mappings for ABP's standard distributed entity events. Automatic entity events are disabled by default; enable the selectors in the application that owns the File Management data when consumers need create, update or delete notifications:
This publishes the standard `EntityCreatedEto<T>`, `EntityUpdatedEto<T>` and `EntityDeletedEto<T>` envelopes with the corresponding ETO payload. See the [distributed event bus documentation](../framework/infrastructure/event-bus/distributed) for delivery and handler configuration.
> You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module.
This module allows you to create questionnaires to gather information. The forms module can store responses as they come in and you can export the data to a CSV file. You can share your form with others with your form unique link. You can request authentication or allow anonymous reply. It is similar to the Google Form application. Usage area is quite wide, you can create surveys, manage event registrations, collect email addresses for a newsletter, create a quiz, and even receive an order request.
The Forms module allows you to create questionnaires, collect responses and export the results to CSV. A form can accept anonymous responses or require authentication, collect email addresses and allow respondents to edit their responses. You can share a form by using its unique link or sending an invitation email.
See [the module description page](https://abp.io/modules/Volo.Forms) for an overview of the module features.
## How to install
## How to Install
The form module doesn't come pre-installed. You need to install it manually. There are 2 ways of installing it:
The Forms module isn't pre-installed. You can install it in one of the following ways:
* **Via ABP CLI:** Open a command line window in your solution folder (in the folder where the `* .sln` file is located) and type the following command:
* **ABP CLI:** Open a command-line terminal in the solution folder (the folder containing the solution file) and run the following command:
```bash
abp add-module Volo.Forms
```
* **Via ABP Suite:** Open ABP Suite and select your project. Then go to the modules page from the top menu. Find **Forms** card and click add as project (with source-code) or add as package (without source-code).
* **ABP Suite:** Open ABP Suite, select your solution and go to the modules page. Find the **Forms** card and add it as source code or as a package.
## Packages
This module follows the [module development best practices guide](../framework/architecture/best-practices) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them.
This module follows the [module development best practices guide](../framework/architecture/best-practices) and consists of several NuGet packages. See the guide if you want to understand the packages and their relationships.
You can visit the [forms module package list page](https://abp.io/packages?moduleName=Volo.Forms) to see list of packages related with this module.
Visit the [Forms module package list](https://abp.io/packages?moduleName=Volo.Forms) to see the packages provided by this module.
## User interface
## User Interface
### Menu items
### Menu Item
SaaS module adds the following item to the root main menu.
The module adds a **Forms** item under the **Administration** menu when the current user has the `Forms.Form` permission. The page is used to create forms, manage their questions and settings, share them and inspect their responses.
* **Forms**: Add a new form, manage your form questions, delete your form.
The `FormsMenus` class contains the menu item names.
### Forms Page
The `FormsMenus` class has the constant variable for the menu item name.
### Pages
#### Forms
Forms page is used to manage the forms. You can view the form contents, send it to others or delete it from the actions menu.
The Forms page lists the forms that you can manage. Its actions menu opens the form designer, opens the invitation dialog or deletes the form. Use the designer to manage questions and settings or preview the form. The **Responses** tab opens the response page, where you can inspect and export responses.

To see the other features of the Forms module, visit [the module description page](https://abp.io/modules/Volo.Forms).
### Question Types
## Data seed
The built-in form designer supports the following question types:
This module adds a sample initial form (see [the data seed system](../framework/infrastructure/data-seeding.md)) to the database when you run the `.DbMigrator` application:
* Short text
* Multiple choice
* Checkboxes
* Dropdown list
* **Form title:** "Test Form"
* **Form description:** "Test Description"
Questions and choices are displayed in their configured order. Multiple-choice and checkbox questions can also include an **Other** option.
## Internals
### Response Settings
### Domain layer
New forms accept responses by default. The other response settings are disabled by default.
#### Aggregates
| Setting | Behavior |
| --- | --- |
| `RequiresLogin` | The packaged MVC page redirects anonymous users to the login page. `GetQuestionsAsync` and response submission also reject anonymous users. The anonymous `GetAsync` endpoint still returns the form details, including its questions, so it is not an authorization boundary for question data. |
| `HasLimitOneResponsePerUser` | Rejects a second response from the same authenticated user. This setting is automatically disabled when `RequiresLogin` is disabled. |
| `IsCollectingEmail` | In the packaged MVC response flow, displays the email field and requires a non-empty value when a response is created or updated. |
| `CanEditResponse` | In the packaged MVC response flow, allows a saved response to be updated. If login is required, that flow only updates the current user's response. A direct update API client must send the target response's actual `FormId`; the application service uses the supplied form ID when it checks these settings and ownership. |
| `IsAcceptingResponses` | Enables or disables response entry on the packaged MVC form page. |
| `IsQuiz` | Stores the quiz-mode flag. The packaged module doesn't calculate a quiz score. |
This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide.
### Sharing and Routing
- ##### Form
The public form page uses the `/Forms/{formId}/ViewForm` route. When login is required, this route redirects an anonymous visitor to the account login page and uses the form route as the return URL.
- The main aggregate root of the form entities. The form options, title and description is being stored on this entity.
By default, the share dialog builds the form link from the current request. Configure `FormRoutingOptions` in the host module when forms must be shared through a dedicated application:
- ##### QuestionBase
```csharp
Configure<FormRoutingOptions>(options =>
{
options.HostUrl = "https://forms.example.com/";
options.OnlyViewInHostProject = true;
});
```
- It stores questions of the form. This entity is dependent to form entity by `FormId`.
`HostUrl` becomes the base URL for links generated by the share dialog. When `OnlyViewInHostProject` is `true` and `HostUrl` has a value, the form route returns a not-found result if the current display URL does not start with the configured `HostUrl`. `OnlyViewInHostProject` has no effect when `HostUrl` is empty.
- ##### FormResponse
The invitation action sends the configured subject and body through ABP's `IEmailSender`. See the [email sending documentation](../framework/infrastructure/emailing.md) to configure an email provider.
- Each form submit is a new form response record. The form response has answer records.
### CSV Export
#### Repositories
The response page can export a requested response page to a `{form-title}.csv` file. The packaged download action requests responses sorted by ID and doesn't send the page or filter state, so it exports the default maximum of 10 responses. API clients can set `MaxResultCount` to `0` to export all matching responses. The export:
This module follows the [Repository Best Practices & Conventions](../framework/architecture/best-practices/repositories.md) guide.
* uses UTF-8 with a byte-order mark;
* uses the current UI culture and quotes all fields;
* writes `Date` as the first column and then the question titles in their configured order;
* writes one row per response and uses the response's last modification time, or its creation time when it hasn't been modified; and
* contains answer values, but doesn't include the separately collected email value.
Following custom repositories are defined for this module:
Response answers can contain personal or confidential data. Grant form-management and export access only to trusted users, and store downloaded CSV files according to your application's data-handling requirements.
* `IFormRepository`
* `IQuestionRepository`
* `IChoiceRepository`
* `IResponseRepository`
## Feature and Permissions
#### Domain services
The module defines the `Volo.Forms.Enable` feature. It is enabled by default. Disabling it prevents the Forms application services from being used, and all Forms permissions require this feature.
This module follows the [Domain Services Best Practices & Conventions](../framework/architecture/best-practices/domain-services.md) guide.
See the [Feature System documentation](../framework/infrastructure/features.md) for feature configuration and the [Feature Management module](feature-management.md) for managing feature values.
##### QuestionManager
The module defines the following permissions:
`QuestionManager` is used to manage the questions of your form.
| Permission | User interface and application task |
| --- | --- |
| `Forms.Form` | Opens the Forms administration page and its form and question management UI. It also protects form operations such as sharing, response inspection and CSV export. |
| `Forms.Form.Delete` | Deletes a form. |
| `Forms.Response` | Displays the response-management tab and response results in the packaged UI. |
| `Forms.Response.Delete` | Deletes an individual response or all responses of a form. |
### Application layer
## UI Support
#### Application services
The module provides an MVC/Razor Pages UI in the `Volo.Forms.Web` package. It doesn't provide an Angular or Blazor WebAssembly UI package. Since the packaged UI uses Razor Pages, it can also be hosted by a Blazor Server application that supports Razor Pages.
- `FormApplicationService`
- `QuestionAppService`
- `ResponseAppService`
## Internals
### Database providers
### Domain Layer
#### Common
#### Aggregates
##### Table / collection prefix & schema
This module follows the [Entity Best Practices & Conventions](../framework/architecture/best-practices/entities.md) guide.
All tables/collections use the `Frm` prefix by default. Set static properties on the `FormsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider).
* `Form` is the aggregate root that stores the form title, description and response settings.
* `QuestionBase` is the base aggregate root for form questions. A question references its form through `FormId`; the concrete question type stores its choices when applicable.
* `FormResponse` is the aggregate root for a form submission and owns its answer collection.
##### Connection string
#### Repositories
This module uses `Forms` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string.
This module follows the [Repository Best Practices & Conventions](../framework/architecture/best-practices/repositories.md) guide and defines the following custom repositories:
See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details.
* `IFormRepository`
* `IQuestionRepository`
* `IChoiceRepository`
* `IResponseRepository`
#### Entity Framework Core / MongoDB
#### Domain Services
##### Tables / Collections
This module follows the [Domain Services Best Practices & Conventions](../framework/architecture/best-practices/domain-services.md) guide.
`QuestionManager` creates, updates and deletes questions and their choices, including their stored display order.
- **FrmForms**: Form list.
- **FrmQuestions**: Questions of the forms.
- **FrmAnswers**: Answers of the form response.
- **FrmChoices**: Choices of questions.
- **FrmFormResponses**: A new form response is being created each time user submits the form.
### Application Layer
The module defines the following application services:
* `FormAppService` manages forms, form settings, questions, sharing and response exports.
* `QuestionAppService` updates, reads and deletes individual questions.
* `ResponseAppService` reads, creates, updates and deletes form responses.
See the `FormsPermissions` class members for all permissions defined for this module.
This module uses `Forms` as its connection string name. If a connection string with this name isn't defined, it falls back to the `Default` connection string.
See the [connection strings](../framework/fundamentals/connection-strings.md) documentation for details.
### Angular UI
#### Entity Framework Core
Forms module doesn't support Angular UI for now.
Entity Framework Core tables use the `Frm` prefix by default. Set the static `FormsDbProperties.DbTablePrefix` and `FormsDbProperties.DbSchema` properties to change the table prefix or schema.
### Blazor UI
The module creates the following tables by default:
> You must have an [ABP Team or a higher license](https://abp.io/pricing) to use this module.
This module allows users to download and delete their personal data collected by the application.
This module allows users to request a download of their personal data and request deletion of their personal data and account.
> The GDPR module requests the information from the other modules that reference the `Volo.Abp.Gdpr.Abstractions` package and merges the response data into a single JSON file and the personal data can be downloaded later by the user. Also, the user can delete her/his personal data and account permanently.
> The GDPR module uses distributed events from the `Volo.Abp.Gdpr.Abstractions` package. Participating modules collect their own data and publish prepared-data events. The GDPR module stores each prepared payload and later returns the available payloads in a ZIP archive.
See [the module description page](https://abp.io/modules/Volo.Gdpr) for an overview of the module features.
@ -41,29 +41,33 @@ You can visit the [Gdpr module package list page](https://abp.io/packages?module
The GDPR module adds the following item to the "User" profile menu.
* **Personal Data**: Personal data management page. You can request your personal data, list all personal data requests, download and/or delete personal data, and delete the account permanently.
* **Personal Data**: Personal data management page. You can request your personal data, list all personal data requests, download available data and request deletion of personal data and the account.
The `GdprMenus` class has the constant variable for the menu item name.
The `GdprMenuNames.PersonalData` constant contains the menu item name.
### Pages
#### Personal Data
The "Personal Data" page is used to manage personal data requests. You can view the past requests, current status of the latest request, create a new request, download data or delete all your personal data and account from the application.
The "Personal Data" page is used to manage personal data requests. You can view past requests, check the latest request, create a new request, download available data or request deletion of personal data and the account.

The GDPR module is designed for distributed architectures. When a user requests their personal data, the module publishes two events:
The GDPR module is designed for distributed architectures. It publishes different events for the two user actions:
- `GdprUserDataRequestedEto`: Triggers personal data collectors to prepare user data
- `GdprUserDataDeletionRequestedEto`: Triggers personal data collectors to delete user data
- `GdprUserDataRequestedEto` is published when the user requests a data download. Collectors respond with `GdprUserDataPreparedEto`.
- `GdprUserDataDeletionRequestedEto` is published when the user requests deletion.
You can subscribe to these events to implement custom data collection and deletion logic in your modules. See the [Distributed Events](#distributed-events) section for more details.
> To see the other features of the GDPR module, visit [the module description page](https://abp.io/modules/Volo.Gdpr).
### Authorization
The module doesn't define a grantable GDPR permission. Its application service requires an authenticated user, and list and token operations verify that the request belongs to the current user. The download action is the exception: it allows anonymous access with the short-lived bearer token issued to the request owner. Keep this token confidential and use HTTPS.
* `RequestTimeInterval` (default: 1 day): It uses to indicate the allowed request time interval. You can configure this property if you want to increase or decrease the personal data request interval. By default, users can request their personal data once a day.
* `MinutesForDataPreparation` (default: 60 minutes): Since the GDPR module is designed to support distributed scenarios, it should take a while to collect and prepare personal data. You can configure this property if you want to increase or decrease data preparation time by the size of your application.
* `RequestTimeInterval` (default: 1 day): Defines the minimum interval measured from the latest stored personal-data request. You can configure this property to increase or decrease that interval. The `IsNewRequestAllowedAsync` application-service method reports whether the current request is allowed.
* `MinutesForDataPreparation` (default: 60 minutes): Sets the earliest time at which the archive can be downloaded. This is a time window for distributed collectors, not a collector-completion check. Set it long enough for your event transport and slowest collector.
### AbpCookieConsentOptions
@ -93,10 +97,10 @@ Example:
```csharp
Configure<AbpCookieConsentOptions>(options =>
{
IsEnabled = true;
CookiePolicyUrl = "/CookiePolicy";
PrivacyPolicyUrl = "/PrivacyPolicy";
Expiration = TimeSpan.FromDays(180);
options.IsEnabled = true;
options.CookiePolicyUrl = "/CookiePolicy";
options.PrivacyPolicyUrl = "/PrivacyPolicy";
options.Expiration = TimeSpan.FromDays(180);
});
```
@ -121,8 +125,8 @@ The main aggregate root of the GDPR requests. This aggregate root stores general
* `GdprRequest` (aggregate root): Represents a GDPR request made by users.
* `UserId`: Id of the user who made the request.
* `ReadyTime`: Indicates the end time for the data preparation process. The `MinutesForDataPreparation` property of the `AbpGdprOptions` sums with the creation time of the request and this property is calculated.
* `Info` (collection): This collection contains the collected personal data of the user.
* `ReadyTime`: Indicates the earliest time at which the archive can be downloaded. It is calculated by adding `AbpGdprOptions.MinutesForDataPreparation` to the request creation time.
* `Infos` (collection): Contains the prepared personal-data payloads received for the request.
#### Entities
@ -133,7 +137,7 @@ This entity is used to store the collected data from a module/provider.
* `GdprInfo` (entity): Represents the personal data of a user.
* `RequestId`: Id of the GDPR request.
* `Data`: Uses to store personal data.
* `Provider`: Indicates the module where the personal data is collected.
* `Provider`: Identifies the collector or provider that prepared the personal data. It is an arbitrary identifier supplied with the prepared-data event and doesn't have to be a module name.
#### Repositories
@ -173,8 +177,8 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do
##### Tables / Collections
- **AbpGdprRequests**
- **AbpGdprInfos**
- **GdprRequests**
- **GdprInfo**
##### Entity Relationships
@ -184,10 +188,11 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do
### Installation
In order to configure the application to use the gdpr module, you first need to import `provideGdprConfig` from `@volo/abp.ng.gdpr/config`to the root configuration. Then, you will need to append it to the `appConfig` array.
To configure the application to use the GDPR module, import `provideGdprConfig` from `@volo/abp.ng.gdpr/config`and append it to the root `ApplicationConfig.providers` array.
```js
```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
The gdpr module should be imported and lazy-loaded in your routing array. It has a static `createRoutes` method for configuration. Available options are listed below. It is available for import from `@volo/abp.ng.gdpr`.
The cookie-consent configuration accepts `isEnabled`, `cookiePolicyUrl`, `privacyPolicyUrl` and `expireDate`. Cookie consent is enabled when `isEnabled` is omitted; an explicit `false` disables it. `expireDate` is a JavaScript `Date` and defaults to six months from initialization when omitted.
The GDPR module should be imported and lazy-loaded in your routing array. It exports a `createRoutes` function from `@volo/abp.ng.gdpr`. Available route options are listed below.
```js
```ts
// app.routes.ts
import { Routes } from '@angular/router';
const APP_ROUTES: Routes = [
// other route definitions
{
@ -223,47 +232,55 @@ const APP_ROUTES: Routes = [
<h4id="h-gdpr-module-options">Options</h4>
You can modify the look and behavior of the module pages by passing the following options to the `createRoutes` static method:
You can modify the look and behavior of the module page by passing these options to the `createRoutes` function:
- **entityActionContributors:** Changes the grid actions. Please check [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md) for details.
- **createFormPropContributors:** Changes the personal-data table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details.
- **toolbarActionContributors:** Changes the page toolbar. Please check [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md) for details.
- **entityPropContributors:** Changes the table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details.
- **createFormPropContributors:** Changes the create form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details.
- **editFormPropContributors:** Changes the create form fields. Please check [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md) for details.
The personal-data page is also replaceable. Use `eGdprComponents.PersonalData` as the replacement key. See [Component Replacement](../framework/ui/angular/component-replacement.md) for the replacement API.
## Distributed Events
The GDPR module collects the data asynchronous to work that is compatible with microservice solutions. An event is published when a user requests their information.
The GDPR module collects data asynchronously so it can work with distributed and microservice solutions. A data request creates a `GdprRequest` and publishes an event for collectors.
### GdprUserDataRequestedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published to trigger all personal data collectors to begin preparing their data. If you want to collect personal data for your module, you need to subscribe to this ETO class and publish the `GdprUserDataPreparedEto` event with your collected data.
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) contains the user and request identifiers. To include data owned by your module, subscribe to this ETO and publish a `GdprUserDataPreparedEto` with the same request identifier, your provider name and the collected data.
### GdprUserDataPreparedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is used to save the collected personal data into a single JSON file per module. Typically, you don't need to implement this event handler since the module already has an implementation that returns the collected data within a zip file containing multiple JSON files, with each file containing data collected from a specific module.
The GDPR module handles this [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object), serializes its data and adds it to the matching request. Each stored prepared-data event becomes one JSON entry when the ZIP archive is generated.
`ReadyTime` is only the download time gate. The module does not track an expected collector count or wait for an explicit "all collectors completed" signal. At or after `ReadyTime`, the archive contains the prepared-data events stored at that moment; it can be incomplete or empty when collectors are delayed or fail. Monitor event delivery and choose `MinutesForDataPreparation` for the slowest expected collector.
Before downloading, an authenticated request owner obtains a download token. The token expires after 60 minutes. After a request presents a matching token and request identifier, the service removes the token before checking `ReadyTime`, so an early sequential attempt consumes it. A mismatched request identifier doesn't consume the token. The cache read and removal are separate operations, so this isn't a concurrency-safe single-use guarantee. The download endpoint accepts the request identifier and token without an authenticated session; use HTTPS and keep the token out of application and proxy logs.
A successful download does not remove the stored `GdprRequest` or its `GdprInfo` data. Define a retention and cleanup policy appropriate for the personal data collected by your application.
### GdprUserDataDeletionRequestedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published when a user requests to permanently delete their personal data and account. By default, only the `IdentityGdprEventHandler` in the [Identity Pro Module](../modules/identity-pro) subscribes to this event to anonymize the user's data and delete their account (using soft-delete unless configured otherwise).
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published when a user requests deletion of personal data and the account. The GDPR module first deletes its stored requests and prepared payloads for the current user, then publishes the event for participating modules.
If you want to delete additional sensitive user data stored in other modules, you can subscribe to this event and implement custom deletion (or anonymization) logic in those modules.
When the standard Identity Pro module is installed, its built-in subscriber anonymizes the identity user's personal fields, deactivates the user and deletes the identity-user record. Other participating modules remain responsible for their own data. Subscribe to the event to implement additional deletion or anonymization logic for application-specific data.
Treat deletion as a distributed workflow. A successful GDPR API response does not by itself prove that every subscriber has completed its module-specific deletion. Make handlers idempotent, monitor failed event deliveries and define how your application revokes active sessions and tokens when the account is deleted.
## Cookie Consent

Cookie Consent can be used to inform the users of the application, before saving any specific data about the users.
Cookie Consent displays a banner and stores the user's acceptance in the consent cookie. It doesn't automatically block nonessential cookies, browser storage or tracking scripts. Your application must prevent those operations until consent when its policy requires that behavior.
This feature is enabled by default for the [Application](../solution-templates/layered-web-application) and [Application Single Layer](../solution-templates/single-layer-web-application) Startup Templates. You can easily enable/disable showing Cookie Consent by configuring the `AbpCookieConsentOptions`
If you want to override the texts in the Cookie Consent component, you just need to define the following localization keys in your localization resource files and change text as you wish:
```json
{
"ThisWebsiteUsesCookie": "This website uses cookies to ensure you get the best experience on the website.",
"CookieConsentAgreePolicies": "If you continue to browse, then you agree to our {0} and {1}.",
"CookieConsentAgreePolicy": "If you continue to browse, then you agree to our {0}.",
"CookieConsentAgreePolicy": "If you continue to browse, then you agree to our {0}."
}
```
> Refer to the [Localization documentation](../framework/fundamentals/localization.md) for more info about defining localization resources and overriding existing localization entries that comes from pre-built modules.