@ -0,0 +1,171 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="2.0.24" />
|
||||
<PackageVersion Include="aliyun-net-sdk-sts" Version="3.1.2" />
|
||||
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
|
||||
<PackageVersion Include="AsyncKeyedLock" Version="6.2.2" />
|
||||
<PackageVersion Include="Autofac" Version="7.1.0" />
|
||||
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
|
||||
<PackageVersion Include="AutoMapper" Version="12.0.1" />
|
||||
<PackageVersion Include="AWSSDK.S3" Version="3.7.300.2" />
|
||||
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.300.2" />
|
||||
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.17.0" />
|
||||
<PackageVersion Include="Azure.Storage.Blobs" Version="12.19.1" />
|
||||
<PackageVersion Include="Blazorise" Version="1.3.2" />
|
||||
<PackageVersion Include="Blazorise.Components" Version="1.3.2" />
|
||||
<PackageVersion Include="Blazorise.DataGrid" Version="1.3.2" />
|
||||
<PackageVersion Include="Blazorise.Snackbar" Version="1.3.2" />
|
||||
<PackageVersion Include="Castle.Core" Version="5.1.1" />
|
||||
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
|
||||
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
|
||||
<PackageVersion Include="Confluent.Kafka" Version="2.3.0" />
|
||||
<PackageVersion Include="Dapper" Version="2.1.21" />
|
||||
<PackageVersion Include="Dapr.AspNetCore" Version="1.12.0" />
|
||||
<PackageVersion Include="Dapr.Client" Version="1.12.0" />
|
||||
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.1.151.7" />
|
||||
<PackageVersion Include="DistributedLock.Core" Version="1.0.5" />
|
||||
<PackageVersion Include="DistributedLock.Redis" Version="1.0.2" />
|
||||
<PackageVersion Include="EphemeralMongo.Core" Version="1.1.3" />
|
||||
<PackageVersion Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" />
|
||||
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
|
||||
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
|
||||
<PackageVersion Include="FluentValidation" Version="11.8.0" />
|
||||
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.6" />
|
||||
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.6" />
|
||||
<PackageVersion Include="HtmlSanitizer" Version="8.0.746" />
|
||||
<PackageVersion Include="IdentityModel" Version="6.2.0" />
|
||||
<PackageVersion Include="IdentityServer4" Version="4.1.2" />
|
||||
<PackageVersion Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageVersion Include="LdapForNet" Version="2.7.15" />
|
||||
<PackageVersion Include="LibGit2Sharp" Version="0.28.0" />
|
||||
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="13.4.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.3.0" />
|
||||
<PackageVersion Include="Markdig.Signed" Version="0.33.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.25" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||
<PackageVersion Include="Minio" Version="6.0.1" />
|
||||
<PackageVersion Include="MongoDB.Driver" Version="2.22.0" />
|
||||
<PackageVersion Include="NEST" Version="7.17.5" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="Nito.AsyncEx.Context" Version="5.1.2" />
|
||||
<PackageVersion Include="Nito.AsyncEx.Coordination" Version="5.1.2" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageVersion Include="NuGet.Versioning" Version="6.7.0" />
|
||||
<PackageVersion Include="NUglify" Version="1.21.0" />
|
||||
<PackageVersion Include="Nullable" Version="1.3.1" />
|
||||
<PackageVersion Include="Octokit" Version="9.0.0" />
|
||||
<PackageVersion Include="OpenIddict.Abstractions" Version="4.10.0" />
|
||||
<PackageVersion Include="OpenIddict.Core" Version="4.10.0" />
|
||||
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="4.10.0" />
|
||||
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="4.10.0" />
|
||||
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="4.10.0" />
|
||||
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="7.21.12" />
|
||||
<PackageVersion Include="Polly" Version="8.2.0" />
|
||||
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
|
||||
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.1" />
|
||||
<PackageVersion Include="Quartz" Version="3.7.0" />
|
||||
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.7.0" />
|
||||
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.7.0" />
|
||||
<PackageVersion Include="Quartz.Serialization.Json" Version="3.7.0" />
|
||||
<PackageVersion Include="RabbitMQ.Client" Version="6.6.0" />
|
||||
<PackageVersion Include="Rebus" Version="7.2.1" />
|
||||
<PackageVersion Include="Rebus.ServiceProvider" Version="9.1.0" />
|
||||
<PackageVersion Include="Scriban" Version="5.9.0" />
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Shouldly" Version="4.2.1" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.0.1" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.6" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.6" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.6" />
|
||||
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
|
||||
<PackageVersion Include="Spectre.Console" Version="0.47.0" />
|
||||
<PackageVersion Include="StackExchange.Redis" Version="2.7.4" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
|
||||
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.3.5" />
|
||||
<PackageVersion Include="System.Linq.Queryable" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Security.Permissions" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="TimeZoneConverter" Version="6.1.0" />
|
||||
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />
|
||||
<PackageVersion Include="xunit" Version="2.6.1" />
|
||||
<PackageVersion Include="xunit.extensibility.execution" Version="2.6.1" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
|
||||
<PackageVersion Include="Fody" Version="6.8.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 7.x.x | :white_check_mark: |
|
||||
| < 7.0.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please don not share vulnerabilities publicly in GitHub or other platforms.
|
||||
You can report security issues by sending a email to `security@abp.io`
|
||||
Your report is immediately evaluated. We publish patch versions for critical vulnerabilities in a week at most.
|
||||
@ -0,0 +1,287 @@
|
||||
# ABP.IO Platform 7.4 RC Has Been Released
|
||||
|
||||
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.4 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
|
||||
|
||||
Try this version and provide feedback for a more stable version of ABP v7.4! Thanks to all of you.
|
||||
|
||||
## Get Started with the 7.4 RC
|
||||
|
||||
Follow the steps below to try version 7.4.0 RC today:
|
||||
|
||||
1) **Upgrade** the ABP CLI to version `7.4.0-rc.1` using a command line terminal:
|
||||
|
||||
````bash
|
||||
dotnet tool update Volo.Abp.Cli -g --version 7.4.0-rc.1
|
||||
````
|
||||
|
||||
**or install** it if you haven't before:
|
||||
|
||||
````bash
|
||||
dotnet tool install Volo.Abp.Cli -g --version 7.4.0-rc.1
|
||||
````
|
||||
|
||||
2) Create a **new application** with the `--preview` option:
|
||||
|
||||
````bash
|
||||
abp new BookStore --preview
|
||||
````
|
||||
|
||||
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options.
|
||||
|
||||
> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application.
|
||||
|
||||
You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).
|
||||
|
||||
## Migration Guides
|
||||
|
||||
There are a few breaking changes in this version that may affect your application.
|
||||
Please see the following migration documents, if you are upgrading from v7.3 or earlier:
|
||||
|
||||
* [ABP Framework 7.3 to 7.4 Migration Guide](https://docs.abp.io/en/abp/7.4/Migration-Guides/Abp-7_4)
|
||||
|
||||
## What's New with ABP Framework 7.4?
|
||||
|
||||
In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections:
|
||||
|
||||
* Dynamic Setting Store
|
||||
* Introducing the `AdditionalAssemblyAttribute`
|
||||
* `CorrelationId` Support on Distributed Events
|
||||
* Database Migration System for EF Core
|
||||
* Other News
|
||||
|
||||
### Dynamic Setting Store
|
||||
|
||||
Prior to this version, it was hard to define settings in different microservices and centrally manage all setting definitions in a single admin application. To make that possible, we used to add project references for all the microservices' service contract packages from a single microservice, so it can know all the setting definitions and manage them.
|
||||
|
||||
In this version, ABP Framework introduces the Dynamic Setting Store, which is an important feature that allows you to collect and get all setting definitions from a single point and overcome the setting management problems on microservices.
|
||||
|
||||
> *Note*: If you are upgrading from an earlier version and using the Setting Management module, you need to create a new migration and apply it to your database because a new database table has been added for this feature.
|
||||
|
||||
### Introducing the `AdditionalAssemblyAttribute`
|
||||
|
||||
In this version, we have introduced the `AdditionalAssemblyAttribute` to define additional assemblies to be part of a module. ABP Framework automatically registers all the services of your module to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection). It finds the service types by scanning types in the assembly that define your module class. Typically, every assembly contains a separate module class definition and modules depend on each other using the `DependsOn` attribute.
|
||||
|
||||
In some rare cases, your module may consist of multiple assemblies and only one of them defines a module class, and you want to make the other assemblies parts of your module. This is especially useful if you can't define a module class in the target assembly or you don't want to depend on that module's dependencies.
|
||||
|
||||
In that case, you can use the `AdditionalAssembly` attribute as shown below:
|
||||
|
||||
```csharp
|
||||
[DependsOn(...)] // Your module dependencies as you normally would do
|
||||
[AdditionalAssembly(typeof(IdentityServiceModule))] // A type in the target assembly (in another assembly)
|
||||
public class IdentityServiceTestModule : AbpModule
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
With the `AdditionalAssembly` attribute definition, ABP loads the assembly containing the `IdentityServiceModule` class as a part of the identity service module. Notice that in this case, none of the module dependencies of the `IdentityServiceModule` are loaded. Because we are not depending on the `IdentityServiceModule`, instead we are just adding its assembly as a part of the `IdentityServiceTestModule`.
|
||||
|
||||
> You can check the [Module Development Basics](https://docs.abp.io/en/abp/7.4/Module-Development-Basics) documentation to learn more.
|
||||
|
||||
### `CorrelationId` Support on Distributed Events
|
||||
|
||||
In this version, `CorrelationId` (a unique key that is used in distributed applications to trace requests across multiple services/operations) is attached to the distributed events, so you can relate events with HTTP requests and can trace all the related activities.
|
||||
|
||||
ABP Framework generates a `correlationId` for the first time when an operation is started and then attaches the current `correlationId` to distributed events as an additional property. For example, if you are using the [transactional outbox or inbox pattern provided by ABP Framework](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#outbox-inbox-for-transactional-events), you can see the `correlationId` in the extra properties of the `IncomingEventInfo` or `OutgoingEventInfo` classes with the standard `X-Correlation-Id` key.
|
||||
|
||||
> You can check [this issue](https://github.com/abpframework/abp/issues/16773) for more information.
|
||||
|
||||
### Database Migration System for EF Core
|
||||
|
||||
In this version, ABP Framework provides base classes and events to migrate the database schema and seed the database on application startup. This system works compatibly with multi-tenancy and whenever a new tenant is created or a tenant's database connection string has been updated, it checks and applies database migrations for the new tenant state.
|
||||
|
||||
This system is especially useful to migrate databases for microservices. In this way, when you deploy a new version of a microservice, you don't need to manually migrate its database.
|
||||
|
||||
You need to take the following actions to use the database migration system:
|
||||
|
||||
* Create a class that derives from `EfCoreRuntimeDatabaseMigratorBase` class, override and implement its `SeedAsync` method. And lastly, execute the `CheckAndApplyDatabaseMigrationsAsync` method of your class in the `OnPostApplicationInitializationAsync` method of your module class.
|
||||
* Create a class that derives from `DatabaseMigrationEventHandlerBase` class, override and implement its `SeedAsync` method. Then, whenever a new tenant is created or a tenant's connection string is changed then the `SeedAsync` method will be executed.
|
||||
|
||||
### Other News
|
||||
|
||||
* [OpenIddict](https://github.com/openiddict/openiddict-core/tree/4.7.0) library has been upgraded to **v4.7.0**. See [#17334](https://github.com/abpframework/abp/pull/17334) for more info.
|
||||
* ABP v7.4 introduces the `Volo.Abp.Maui.Client` package, which is used by the MAUI mobile application in ABP Commercial. See [#17201](https://github.com/abpframework/abp/pull/17201) for more info.
|
||||
* In this version, the `AbpAspNetCoreIntegratedTestBase` class gets a generic type parameter, which expects either a startup class or an ABP module class. This allows us to use configurations from an ABP module or old-style ASP.NET Core Startup class in a test application class and this simplifies the test application project. See [#17039](https://github.com/abpframework/abp/pull/17039) for more info.
|
||||
|
||||
## What's New with ABP Commercial 7.4?
|
||||
|
||||
We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce new features coming with ABP Commercial 7.4.
|
||||
|
||||
### Dynamic Text Template Store
|
||||
|
||||
Prior to this version, it was hard to create text templates in different microservices and centrally manage them in a single admin application. For example, if you would define a text template in your ordering microservice, then those text templates could not be seen on the administration microservice because the administration microservice would not have any knowledge about that text template (because it's hard-coded in the ordering microservice).
|
||||
|
||||
For this reason, in this version, the Dynamic Text Template Store has been introduced to make the [Text Template Management module](https://docs.abp.io/en/commercial/latest/modules/text-template-management) compatible with microservices and distributed systems. It allows you to store and get all text templates from a single point. Thanks to that, you can centrally manage the text templates in your admin application.
|
||||
|
||||
> *Note*: If you are upgrading from an earlier version and are using the Text Template Management module, you need to create a new migration and apply it to your database.
|
||||
|
||||
To enable the dynamic template store, you just need to configure the `TextTemplateManagementOptions` and set the `IsDynamicTemplateStoreEnabled` as true in your module class:
|
||||
|
||||
```csharp
|
||||
Configure<TextTemplateManagementOptions>(options =>
|
||||
{
|
||||
options.IsDynamicTemplateStoreEnabled = true;
|
||||
});
|
||||
```
|
||||
|
||||
Notice this is only needed in the microservice where you centrally manage your text template contents. So, typically you would use the configuration above in your administration microservice. Other microservices automatically save their text template contents to the central database.
|
||||
|
||||
### Suite: Custom Code Support
|
||||
|
||||
In this version, we have implemented the custom code support in Suite. This allows you to customize the generated code-blocks and preserve your custom code changes in the next CRUD Page Generation in Suite. ABP Suite specifies hook-points to allow adding custom code blocks. Then, the code that you wrote to these hook points will be respected and will not be overridden in the next entity generation.
|
||||
|
||||

|
||||
|
||||
To enable custom code support, you should check the *Customizable code* option in the crud page generation page. When you enable the custom code support, you will be seeing some hook-points in your application.
|
||||
|
||||
For example, on the C# side, you'll be seeing some abstract classes and classes that derive from them (for entities, application services, interfaces, domain services, and so on...). You can write your custom code in those classes (`*.Extended.cs`) and the next time when you need to re-generate the entity, your custom code will not be overridden (only the base abstract classes will be re-generated and your changes on Suite will be respected):
|
||||
|
||||
Folder structure | Book.Extended.cs
|
||||
:-------------------------:|:-------------------------:
|
||||
 | 
|
||||
|
||||
> *Note*: If you want to override the entity and add custom code, please do not touch the code between `<suite-custom-code-autogenerated>...</suite-custom-code-autogenerated>` placeholders, because the constructor of the entity should be always re-generated in case of a new property added.
|
||||
|
||||
On the UI side, you can see the *comment placeholders* on the pages for MVC & Blazor applications. These are hook-points provided by ABP Suite and you can write your custom code between these comment sections:
|
||||
|
||||
Folder structure | Books/Index.cshtml
|
||||
:-------------------------:|:-------------------------:
|
||||
 | 
|
||||
|
||||
### MAUI & React Native UI Revisions
|
||||
|
||||
In this version, we have revised MAUI & React Native mobile applications and added new pages, functionalities and made improvements on the UI side.
|
||||
|
||||

|
||||
|
||||
For example, in the MAUI application, we have implemented the following functionalities and changed the UI completely:
|
||||
|
||||
* **User Management Page**: Management page for your application users. You can search, add, update, or delete users of your application.
|
||||
* **Tenants**: Management page for your tenants.
|
||||
* **Settings**: Management page for your application settings. On this page, you can change **the current language**, **the profile picture**, **the current password**, or/and **the current theme**.
|
||||
|
||||
Also, we have aligned the features on both of these mobile options (MAUI & React Native) and showed them in the ["ABP Community Talks 2023.5: Exploring the Options for Mobile Development with the ABP Framework"](https://community.abp.io/events/mobile-development-with-the-abp-framework-ogtwaz5l).
|
||||
|
||||
> If you have missed the event, you can watch from 👉 [here](https://www.youtube.com/watch?v=-wrdngeKgZw).
|
||||
|
||||
### New LeptonX Theme Features
|
||||
|
||||
In the new version of LeptonX Theme, which is v2.4.0-rc.1, there are some new features that we want to mention.
|
||||
|
||||
#### Mobile Toolbars
|
||||
|
||||
The [Toolbar System](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) is used to define *toolbars* on the user interface. Modules (or your application) can add items to a toolbar, then the UI themes can render the toolbar on the layout.
|
||||
|
||||
LeptonX Theme extends this system even further and introduces mobile toolbars with this version. You can create a component and add it as a mobile toolbar as below:
|
||||
|
||||
```csharp
|
||||
public class MyToolbarContributor : IToolbarContributor
|
||||
{
|
||||
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
|
||||
{
|
||||
if (context.Toolbar.Name == LeptonXToolbars.MainMobile)
|
||||
{
|
||||
context.Toolbar.Items.Add(new ToolbarItem(typeof(ShoppingCardToolbarComponent)));
|
||||
|
||||
//other mobile toolbars...
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, the LeptonX Theme will render these mobile toolbars like in the figure below:
|
||||
|
||||

|
||||
|
||||
> **Note**: The Angular UI hasn't been completed yet. We aim to complete it as soon as possible and include it in the next release.
|
||||
|
||||
#### New Error Page Designs
|
||||
|
||||
In this version, we have implemented new error pages. Encounter a fresh look during error situations with the 'New Error Page Designs,' providing informative and visually appealing error displays that enhance user experience:
|
||||
|
||||

|
||||
|
||||
#### Fluid Layout
|
||||
|
||||
In this version, LeptonX Theme introduces the fresh-looking **Fluid Layout**, which is a layout that lets you align elements so that they automatically adjust their alignment and proportions for different page sizes and orientations.
|
||||
|
||||

|
||||
|
||||
> You can visit [the live demo of LeptonX Theme](https://x.leptontheme.com/side-menu) and try the Fluid Layout now!
|
||||
|
||||
### Check & Move Related Entities on Deletion/Demand
|
||||
|
||||
In application modules, there are some entities that have complete relationships with each other such as role-user relations. In such cases, it's a typical requirement to check & move related entities that have a relation with the other entity that is about to be deleted.
|
||||
|
||||
For example, if you need to delete an edition from your system, you would typically want to move the tenant that is associated with that edition. For this purpose, in this version, ABP Commercial allows you to move related entities on deletion/demand.
|
||||
|
||||

|
||||
|
||||
Currently, this feature is implemented for SaaS and Identity Pro modules and for the following relations:
|
||||
|
||||
* Edition - Tenant
|
||||
* Role - User
|
||||
* Organization Unit - User
|
||||
|
||||
Also, it's possible to move the related associated-records before deleting the record. For example, you can move all tenants from an edition as shown in the figure below:
|
||||
|
||||
"Move all tenants" action | "Move all tenants" modal
|
||||
:-------------------------:|:-------------------------:
|
||||
 | 
|
||||
|
||||
### CMS Kit Pro: Page Feedback
|
||||
|
||||
In this version, the **Page Feedback** feature has been added to the [CMS Kit Pro](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index) module. This feature allows you to get feedback from a page in your application.
|
||||
|
||||
This is especially useful if you have content that needs feedback from users. For example, if you have documentation or a blog website, it's a common requirement to assess the quality of the articles and get feedback from users. In that case, you can use this feature:
|
||||
|
||||

|
||||
|
||||
### Chat Module: Deleting Messages & Conversations
|
||||
|
||||
In this version, the [Chat Module](https://docs.abp.io/en/commercial/latest/modules/chat) allows you to delete individual messages or a complete conversation.
|
||||
|
||||
You can enable or disable the message/conversation deletion globally on your application:
|
||||
|
||||

|
||||
|
||||
> **Note**: The Angular UI hasn't been completed yet. We aim to complete it as soon as possible and include it in the next release.
|
||||
|
||||
### Password Complexity Indicators
|
||||
|
||||
In this version, ABP Framework introduces an innovative ["Password Complexity Indicator"](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) feature, designed to enhance security and user experience. This feature dynamically evaluates and rates the strength of user-generated passwords, providing real-time feedback to users as they create or update their passwords. By visually indicating the complexity level, users are guided toward crafting stronger passwords that meet modern security standards.
|
||||
|
||||

|
||||
|
||||
You can check the [Password Complexity Indicator Angular documentation](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) to learn more.
|
||||
|
||||
> **Note**: Currently, this feature is only available for the Angular UI, but we will be implemented for other UIs in the next version.
|
||||
|
||||
## Community News
|
||||
|
||||
### DevNot Developer Summit 2023
|
||||
|
||||

|
||||
|
||||
We are thrilled to announce that the co-founder of [Volosoft](https://volosoft.com/) and Lead Developer of the ABP Framework, Halil Ibrahim Kalkan will give a speech about "Building a Kubernetes Integrated Local Development Environment" in the [Developer Summit 2023 event](https://summit.devnot.com/) on the 7th of October.
|
||||
|
||||
### New ABP Community Posts
|
||||
|
||||
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
|
||||
|
||||
* [ABP Commercial - GDPR Module Overview](https://community.abp.io/posts/abp-commercial-gdpr-module-overview-kvmsm3ku) by [Engincan Veske](https://twitter.com/EngincanVeske)
|
||||
* [Video: ABP Framework Data Transfer Objects](https://community.abp.io/videos/abp-framework-data-transfer-objects-qwebfqz5) by [Hamza Albreem](https://github.com/braim23)
|
||||
* [Video: ABP Framework Essentials: MongoDB](https://community.abp.io/videos/abp-framework-essentials-mongodb-gwlblh5x) by [Hamza Albreem](https://github.com/braim23)
|
||||
* [ABP Modules and Entity Dependencies](https://community.abp.io/posts/abp-modules-and-entity-dependencies-hn7wr093) by [Jack Fistelmann](https://github.com/nebula2)
|
||||
* [How to add dark mode support to the Basic Theme in 3 steps?](https://community.abp.io/posts/how-to-add-dark-mode-support-to-the-basic-theme-in-3-steps-ge9c0f85) by [Enis Necipoğlu](https://twitter.com/EnisNecipoglu)
|
||||
* [Deploying docker image to Azure with yml and bicep through Github Actions](https://community.abp.io/posts/deploying-docker-image-to-azure-with-yml-and-bicep-through-github-actions-cjiuh55m) by [Sturla](https://community.abp.io/members/Sturla)
|
||||
|
||||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.4/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.4 RC and provide feedback to help us release a more stable version.
|
||||
|
||||
Thanks for being a part of this community!
|
||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 101 KiB |
@ -0,0 +1,80 @@
|
||||
# ABP.IO Platform 7.4 Final Has Been Released!
|
||||
|
||||
[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 7.4 versions have been released today.
|
||||
|
||||
## What's New With Version 7.4?
|
||||
|
||||
All the new features were already explained in detail in the [7.4 RC Announcement Post](https://blog.abp.io/abp/ABP.IO-Platform-7-4-RC-Has-Been-Published), so no need to go over them again. Check it out for more details.
|
||||
|
||||
## Getting Started with 7.4
|
||||
|
||||
### Creating New Solutions
|
||||
|
||||
You can create a new solution with the ABP Framework version 7.4 by either using the `abp new` command or generating the CLI command on the [get started page](https://abp.io/get-started).
|
||||
|
||||
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for more.
|
||||
|
||||
### How to Upgrade an Existing Solution
|
||||
|
||||
#### Install/Update the ABP CLI
|
||||
|
||||
First of all, install the ABP CLI or upgrade it to the latest version.
|
||||
|
||||
If you haven't installed it yet:
|
||||
|
||||
```bash
|
||||
dotnet tool install -g Volo.Abp.Cli
|
||||
```
|
||||
|
||||
To update the existing CLI:
|
||||
|
||||
```bash
|
||||
dotnet tool update -g Volo.Abp.Cli
|
||||
```
|
||||
|
||||
#### Upgrading Existing Solutions with the ABP Update Command
|
||||
|
||||
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command:
|
||||
|
||||
```bash
|
||||
abp update
|
||||
```
|
||||
|
||||
Run this command in the root folder of your solution.
|
||||
|
||||
## Migration Guides
|
||||
|
||||
There are breaking changes in this version that may affect your application.
|
||||
Please see the following migration documents, if you are upgrading from v7.3:
|
||||
|
||||
* [ABP Framework 7.3 to 7.4 Migration Guide](https://docs.abp.io/en/abp/7.4/Migration-Guides/Abp-7_4)
|
||||
|
||||
## Community News
|
||||
|
||||
### ABP Community Talks 2023.7: Build Your Content Management System with .NET
|
||||
|
||||
We as the ABP team organized the [**ABP Community Talks 2023.7: Build Your Content Management System with .NET**](https://community.abp.io/events/build-your-own-cms-with-.net-a-first-look-at-abps-content-management-system-kit-3nfvm9ix) event to explore the depths of the CMS Kit Module and its real-world applications. The talk delved into the intricacies of the CMS Kit Module, providing valuable insights into its features and functionalities. Attendees had the opportunity to witness the module in action through live demonstrations and interactive Q&A sessions.
|
||||
|
||||
For those who missed the live session, you can catch up on all the enriching discussions and demonstrations by watching the record below 👇:
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/S9__Hnu29tI?si=vrLWLI3NQX2eaSMD" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
### BASTA! Mainz 2023
|
||||
|
||||

|
||||
|
||||
BASTA! Mainz 2023 has wrapped up, and what an extraordinary journey it has been! We have shared our impressions, highlights, and the incredible impact it had on the tech community in Germany and beyond in a blog post, which you can find at [https://blog.abp.io/abp/BASTA-Mainz-2023-What-a-Blast-in-Germany](https://blog.abp.io/abp/BASTA-Mainz-2023-What-a-Blast-in-Germany).
|
||||
|
||||
### New ABP Community Posts
|
||||
|
||||
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
|
||||
|
||||
* [Moving Background Job Execution To A Separate Application](https://community.abp.io/posts/moving-background-job-execution-to-a-separate-application-my9cgo9a) by [liangshiwei](https://github.com/RealLowis).
|
||||
* [Cascading Option Loading with Extensions System in ABP Angular](https://community.abp.io/posts/cascading-option-loading-with-extensions-system-in-abp-angular-gcxgp0v9) by [Masum Ulu](https://twitter.com/masumulu).
|
||||
* [How to use domain-based tenant resolver in ABP with Angular and OpenIddict](https://community.abp.io/posts/how-to-use-domainbased-tenant-resolver-in-abp-with-angular-and-openiddict-v9y8da7v) by [Mahmut Gündoğdu](https://twitter.com/MahmutGundogdu).
|
||||
|
||||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community.
|
||||
|
||||
## About the Next Version
|
||||
|
||||
The next feature version will be 8.0. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version.
|
||||
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 233 KiB |
@ -0,0 +1,450 @@
|
||||
# Cascading Option Loading with Extensions System in ABP Angular
|
||||
|
||||
This article will show how to load cascading options with an extensions system in ABP Angular. For this example, we'll simulate renting a book process. Besides our default form properties, we'll contribute `Name` property to our `Rent Form Modal` in the Books module. This property will be loaded after `Genre` is selected.
|
||||
|
||||
> Before starting this article, I suggest you read the [ABP Angular Dynamic Form Extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Dynamic-Form-Extensions)
|
||||
|
||||
### Environment
|
||||
|
||||
- **ABP Framework Version:** ~7.3.0 (`~` means that use the latest patch version of the specified release)
|
||||
- **DB Provider:** MongoDB
|
||||
- **Angular Version:** ~16.0.0
|
||||
|
||||
### Project structure
|
||||
|
||||
The books module is not a library; for this demo, it'll placed in the application itself.
|
||||
|
||||

|
||||
|
||||
- **books folder:** Contains default form properties, tokens, models, etc. It's similar to the ABP module structure.
|
||||
- Also I've used **standalone** and **signals** feature in this demo.
|
||||
- **books-extended folder:** Contains only `Name` property for the contribute `Rent Form Modal` inside the Books module.
|
||||
- **For more readability, I've used TS path aliases in this demo. Don't forget to export files in `index.ts` file 🙂**
|
||||
|
||||

|
||||
|
||||
### First look at the demo
|
||||
|
||||

|
||||
|
||||
### What is the Extension system?
|
||||
|
||||

|
||||
|
||||
# Reviewing the code step by step
|
||||
|
||||
**1. Create default form properties for `Rent Form` in the `Books` module**
|
||||
|
||||
- `getInjected` function is the key point of the cascading loading
|
||||
- We can reach and track any value from `Service` or `Component`
|
||||
- In that way we can load options according to the selected value
|
||||
|
||||
```ts
|
||||
// ~/books/defaults/default-books-form.props.ts
|
||||
|
||||
import { Validators } from "@angular/forms";
|
||||
import { map, of } from "rxjs";
|
||||
import { ePropType, FormProp } from "@abp/ng.theme.shared/extensions";
|
||||
import { BookDto, AuthorService, BooksService } from "../proxy";
|
||||
import { RentBookComponent } from "../components";
|
||||
import { DefaultOption } from "../utils";
|
||||
|
||||
const { required } = Validators;
|
||||
|
||||
export const DEFAULT_RENT_FORM_PROPS = FormProp.createMany<BookDto>([
|
||||
{
|
||||
type: ePropType.String,
|
||||
id: "authorId",
|
||||
name: "authorId",
|
||||
displayName: "BookStore::Author",
|
||||
defaultValue: null,
|
||||
validators: () => [required],
|
||||
options: (data) => {
|
||||
const { authors } = data.getInjected(AuthorService);
|
||||
|
||||
return of([
|
||||
DefaultOption,
|
||||
...authors().map((author) => ({ value: author.id, key: author.name })),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ePropType.String,
|
||||
id: "genreId",
|
||||
name: "genreId",
|
||||
displayName: "BookStore::Genre",
|
||||
defaultValue: null,
|
||||
validators: () => [required],
|
||||
options: (data) => {
|
||||
const rentBookComponent = data.getInjected(RentBookComponent);
|
||||
const { genres } = data.getInjected(BooksService);
|
||||
|
||||
const genreOptions = genres().map(({ id, name }) => ({
|
||||
value: id,
|
||||
key: name,
|
||||
}));
|
||||
|
||||
return rentBookComponent.form.controls.authorId.valueChanges.pipe(
|
||||
map((value: string | undefined) =>
|
||||
value ? [DefaultOption, ...genreOptions] : [DefaultOption]
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ePropType.Date,
|
||||
id: "returnDate",
|
||||
name: "returnDate",
|
||||
displayName: "BookStore::ReturnDate",
|
||||
defaultValue: null,
|
||||
validators: () => [required],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
**2. Configure tokens and config options**
|
||||
|
||||
The documentation explains these steps; that's why I won't explain it again. If documents or samples are not enough, please let me know in the comments 🙂
|
||||
|
||||
**Extensions Token**
|
||||
|
||||
```ts
|
||||
// ~/books/tokens/extensions.token.ts
|
||||
|
||||
import { CreateFormPropContributorCallback } from "@abp/ng.theme.shared/extensions";
|
||||
import { InjectionToken } from "@angular/core";
|
||||
import { BookDto } from "../proxy";
|
||||
import { eBooksComponents } from "../enums";
|
||||
import { DEFAULT_RENT_FORM_PROPS } from "../defaults";
|
||||
|
||||
export const DEFAULT_BOOK_STORE_CREATE_FORM_PROPS = {
|
||||
[eBooksComponents.RentBook]: DEFAULT_RENT_FORM_PROPS,
|
||||
};
|
||||
|
||||
export const BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS =
|
||||
new InjectionToken<CreateFormPropContributors>(
|
||||
"BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS"
|
||||
);
|
||||
|
||||
type CreateFormPropContributors = Partial<{
|
||||
[eBooksComponents.RentBook]: CreateFormPropContributorCallback<BookDto>[];
|
||||
/**
|
||||
* Other creation form prop contributors...
|
||||
*/
|
||||
// [eBooksComponents.CreateBook]: CreateFormPropContributorCallback<BookDto>[];
|
||||
}>;
|
||||
```
|
||||
|
||||
**Extensions Config Option**
|
||||
|
||||
```ts
|
||||
// ~/books/models/config-options.ts
|
||||
|
||||
import { CreateFormPropContributorCallback } from "@abp/ng.theme.shared/extensions";
|
||||
import { BookDto } from "../proxy";
|
||||
import { eBooksComponents } from "../enums";
|
||||
|
||||
export type BookStoreRentFormPropContributors = Partial<{
|
||||
[eBooksComponents.RentBook]: CreateFormPropContributorCallback<BookDto>[];
|
||||
}>;
|
||||
|
||||
export interface BooksConfigOptions {
|
||||
rentFormPropContributors?: BookStoreRentFormPropContributors;
|
||||
}
|
||||
```
|
||||
|
||||
**3. Extensions Guard**
|
||||
|
||||
It'll to collect all contributors from [ExtensionsService](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/extensions/src/lib/services/extensions.service.ts)
|
||||
|
||||
```ts
|
||||
// ~/books/guards/extensions.guard.ts
|
||||
|
||||
import { Injectable, inject } from "@angular/core";
|
||||
import { Observable, map, tap } from "rxjs";
|
||||
import { ConfigStateService, IAbpGuard } from "@abp/ng.core";
|
||||
import {
|
||||
ExtensionsService,
|
||||
getObjectExtensionEntitiesFromStore,
|
||||
mapEntitiesToContributors,
|
||||
mergeWithDefaultProps,
|
||||
} from "@abp/ng.theme.shared/extensions";
|
||||
import {
|
||||
BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS,
|
||||
DEFAULT_BOOK_STORE_CREATE_FORM_PROPS,
|
||||
} from "../tokens";
|
||||
|
||||
@Injectable()
|
||||
export class BooksExtensionsGuard implements IAbpGuard {
|
||||
protected readonly configState = inject(ConfigStateService);
|
||||
protected readonly extensions = inject(ExtensionsService);
|
||||
|
||||
canActivate(): Observable<boolean> {
|
||||
const createFormContributors =
|
||||
inject(BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS, { optional: true }) || {};
|
||||
|
||||
return getObjectExtensionEntitiesFromStore(
|
||||
this.configState,
|
||||
"BookStore"
|
||||
).pipe(
|
||||
mapEntitiesToContributors(this.configState, "BookStore"),
|
||||
tap((objectExtensionContributors) => {
|
||||
mergeWithDefaultProps(
|
||||
this.extensions.createFormProps,
|
||||
DEFAULT_BOOK_STORE_CREATE_FORM_PROPS,
|
||||
objectExtensionContributors.createForm,
|
||||
createFormContributors
|
||||
);
|
||||
}),
|
||||
map(() => true)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Yes, I'm still using class-based guard 🙂 much more flexible...
|
||||
|
||||
**4. RentBookComponent**
|
||||
|
||||
- Our trackable variable is defined here `(form:FormGroup)`, which means We'll track this variable in `options` property at defaults || contributors files.
|
||||
- Providing `AuthorService`, also `EXTENSIONS_IDENTIFIER` for the reach dynamic properties
|
||||
|
||||
```ts
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Injector,
|
||||
Output,
|
||||
inject,
|
||||
} from "@angular/core";
|
||||
import { FormGroup } from "@angular/forms";
|
||||
import { CoreModule, uuid } from "@abp/ng.core";
|
||||
import { ThemeSharedModule } from "@abp/ng.theme.shared";
|
||||
import {
|
||||
EXTENSIONS_IDENTIFIER,
|
||||
FormPropData,
|
||||
UiExtensionsModule,
|
||||
generateFormFromProps,
|
||||
} from "@abp/ng.theme.shared/extensions";
|
||||
import { AuthorService, BookDto, BooksService } from "../../proxy";
|
||||
import { eBooksComponents } from "../../enums";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-rent-book",
|
||||
templateUrl: "./rent-book.component.html",
|
||||
imports: [CoreModule, UiExtensionsModule, ThemeSharedModule],
|
||||
providers: [
|
||||
{
|
||||
provide: EXTENSIONS_IDENTIFIER,
|
||||
useValue: eBooksComponents.RentBook,
|
||||
},
|
||||
AuthorService,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RentBookComponent {
|
||||
protected readonly injector = inject(Injector);
|
||||
protected readonly authorService = inject(AuthorService);
|
||||
protected readonly booksService = inject(BooksService);
|
||||
|
||||
//#region Just for demo
|
||||
readonly #authors = this.authorService.authors();
|
||||
readonly #genres = this.booksService.genres();
|
||||
readonly #books = this.booksService.books();
|
||||
//#endregion
|
||||
|
||||
protected modalVisible = true;
|
||||
@Output() modalVisibleChange = new EventEmitter<boolean>();
|
||||
|
||||
selected: BookDto;
|
||||
form: FormGroup;
|
||||
modalBusy = false;
|
||||
|
||||
protected buildForm(): void {
|
||||
const data = new FormPropData(this.injector, this.selected);
|
||||
this.form = generateFormFromProps(data);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.buildForm();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalBusy = true;
|
||||
|
||||
const { authorId, genreId, bookId, returnDate } = this.form.value;
|
||||
|
||||
//#region Just for demo
|
||||
const authorName = this.#authors.find(({ id }) => id === authorId).name;
|
||||
const genreName = this.#genres.find(({ id }) => id === genreId).name;
|
||||
const bookName = this.#books.find(({ id }) => id === bookId).name;
|
||||
//#endregion
|
||||
|
||||
this.booksService.rentedBooks.update((books) => [
|
||||
{
|
||||
id: uuid(),
|
||||
name: bookName,
|
||||
author: authorName,
|
||||
genre: genreName,
|
||||
returnDate,
|
||||
},
|
||||
...books,
|
||||
]);
|
||||
|
||||
this.modalBusy = false;
|
||||
this.modalVisible = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<abp-modal
|
||||
[visible]="modalVisible"
|
||||
[busy]="modalBusy"
|
||||
(visibleChange)="modalVisibleChange.next($event)"
|
||||
>
|
||||
<ng-template #abpHeader>
|
||||
<h3>{{ 'BookStore::RentABook' | abpLocalization }}</h3>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #abpBody>
|
||||
<ng-template #loaderRef>
|
||||
<div class="text-center">
|
||||
<i class="fa fa-pulse fa-spinner" aria-hidden="true"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<form
|
||||
*ngIf="form; else loaderRef"
|
||||
[formGroup]="form"
|
||||
(ngSubmit)="save()"
|
||||
validateOnSubmit
|
||||
>
|
||||
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
|
||||
</form>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #abpFooter>
|
||||
<button abpClose type="button" class="btn btn-secondary">
|
||||
{{ 'AbpIdentity::Cancel' | abpLocalization }}
|
||||
</button>
|
||||
<abp-button
|
||||
iconClass="fa fa-check"
|
||||
[disabled]="form?.invalid"
|
||||
(click)="save()"
|
||||
>
|
||||
{{ 'AbpIdentity::Save' | abpLocalization }}
|
||||
</abp-button>
|
||||
</ng-template>
|
||||
</abp-modal>
|
||||
```
|
||||
|
||||
Up to now, we have constructed our module's default form properties.
|
||||
|
||||
- As you can see, there are no book names we'll add them via contributors
|
||||
|
||||

|
||||
|
||||
## Next, add new property dynamically (book name list as dropdown)
|
||||
|
||||
- Created new folder ./src/app/books-extended
|
||||
- Create contributors/form-prop.contributors.ts
|
||||
|
||||
```ts
|
||||
// ~/books-extened/contributors/form-prop.contributors.ts
|
||||
|
||||
import { Validators } from "@angular/forms";
|
||||
import { map } from "rxjs";
|
||||
import {
|
||||
ePropType,
|
||||
FormProp,
|
||||
FormPropList,
|
||||
} from "@abp/ng.theme.shared/extensions";
|
||||
import {
|
||||
BookDto,
|
||||
BookStoreRentFormPropContributors,
|
||||
BooksService,
|
||||
DefaultOption,
|
||||
RentBookComponent,
|
||||
eBooksComponents,
|
||||
} from "@book-store/books";
|
||||
|
||||
const { required, maxLength } = Validators;
|
||||
|
||||
const bookIdProp = new FormProp<BookDto>({
|
||||
type: ePropType.String,
|
||||
id: "bookId",
|
||||
name: "bookId",
|
||||
displayName: "BookStore::Name",
|
||||
options: (data) => {
|
||||
const rentBook = data.getInjected(RentBookComponent);
|
||||
const { books } = data.getInjected(BooksService);
|
||||
const bookOptions = books().map(({ id, name }) => ({
|
||||
value: id,
|
||||
key: name,
|
||||
}));
|
||||
|
||||
return rentBook.form.controls.genreId.valueChanges.pipe(
|
||||
map((value: string | undefined) =>
|
||||
value ? [DefaultOption, ...bookOptions] : [DefaultOption]
|
||||
)
|
||||
);
|
||||
},
|
||||
validators: () => [required, maxLength(255)],
|
||||
});
|
||||
|
||||
export function bookIdPropContributor(propList: FormPropList<BookDto>) {
|
||||
propList.addByIndex(bookIdProp, 2);
|
||||
}
|
||||
|
||||
export const bookStoreRentFormPropContributors: BookStoreRentFormPropContributors =
|
||||
{
|
||||
[eBooksComponents.RentBook]: [bookIdPropContributor],
|
||||
};
|
||||
```
|
||||
|
||||
- Load new contributions via routing & forLazy method
|
||||
|
||||
```ts
|
||||
// ~/app-routing.module.ts
|
||||
import { bookStoreRentFormPropContributors } from "./books-extended/contributors/form-prop.contributors";
|
||||
|
||||
const routes: Routes = [
|
||||
// other routes...
|
||||
{
|
||||
path: "books",
|
||||
loadChildren: () =>
|
||||
import("@book-store/books").then((m) =>
|
||||
m.BooksModule.forLazy({
|
||||
rentFormPropContributors: bookStoreRentFormPropContributors,
|
||||
})
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, {})],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
```
|
||||
|
||||
Finally, we've added a new property to our module, and it'll be loaded after `Genre` is selected.
|
||||
|
||||
## Conclusion
|
||||
|
||||

|
||||
|
||||
- In ABP Angular, we can create form properties and load dropdown options dynamically via the Extensions System
|
||||
- We can reach and track any value from `Service` or `Component`
|
||||
- We can create our custom library or module and contribute it to any module in the application
|
||||
|
||||
Thanks for reading, I hope it was helpful. If you have any questions, please let me know in the comments section. 👋👋
|
||||
|
||||
> You can find the source code of this article on [Github](https://github.com/abpframework/abp-samples/tree/master/AngularCascadingOptionLoading/Volo.BookStore)
|
||||
|
After Width: | Height: | Size: 8.5 MiB |
|
After Width: | Height: | Size: 57 KiB |