Browse Source

Merge branch 'dev' into feature/ssr-support

# Conflicts:
#	npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
#	npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.ts
#	npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts
#	npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts
#	npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts
#	npm/ng-packs/packages/theme-basic/src/lib/handlers/lazy-style.handler.ts
#	npm/ng-packs/packages/theme-basic/src/lib/services/layout.service.ts
#	npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts
feature/ssr-migration-schematic
erdemcaygor 1 year ago
parent
commit
53f68e9ab0
  1. 10
      Directory.Packages.props
  2. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  3. 28
      docs/en/Community-Articles/2025-07-17-summar-campaign/post.md
  4. 6
      docs/en/docs-nav.json
  5. 221
      docs/en/framework/architecture/modularity/installer-projects.md
  6. 17
      docs/en/framework/infrastructure/json.md
  7. 23
      docs/en/framework/ui/maui/index.md
  8. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-solution-structure-tiered.png
  9. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-solution-structure.png
  10. 8
      docs/en/get-started/layered-web-application.md
  11. 10
      docs/en/solution-templates/microservice/index.md
  12. 10
      docs/en/solution-templates/microservice/main-components.md
  13. 5
      docs/en/tutorials/book-store/part-05.md
  14. 6
      framework/src/Volo.Abp.BlazoriseUI/Components/EntityActions.razor.cs
  15. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs
  16. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliConsts.cs
  17. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/AbpIoApiKeyService.cs
  18. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs
  19. 24
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  20. 80
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
  21. 92
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEfCoreNavigationHelper_Tests.cs
  22. 5
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  23. 16
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  24. 31
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/BlogPost.cs
  25. 17
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  26. 9
      latest-versions.json
  27. 4
      modules/README.md
  28. 6
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogFeature.cs
  29. 4
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/UserMarkedItem.cs
  30. 10
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MediaDescriptors/MediaDescriptor.cs
  31. 26
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs
  32. 10
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/DocumentContributor.cs
  33. 20
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionRecord.cs
  34. 6
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecord.cs
  35. 24
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLog.cs
  36. 35
      modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs
  37. 1
      modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs
  38. 6
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.Designer.cs
  39. 2
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.cs
  40. 4
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
  41. 3
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs
  42. 8
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictErrors.cs
  43. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs
  44. 8
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs
  45. 92
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs
  46. 5
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs
  47. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Tokens/OpenIddictTokenConsts.cs
  48. 19
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictDomainModule.cs
  49. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs
  50. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/AbpAuthorizationManager.cs
  51. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Scopes/AbpScopeManager.cs
  52. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/AbpTokenManager.cs
  53. 6
      npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/account.service.ts
  54. 6
      npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/profile.service.ts
  55. 6
      npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/web/areas/account/controllers/account.service.ts
  56. 13
      npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts
  57. 14
      npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts
  58. 16
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  59. 10
      npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts
  60. 11
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
  61. 8
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts
  62. 18
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  63. 16
      npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
  64. 35
      npm/ng-packs/packages/components/chart.js/src/chart.component.ts
  65. 6
      npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts
  66. 15
      npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts
  67. 12
      npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts
  68. 28
      npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts
  69. 41
      npm/ng-packs/packages/components/page/src/page-part.directive.ts
  70. 14
      npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts
  71. 14
      npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts
  72. 6
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  73. 12
      npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts
  74. 6
      npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts
  75. 10
      npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts
  76. 35
      npm/ng-packs/packages/core/src/lib/directives/for.directive.ts
  77. 30
      npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts
  78. 6
      npm/ng-packs/packages/core/src/lib/directives/init.directive.ts
  79. 35
      npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts
  80. 35
      npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts
  81. 10
      npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts
  82. 10
      npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts
  83. 5
      npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts
  84. 5
      npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts
  85. 13
      npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts
  86. 13
      npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts
  87. 13
      npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts
  88. 5
      npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts
  89. 9
      npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts
  90. 8
      npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts
  91. 8
      npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts
  92. 8
      npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts
  93. 8
      npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts
  94. 14
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  95. 5
      npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts
  96. 6
      npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts
  97. 6
      npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts
  98. 6
      npm/ng-packs/packages/core/src/lib/services/list.service.ts
  99. 17
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  100. 16
      npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts

10
Directory.Packages.props

@ -128,11 +128,11 @@
<PackageVersion Include="NUglify" Version="1.21.15" />
<PackageVersion Include="Nullable" Version="1.3.1" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Core" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.0.0" />
<PackageVersion Include="OpenIddict.Core" Version="7.0.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="7.0.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="7.0.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="7.0.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.23.80" />
<PackageVersion Include="Polly" Version="8.5.2" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />

4
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -1892,6 +1892,8 @@
"SEOTitle": "SEO URL",
"InvalidYouTubeUrl": "The URL you entered is not a valid YouTube video link. Please make sure it points to a specific video and try again.",
"SelectAnOption": "Select an option",
"MostPopular": "Most Popular"
"MostPopular": "Most Popular",
"AnnouncmentsPageTitle": "ABP Community Announcements | Stay Updated with the Latest News",
"AnnouncmentsPageDescription": "Get the latest news, feature updates, release notes, and important announcements about the ABP framework and .NET ecosystem. Stay ahead with timely information directly from the ABP team."
}
}

28
docs/en/Community-Articles/2025-07-17-summar-campaign/post.md

@ -0,0 +1,28 @@
**It is going to get hotter with ABP’s Summer Campaign!**
Since it’s summer time, we wanted to make it even hotter by announcing a summer campaign! From July 21 to 31 we are offering a 20% discount on all ABP licenses. Now is the best time to invest in ABP and start developing asp net applications faster without wasting your time with repetitive tasks.
## Summer Campaign Terms
Please review the following terms and conditions carefully.
* This offer is available for extensions and new purchases.
* Developer seat purchases are also included to the campaign.
* Campaign is available from July 21st to July 31st.
* Discounts are valid on selected licenses only.
* This offer cannot be combined with other promotions or discounts.
**Why Choose ABP?**
ABP offers a powerful infrastructure, simplifying modern ASP.NET core development. It helps develop modern ASP.NET applications, including ASP.NET core MVC web applications, blazor front-end projects, and angular .NET Core solutions.
-The core framework and pre-built modules are designed with microservice architecture in mind.
-ABP provides a module system that allows you to develop reusable application modules.
-Helps implement a DDD based layered architecture and build a maintainable code base.
-Easily manage SaaS applications with integrated multi-tenancy, from database to UI.
**This Offer Ends July 31, So Hurry Up!**
This summer campaign is running from July 21 to July 31, so don’t miss your chance. Now is the perfect opportunity to enhance your asp net web development with ABP and benefit from our exclusive features.
Get Your Discount Now: [https://abp.io/pricing?utm_source=abpwebsite&utm_medium=referral&utm_campaign=summer25_blog](https://abp.io/pricing?utm_source=abpwebsite&utm_medium=referral&utm_campaign=summer25_blog)

6
docs/en/docs-nav.json

@ -2127,11 +2127,11 @@
{
"text": "Microservice Solution",
"isLazyExpandable": true,
"path": "solution-templates/microservice",
"path": "solution-templates/microservice/index.md",
"items":[
{
"text": "Overview",
"path": "solution-templates/microservice"
"path": "solution-templates/microservice/overview.md"
},
{
"text": "Solution Structure",
@ -2187,7 +2187,7 @@
},
{
"text": "Swagger integration",
"path": "solution-templates/microservice/swagger.md"
"path": "solution-templates/microservice/swagger-integration.md"
},
{
"text": "Permission management",

221
docs/en/framework/architecture/modularity/installer-projects.md

@ -0,0 +1,221 @@
# Module Installer Projects
Each ABP module includes an `.Installer` project (e.g., `Volo.Abp.Account.Installer`) that serves as a **Virtual File System container** for module installation and resource management. These projects are essential for the ABP CLI to understand and install modules properly.
## Purpose of Installer Projects
Installer projects have three main purposes:
1. **Virtual File System Integration**: Register the module's embedded resources with ABP's Virtual File System
2. **Resource Packaging**: Package module metadata files (`.abpmdl` and `.abppkg`) as embedded resources
3. **CLI Integration**: Enable the ABP CLI to understand module structure and install modules automatically
## Structure of Installer Projects
### Project Files
- **`{ModuleName}.Installer.csproj`**: References `Volo.Abp.VirtualFileSystem` and embeds module metadata files
- **`InstallationNotes.md`**: Documentation for the module
- **`Volo/Abp/{ModuleName}/Abp{ModuleName}InstallerModule.cs`**: The core module class that registers embedded resources
### Example Installer Module
```csharp
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Account;
[DependsOn(typeof(AbpVirtualFileSystemModule))]
public class AbpAccountInstallerModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAccountInstallerModule>();
});
}
}
```
### Project Configuration
The `.csproj` file embeds module metadata as content:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
</ItemGroup>
<ItemGroup>
<!-- Embed module definition file -->
<Content Include="..\..\Volo.Abp.Account.abpmdl">
<Pack>true</Pack>
<PackagePath>content\</PackagePath>
</Content>
<!-- Embed package definition files -->
<Content Include="..\..\**\*.abppkg*">
<Pack>true</Pack>
<PackagePath>content\</PackagePath>
</Content>
</ItemGroup>
</Project>
```
## Module Metadata Files
### `.abpmdl` (Module Definition)
The module definition file describes the module's structure and packages:
```json
{
"folders": {
"items": {
"src": {},
"test": {}
}
},
"packages": {
"Volo.Abp.Account.Web": {
"path": "src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg",
"folder": "src"
},
"Volo.Abp.Account.Application": {
"path": "src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg",
"folder": "src"
}
}
}
```
### `.abppkg` (Package Definition)
Each package has a definition file that specifies its role:
```json
{
"role": "lib.application"
}
```
Common roles:
- `lib.application`: Application layer package
- `lib.mvc`: MVC/Web layer package
- `lib.domain`: Domain layer package
- `lib.domain-shared`: Shared domain layer package
- `lib.efcore`: Entity Framework Core package
## How Installer Projects Work
### 1. CLI Installation Process
When you run `abp add-module Volo.Abp.Account`:
1. **Download Installer Package**: CLI downloads `Volo.Abp.Account.Installer` from NuGet
2. **Read Module Definition**: CLI reads the embedded `.abpmdl` file to understand module structure
3. **Read Package Definitions**: CLI reads `.abppkg` files to understand package roles
4. **Install Packages**: CLI installs appropriate packages to correct project types based on roles
5. **Add Dependencies**: CLI adds module dependencies to project module classes
### 2. Virtual File System Integration
The `InstallerModule` registers itself with the Virtual File System:
```csharp
options.FileSets.AddEmbedded<AbpAccountInstallerModule>();
```
This makes embedded resources available at runtime and enables:
- Access to module metadata
- Resource file management
- Module configuration
## Creating Installer Projects for New Modules
### Required Files
1. **Project File**: `{ModuleName}.Installer.csproj`
2. **Module Class**: `Abp{ModuleName}InstallerModule.cs`
3. **Documentation**: `InstallationNotes.md`
4. **Module Definition**: `{ModuleName}.abpmdl` (in module root)
5. **Package Definitions**: `{PackageName}.abppkg` (in each package)
### Template Structure
```
modules/your-module/
├── src/
│ ├── Volo.Abp.YourModule.Installer/
│ │ ├── Volo.Abp.YourModule.Installer.csproj
│ │ ├── InstallationNotes.md
│ │ └── Volo/
│ │ └── Abp/
│ │ └── YourModule/
│ │ └── AbpYourModuleInstallerModule.cs
│ └── [other packages]/
├── Volo.Abp.YourModule.abpmdl
└── [other module files]
```
### Package Definition Examples
For different package types:
```json
// Application package
{ "role": "lib.application" }
// MVC package
{ "role": "lib.mvc" }
// Domain package
{ "role": "lib.domain" }
// EF Core package
{ "role": "lib.efcore" }
```
## Why Installer Projects Appear "Empty"
Installer projects appear minimal because their primary function is infrastructure, not business logic:
- **No Business Logic**: Business logic belongs in the actual module packages
- **Pure Infrastructure**: They only handle module installation and resource management
- **CLI Integration**: They enable automated module installation through the ABP CLI
- **Resource Management**: They package and distribute module metadata
## Best Practices
1. **Follow Naming Convention**: Use `{ModuleName}.Installer` for the project name
2. **Include Documentation**: Always provide `InstallationNotes.md` with module information
3. **Proper Dependencies**: Only depend on `Volo.Abp.VirtualFileSystem`
4. **Embed All Metadata**: Include both `.abpmdl` and `.abppkg` files
5. **Test Installation**: Verify your installer works with `abp add-module` command
## Troubleshooting
### Common Issues
1. **Missing .abpmdl file**: Ensure the module definition file exists in the module root
2. **Missing .abppkg files**: Each package needs a definition file
3. **Incorrect roles**: Use appropriate roles for each package type
4. **CLI not finding module**: Verify the installer package is published to NuGet
### Verification Steps
1. Build the installer project: `dotnet build`
2. Check embedded resources: Verify `.abpmdl` and `.abppkg` files are embedded
3. Test CLI installation: `abp add-module YourModule`
4. Verify dependencies: Check that module dependencies are added correctly
This installer system enables ABP's sophisticated module architecture, allowing for automated installation with proper dependency resolution and project type matching.

17
docs/en/framework/infrastructure/json.md

@ -69,6 +69,19 @@ Add [Volo.Abp.Json.Newtonsoft](https://www.nuget.org/packages/Volo.Abp.Json.Newt
## Configuring JSON options in ASP.NET Core
You can change the JSON behavior in ASP.NET Core by configuring [JsonOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.jsonoptions) or
[MvcNewtonsoftJsonOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.mvcnewtonsoftjsonoptions)(if you use `Newtonsoft.Json`)
Configuring JSON options in ABP does not affect ASP.NET Core's JSON settings. To modify ASP.NET Core JSON behavior, configure [JsonOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.jsonoptions) or [MvcNewtonsoftJsonOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.mvcnewtonsoftjsonoptions) (if you're using `Newtonsoft.Json`) separately.
Example:
```csharp
Configure<JsonOptions>(options =>
{
//options.SerializerOptions
});
// If you use Newtonsoft.Json
Configure<MvcNewtonsoftJsonOptions>(options =>
{
//options.SerializerSettings
});
```

23
docs/en/framework/ui/maui/index.md

@ -13,6 +13,8 @@ ABP Commercial platform provides a basic [MAUI](https://docs.microsoft.com/en-us
Run the backend application described in the [getting started document](../../../get-started/index.md).
## Run the Mobile Application
Open the `appsettings.json` in the `MAUI` project:
{{ if Tiered == "Yes" }}
@ -25,6 +27,12 @@ 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.
> 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).
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.
### Android
If you get the following error when connecting to the emulator or a physical phone, you need to set up port mapping.
@ -65,10 +73,11 @@ If you run the MAUI on a Mac agent, the remote iOS Simulator can't access the ba
## User Interface
The MAUI template consists of four pages:
* **Homepage**: This is the welcome page of the application.
* **Users**: 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**.
- **Homepage**: This is the welcome page of the application.
- **Users**: 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**.
### Homepage
@ -86,12 +95,6 @@ The MAUI template consists of four pages:
![Maui Settings Page](../../../images/maui-settings-page.png)
## Run the Mobile Application
You can run the MAUI application through Visual Studio or any other IDE that supports MAUI. After the application is up and running, you can continue to develop your application based on this startup template.
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.
### Advanced
#### Validation

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-solution-structure-tiered.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-solution-structure.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 68 KiB

8
docs/en/get-started/layered-web-application.md

@ -75,8 +75,16 @@ Installing a module after creating the solution may require manual steps. So, it
Once you select the desired modules, click the *Next* button for the *Solution Structure* screen:
{{ if Tiered == "Yes" }}
![abp-studio-new-solution-dialog-solution-structure-tiered](images/abp-studio-new-solution-dialog-solution-structure-tiered.png)
{{ else }}
![abp-studio-new-solution-dialog-solution-structure](images/abp-studio-new-solution-dialog-solution-structure.png)
{{ end }}
It creates a separate host application that only serves the HTTP (REST) APIs. The web application then performs remote HTTP calls to that application for every operation. If the *Tiered* option is not selected, then the web and HTTP APIs are hosted in a single application, and the calls from the UI layer to the API layer are performed in-process.
The tiered architecture allows you to host the web (UI) application in a server that can not access to your database server. However, it brings a slight loss of performance (because of the HTTP calls between UI and HTTP API applications) and makes your architecture, development, and deployment more complex. If you don't understand the tiered structure, just skip it.

10
docs/en/solution-templates/microservice/index.md

@ -1,5 +1,15 @@
# ABP Studio: Microservice Solution Template
````json
//[doc-nav]
{
"Next": {
"Name": "Microservice Solution: Overview",
"Path": "solution-templates/microservice/overview"
}
}
````
> You must have an ABP Business or a higher license to use this startup template.
ABP Studio provides pre-architected and production-ready templates to jump start a new solution. One of them is the Microservice solution template. You can use it to build distributed systems with common microservice patterns. It includes multiple services, API gateways and applications that are well integrated to each other and ready to be a great base solution for your microservice system.

10
docs/en/solution-templates/microservice/main-components.md

@ -1,5 +1,15 @@
# Microservice Solution: Main Components
```json
//[doc-nav]
{
"Next": {
"Name": "Microservices",
"Path": "solution-templates/microservice/microservices"
}
}
```
> You must have an ABP Business or a higher license to be able to create a microservice solution.
The solution consists of various applications, API gateways and services. The following documents explains these components in details:

5
docs/en/tutorials/book-store/part-05.md

@ -72,7 +72,12 @@ public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvide
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
//Dashboard permissions
bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Host, L("Permission:Dashboard"), MultiTenancySides.Host);
bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Tenant, L("Permission:Dashboard"), MultiTenancySides.Tenant);
//Books permissions
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));
booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit"));

6
framework/src/Volo.Abp.BlazoriseUI/Components/EntityActions.razor.cs

@ -42,10 +42,10 @@ public partial class EntityActions<TItem> : ComponentBase
{
Actions.Add(action);
}
private bool DisabledOrNoActions()
protected virtual bool DisabledOrNoActions()
{
return Disabled || !Actions.Any(t => t is { Visible: true, HasPermission: true });
return Disabled || (Actions.Any() && Actions.All(t => !t.Visible || !t.HasPermission));
}
protected override void OnInitialized()

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs

@ -47,7 +47,7 @@ public class AuthService : IAuthService, ITransientDependency
return null;
}
var url = $"{CliUrls.WwwAbpIo}api/license/login-info";
var url = $"{CliUrls.AccountAbpIo}api/license/login-info";
var client = CliHttpClientFactory.CreateClient();
@ -130,7 +130,7 @@ public class AuthService : IAuthService, ITransientDependency
public async Task<bool> CheckMultipleOrganizationsAsync(string username)
{
var url = $"{CliUrls.WwwAbpIo}api/license/check-multiple-organizations?username={username}";
var url = $"{CliUrls.AccountAbpIo}api/license/check-multiple-organizations?username={username}";
var client = CliHttpClientFactory.CreateClient();

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliConsts.cs

@ -12,7 +12,7 @@ public static class CliConsts
public static string GithubHttpClientName = "GithubHttpClient";
public static string LogoutUrl = CliUrls.WwwAbpIo + "api/license/logout";
public static string LogoutUrl = CliUrls.AccountAbpIo + "api/license/logout";
public static string LicenseCodePlaceHolder = @"<LICENSE_CODE/>";

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/AbpIoApiKeyService.cs

@ -51,7 +51,7 @@ public class AbpIoApiKeyService : IApiKeyService, ITransientDependency
return _apiKeyResult;
}
var url = $"{CliUrls.WwwAbpIo}api/license/api-key";
var url = $"{CliUrls.AccountAbpIo}api/license/api-key";
var client = _cliHttpClientFactory.CreateClient();
using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, _logger))

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs

@ -88,7 +88,7 @@ public class TemplateInfoProvider : ITemplateInfoProvider, ITransientDependency
try
{
var url = $"{CliUrls.WwwAbpIo}api/license/check-user";
var url = $"{CliUrls.AccountAbpIo}api/license/check-user";
var client = _cliHttpClientFactory.CreateClient();
using (var response = await client.GetHttpResponseMessageWithRetryAsync(url, CancellationTokenProvider.Token, Logger))

24
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs

@ -60,7 +60,7 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return;
}
var foreignKeys = entityEntry.Metadata.GetForeignKeys().ToList();
var foreignKeys = entityEntry.Metadata.GetForeignKeys();
foreach (var foreignKey in foreignKeys)
{
var principal = stateManager.FindPrincipal(internalEntityEntityEntry, foreignKey);
@ -75,15 +75,20 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
continue;
}
abpEntityEntry.UpdateNavigationEntries();
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey);
if (navigationEntry != null && checkEntityEntryState && entityEntry.State == EntityState.Unchanged)
{
abpEntityEntry.UpdateNavigation(entityEntry, navigationEntry);
}
if (!abpEntityEntry.IsModified && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
abpEntityEntry.IsModified = true;
DetectChanges(abpEntityEntry.EntityEntry, false);
}
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey);
if (navigationEntry != null && IsEntityEntryChanged(entityEntry))
{
navigationEntry.IsModified = true;
@ -115,15 +120,20 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
continue;
}
abpEntityEntry.UpdateNavigationEntries();
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey);
if (navigationEntry != null && checkEntityEntryState && entityEntry.State == EntityState.Unchanged)
{
abpEntityEntry.UpdateNavigation(entityEntry, navigationEntry);
}
if (!abpEntityEntry.IsModified && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
abpEntityEntry.IsModified = true;
DetectChanges(abpEntityEntry.EntityEntry, false);
}
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey);
if (navigationEntry != null && (!checkEntityEntryState || IsEntityEntryChanged(entityEntry)))
{
navigationEntry.IsModified = true;

80
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs

@ -1,4 +1,4 @@
using System.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@ -21,10 +21,7 @@ public class AbpEntityEntry
{
return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified);
}
set
{
_isModified = value;
}
set => _isModified = value;
}
public AbpEntityEntry(string id, EntityEntry entityEntry)
@ -34,46 +31,29 @@ public class AbpEntityEntry
NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList();
}
public void UpdateNavigationEntries()
public void UpdateNavigation(EntityEntry entityEntry, AbpNavigationEntry navigationEntry)
{
foreach (var navigationEntry in NavigationEntries)
if (IsModified ||
EntityEntry.State == EntityState.Modified ||
navigationEntry.IsModified)
{
if (IsModified ||
EntityEntry.State == EntityState.Modified ||
navigationEntry.IsModified ||
navigationEntry.NavigationEntry.IsModified)
{
continue;
}
var navigation = EntityEntry.Navigations.FirstOrDefault(n => n.Metadata.Name == navigationEntry.Name);
var currentValue = AbpNavigationEntry.GetOriginalValue(navigation?.CurrentValue);
if (currentValue == null)
{
continue;
}
return;
}
switch (navigationEntry.OriginalValue)
{
case null:
navigationEntry.OriginalValue = currentValue;
break;
case IEnumerable originalValueCollection when currentValue is IEnumerable currentValueCollection:
{
var existingList = originalValueCollection.Cast<object?>().ToList();
var newList = currentValueCollection.Cast<object?>().ToList();
if (newList.Count > existingList.Count)
{
navigationEntry.OriginalValue = currentValue;
}
var currentValue = navigationEntry.NavigationEntry.CurrentValue;
if (currentValue == null)
{
return;
}
break;
}
default:
navigationEntry.OriginalValue = currentValue;
break;
}
if (navigationEntry.NavigationEntry is CollectionEntry)
{
navigationEntry.OriginalValue ??= new List<object>();
navigationEntry.OriginalValue.As<List<object>>().Add(entityEntry.Entity);
}
else
{
navigationEntry.OriginalValue = currentValue;
}
}
}
@ -86,7 +66,7 @@ public class AbpNavigationEntry
public bool IsModified { get; set; }
public List<object>? OriginalValue { get; set; }
public object? OriginalValue { get; set; }
public object? CurrentValue => NavigationEntry.CurrentValue;
@ -94,21 +74,9 @@ public class AbpNavigationEntry
{
NavigationEntry = navigationEntry;
Name = name;
OriginalValue = GetOriginalValue(navigationEntry.CurrentValue);
}
public static List<object>? GetOriginalValue(object? currentValue)
{
if (currentValue is null)
if (navigationEntry.CurrentValue != null )
{
return null;
OriginalValue = navigationEntry is CollectionEntry ? new List<object>() : navigationEntry.CurrentValue;
}
if (currentValue is IEnumerable enumerable)
{
return enumerable.Cast<object>().ToList();
}
return new List<object> { currentValue };
}
}

92
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEfCoreNavigationHelper_Tests.cs

@ -0,0 +1,92 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore;
public class AbpEfCoreNavigationHelper_Tests : EntityFrameworkCoreTestBase
{
private readonly IRepository<Blog, Guid> _blogRepository;
public AbpEfCoreNavigationHelper_Tests()
{
_blogRepository = GetRequiredService<IRepository<Blog, Guid>>();
}
[Fact]
public async Task Performance_Test()
{
//These time taken varies on different machines.
//I used relatively large values, but it can also check for performance problem.
var batchUpdateTime = TimeSpan.FromSeconds(30);
var queryTime = TimeSpan.FromSeconds(10);
if (!Environment.GetEnvironmentVariable("GITHUB_ACTIONS").IsNullOrWhiteSpace())
{
batchUpdateTime = batchUpdateTime * 6;
queryTime = queryTime * 6;
}
var stopWatch = Stopwatch.StartNew();
await WithUnitOfWorkAsync(async () =>
{
for (var i = 0; i < 5 * 1000; i++)
{
await _blogRepository.InsertAsync(
new Blog(Guid.NewGuid())
{
Name = "Blog" + i,
BlogPosts =
[
new BlogPost(Guid.NewGuid())
{
Title = "Post" + i
}
]
});
}
});
stopWatch.Stop();
stopWatch.Elapsed.ShouldBeLessThan(batchUpdateTime);
stopWatch.Restart();
var blogs = await _blogRepository.GetListAsync(includeDetails: true);
blogs.Count.ShouldBe(5 * 1000);
blogs.SelectMany(x => x.BlogPosts).Count().ShouldBe(5 * 1000);
stopWatch.Stop();
stopWatch.Elapsed.ShouldBeLessThan(queryTime);
var blogId = blogs.First().Id;
stopWatch.Restart();
await WithUnitOfWorkAsync(async () =>
{
var blog = await _blogRepository.GetAsync(blogId);
blog.ShouldNotBeNull();
for (var i = 0; i < 5 * 1000; i++)
{
blog.BlogPosts.Add(
new BlogPost(Guid.NewGuid())
{
Title = "NewPost" + i
});
}
await _blogRepository.UpdateAsync(blog);
});
stopWatch.Stop();
stopWatch.Elapsed.ShouldBeLessThan(batchUpdateTime);
stopWatch.Restart();
var blog = await _blogRepository.GetAsync(blogId);
blog.BlogPosts.Count.ShouldBe(5 * 1000 + 1);
stopWatch.Stop();
stopWatch.Elapsed.ShouldBeLessThan(queryTime);
}
}

5
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

@ -53,6 +53,11 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
{
opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).ThenInclude(x => x.OneToOne).Include(p => p.OneToMany).ThenInclude(x => x.OneToMany).Include(p => p.ManyToMany);
});
options.Entity<Blog>(opt =>
{
opt.DefaultWithDetailsFunc = q => q.Include(p => p.BlogPosts);
});
});
context.Services.AddAbpDbContext<HostTestAppDbContext>(options =>

16
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -30,6 +30,9 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
public DbSet<AppEntityWithNavigationsForeign> AppEntityWithNavigationsForeign { get; set; }
public DbSet<Blog> Blogs { get; set; }
public DbSet<BlogPost> BlogPosts { get; set; }
public TestMigrationsDbContext(DbContextOptions<TestMigrationsDbContext> options)
: base(options)
{
@ -97,5 +100,18 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
{
b.ConfigureByConvention();
});
modelBuilder.Entity<Blog>(b =>
{
b.ConfigureByConvention();
b.HasMany(bp => bp.BlogPosts)
.WithOne(bp => bp.Blog)
.HasForeignKey(bp => bp.BlogId);
});
modelBuilder.Entity<BlogPost>(b =>
{
b.ConfigureByConvention();
});
}
}

31
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/BlogPost.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
namespace Volo.Abp.TestApp.Domain;
public class Blog : FullAuditedAggregateRoot<Guid>
{
public Blog(Guid id)
: base(id)
{
}
public string Name { get; set; }
public List<BlogPost> BlogPosts { get; set; }
}
public class BlogPost : Entity<Guid>
{
public BlogPost(Guid id)
: base(id)
{
}
public Guid BlogId { get; set; }
public Blog Blog { get; set; }
public string Title { get; set; }
}

17
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -1,6 +1,7 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
@ -36,6 +37,9 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
public DbSet<AppEntityWithNavigationsForeign> AppEntityWithNavigationsForeign { get; set; }
public DbSet<Blog> Blogs { get; set; }
public DbSet<BlogPost> BlogPosts { get; set; }
public TestAppDbContext(DbContextOptions<TestAppDbContext> options)
: base(options)
{
@ -123,6 +127,19 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.ConfigureByConvention();
});
modelBuilder.Entity<Blog>(b =>
{
b.ConfigureByConvention();
b.HasMany(bp => bp.BlogPosts)
.WithOne(bp => bp.Blog)
.HasForeignKey(bp => bp.BlogId);
});
modelBuilder.Entity<BlogPost>(b =>
{
b.ConfigureByConvention();
});
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
}

9
latest-versions.json

@ -1,4 +1,13 @@
[
{
"version": "9.2.2",
"releaseDate": "",
"type": "stable",
"message": "",
"leptonx": {
"version": "4.2.2"
}
},
{
"version": "9.2.1",
"releaseDate": "",

4
modules/README.md

@ -1,3 +1,5 @@
## ABP Free Modules
These modules are free & open source.
These modules are free & open source.
For detailed information about module architecture and installer projects, see the [Module Installer Projects documentation](../docs/en/framework/architecture/modularity/installer-projects.md).

6
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogFeature.cs

@ -7,11 +7,11 @@ namespace Volo.CmsKit.Blogs;
public class BlogFeature : FullAuditedAggregateRoot<Guid>
{
public Guid BlogId { get; protected set; }
public virtual Guid BlogId { get; protected set; }
public string FeatureName { get; protected set; }
public virtual string FeatureName { get; protected set; }
public bool IsEnabled { get; protected internal set; }
public virtual bool IsEnabled { get; protected internal set; }
protected BlogFeature()
{

4
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/UserMarkedItem.cs

@ -15,8 +15,8 @@ public class UserMarkedItem : BasicAggregateRoot<Guid>, IHasCreationTime, IMustH
public virtual DateTime CreationTime { get; set; }
public string EntityId { get; protected set; }
public string EntityType { get; protected set; }
public virtual string EntityId { get; protected set; }
public virtual string EntityType { get; protected set; }
protected UserMarkedItem() { }

10
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MediaDescriptors/MediaDescriptor.cs

@ -7,15 +7,15 @@ namespace Volo.CmsKit.MediaDescriptors;
public class MediaDescriptor : FullAuditedAggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; protected set; }
public virtual Guid? TenantId { get; protected set; }
public string EntityType { get; protected set; }
public virtual string EntityType { get; protected set; }
public string Name { get; protected set; }
public virtual string Name { get; protected set; }
public string MimeType { get; protected set; }
public virtual string MimeType { get; protected set; }
public long Size { get; protected set; }
public virtual long Size { get; protected set; }
protected MediaDescriptor()
{

26
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs

@ -17,31 +17,31 @@ public class MenuItem : AuditedAggregateRoot<Guid>, IMultiTenant
/// Presents another <see cref="MenuItem"/> Id.
/// If it's <see langword="null"/>, then it's a root menu item.
/// </summary>
public Guid? ParentId { get; set; }
public virtual Guid? ParentId { get; set; }
[NotNull]
public string DisplayName { get; protected set; }
public virtual string DisplayName { get; protected set; }
public bool IsActive { get; set; }
public virtual bool IsActive { get; set; }
[NotNull]
public string Url { get; protected set; }
public virtual string Url { get; protected set; }
public string Icon { get; set; }
public virtual string Icon { get; set; }
public int Order { get; set; }
public virtual int Order { get; set; }
public string Target { get; set; }
public virtual string Target { get; set; }
public string ElementId { get; set; }
public virtual string ElementId { get; set; }
public string CssClass { get; set; }
public virtual string CssClass { get; set; }
public Guid? PageId { get; protected set; }
public virtual Guid? PageId { get; protected set; }
public Guid? TenantId { get; protected set; }
public string RequiredPermissionName { get; set; }
public virtual Guid? TenantId { get; protected set; }
public virtual string RequiredPermissionName { get; set; }
protected MenuItem()
{

10
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/DocumentContributor.cs

@ -5,15 +5,15 @@ namespace Volo.Docs.Documents
{
public class DocumentContributor : Entity
{
public Guid DocumentId { get; set; }
public virtual Guid DocumentId { get; set; }
public string Username { get; set; }
public virtual string Username { get; set; }
public int CommitCount { get; set; }
public virtual int CommitCount { get; set; }
public string UserProfileUrl { get; set; }
public virtual string UserProfileUrl { get; set; }
public string AvatarUrl { get; set; }
public virtual string AvatarUrl { get; set; }
protected DocumentContributor()
{

20
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionRecord.cs

@ -7,31 +7,31 @@ namespace Volo.Abp.FeatureManagement;
public class FeatureDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraProperties
{
public string GroupName { get; set; }
public virtual string GroupName { get; set; }
public string Name { get; set; }
public virtual string Name { get; set; }
public string ParentName { get; set; }
public virtual string ParentName { get; set; }
public string DisplayName { get; set; }
public virtual string DisplayName { get; set; }
public string Description { get; set; }
public virtual string Description { get; set; }
public string DefaultValue { get; set; }
public virtual string DefaultValue { get; set; }
public bool IsVisibleToClients { get; set; }
public virtual bool IsVisibleToClients { get; set; }
public bool IsAvailableToHost { get; set; }
public virtual bool IsAvailableToHost { get; set; }
/// <summary>
/// Comma separated list of provider names.
/// </summary>
public string AllowedProviders { get; set; }
public virtual string AllowedProviders { get; set; }
/// <summary>
/// Serialized string to store info about the ValueType.
/// </summary>
public string ValueType { get; set; } // ToggleStringValueType
public virtual string ValueType { get; set; } // ToggleStringValueType
public ExtraPropertyDictionary ExtraProperties { get; protected set; }

6
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecord.cs

@ -6,11 +6,11 @@ namespace Volo.Abp.FeatureManagement;
public class FeatureGroupDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraProperties
{
public string Name { get; set; }
public virtual string Name { get; set; }
public string DisplayName { get; set; }
public virtual string DisplayName { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; protected set; }
public virtual ExtraPropertyDictionary ExtraProperties { get; protected set; }
public FeatureGroupDefinitionRecord()
{

24
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySecurityLog.cs

@ -8,29 +8,29 @@ namespace Volo.Abp.Identity;
public class IdentitySecurityLog : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; protected set; }
public virtual Guid? TenantId { get; protected set; }
public string ApplicationName { get; protected set; }
public virtual string ApplicationName { get; protected set; }
public string Identity { get; protected set; }
public virtual string Identity { get; protected set; }
public string Action { get; protected set; }
public virtual string Action { get; protected set; }
public Guid? UserId { get; protected set; }
public virtual Guid? UserId { get; protected set; }
public string UserName { get; protected set; }
public virtual string UserName { get; protected set; }
public string TenantName { get; protected set; }
public virtual string TenantName { get; protected set; }
public string ClientId { get; protected set; }
public virtual string ClientId { get; protected set; }
public string CorrelationId { get; protected set; }
public virtual string CorrelationId { get; protected set; }
public string ClientIpAddress { get; protected set; }
public virtual string ClientIpAddress { get; protected set; }
public string BrowserInfo { get; protected set; }
public virtual string BrowserInfo { get; protected set; }
public DateTime CreationTime { get; protected set; }
public virtual DateTime CreationTime { get; protected set; }
protected IdentitySecurityLog()
{

35
modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs

@ -75,6 +75,41 @@ Console.WriteLine("UserInfo: {0}", JsonSerializer.Serialize(JsonDocument.Parse(u
}));
Console.WriteLine();
var tokenExchangeResponse = await client.RequestTokenExchangeTokenAsync(new TokenExchangeTokenRequest()
{
Address = configuration.TokenEndpoint,
ClientId = clientId,
ClientSecret = clientSecret,
SubjectToken = refreshTokenResponse.AccessToken!,
SubjectTokenType = "urn:ietf:params:oauth:token-type:access_token",
Scope = "AbpAPI profile roles email phone offline_access",
});
if (tokenExchangeResponse.IsError)
{
throw new Exception(tokenExchangeResponse.Error);
}
Console.WriteLine("Token Exchange token: {0}", tokenExchangeResponse.AccessToken);
Console.WriteLine();
Console.WriteLine("Token Exchange token: {0}", tokenExchangeResponse.RefreshToken);
Console.WriteLine();
userinfo = await client.GetUserInfoAsync(new UserInfoRequest()
{
Address = configuration.UserInfoEndpoint,
Token = tokenExchangeResponse.AccessToken
});
if (userinfo.IsError)
{
throw new Exception(userinfo.Error);
}
Console.WriteLine("Token Exchange UserInfo: {0}", JsonSerializer.Serialize(JsonDocument.Parse(userinfo.Raw), new JsonSerializerOptions
{
WriteIndented = true
}));
Console.WriteLine();
var introspectionResponse = await client.IntrospectTokenAsync(new TokenIntrospectionRequest()
{

1
modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs

@ -79,6 +79,7 @@ public class ServerDataSeedContributor : IDataSeedContributor, ITransientDepende
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
OpenIddictConstants.Permissions.GrantTypes.DeviceCode,
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
OpenIddictConstants.Permissions.GrantTypes.TokenExchange,
OpenIddictConstants.Permissions.Prefixes.GrantType + MyTokenExtensionGrant.ExtensionGrantName,
OpenIddictConstants.Permissions.ResponseTypes.Code,

6
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250630055813_Initial.Designer.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace OpenIddict.Demo.Server.Migrations
{
[DbContext(typeof(ServerDbContext))]
[Migration("20250630055813_Initial")]
[Migration("20250710090114_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -1164,8 +1164,8 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(400)");
b.Property<string>("Type")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.HasKey("Id");

2
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250630055813_Initial.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.cs

@ -645,7 +645,7 @@ namespace OpenIddict.Demo.Server.Migrations
ReferenceId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Subject = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: true),
Type = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Type = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: false)
},

4
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs

@ -1161,8 +1161,8 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(400)");
b.Property<string>("Type")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.HasKey("Id");

3
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs

@ -79,7 +79,8 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule
.AllowClientCredentialsFlow()
.AllowRefreshTokenFlow()
.AllowDeviceAuthorizationFlow()
.AllowNoneFlow();
.AllowNoneFlow()
.AllowTokenExchangeFlow();
builder.RegisterScopes(new[]
{

8
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictErrors.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.OpenIddict;
public static class AbpOpenIddictErrors
{
public const string AccountLocked = "account_locked";
public const string AccountInactive = "account_inactive";
}

2
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs

@ -44,7 +44,7 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (result is not { Succeeded: true } ||
((request.HasPromptValue(OpenIddictConstants.PromptValues.Login) || request.MaxAge is 0 ||
(request.MaxAge != null && result.Properties?.IssuedUtc != null &&
(request.MaxAge is not null && result.Properties?.IssuedUtc is not null &&
TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) &&
TempData["IgnoreAuthenticationChallenge"] is null or false))
{

8
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs

@ -107,10 +107,13 @@ public partial class TokenController
ClientId = request.ClientId
});
var errorCode = OpenIddictConstants.Errors.InvalidGrant;
string errorDescription;
if (result.IsLockedOut)
{
Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", request.Username);
errorCode = AbpOpenIddictErrors.AccountLocked;
errorDescription = "The user account has been locked out due to invalid login attempts. Please wait a while and try again.";
}
else if (result.IsNotAllowed)
@ -139,7 +142,8 @@ public partial class TokenController
return await HandleConfirmUserAsync(request, user);
}
errorDescription = "You are not allowed to login! Your account is inactive.";
errorCode = AbpOpenIddictErrors.AccountInactive;
errorDescription = "You are not allowed to login! Your account is inactive or needs to confirm your email/phone number.";
}
}
else
@ -150,7 +154,7 @@ public partial class TokenController
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.Error] = errorCode,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = errorDescription
});

92
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
namespace Volo.Abp.OpenIddict.Controllers;
public partial class TokenController
{
protected virtual async Task<IActionResult> HandleTokenExchangeGrantTypeAsync(OpenIddictRequest request)
{
// Retrieve the claims principal stored in the subject token.
//
// Note: the principal may not represent a user (e.g if the token was issued during a client credentials token
// request and represents a client application): developers are strongly encouraged to ensure that the user
// and client identifiers are randomly generated so that a malicious client cannot impersonate a legit user.
//
// See https://datatracker.ietf.org/doc/html/rfc9068#SecurityConsiderations for more information.
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// If available, retrieve the claims principal stored in the actor token.
var actor = result.Properties?.GetParameter<ClaimsPrincipal>(OpenIddictServerAspNetCoreConstants.Properties.ActorTokenPrincipal);
// Retrieve the user profile corresponding to the subject token.
var user = await UserManager.FindByIdAsync(result.Principal!.GetClaim(OpenIddictConstants.Claims.Subject)!);
if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await PreSignInCheckAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
// Note: whether the identity represents a delegated or impersonated access (or any other
// model) is entirely up to the implementer: to support all scenarios, OpenIddict doesn't
// enforce any specific constraint on the identity used for the sign-in operation and only
// requires that the standard "act" and "may_act" claims be valid JSON objects if present.
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await SignInManager.CreateUserPrincipalAsync(user);
// Note: IdentityModel doesn't support serializing ClaimsIdentity.Actor to the
// standard "act" claim yet, which requires adding the "act" claim manually.
//
// For more information, see
// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3219.
if (!string.IsNullOrEmpty(actor?.GetClaim(OpenIddictConstants.Claims.Subject)) &&
!string.Equals(principal.GetClaim(OpenIddictConstants.Claims.Subject), actor.GetClaim(OpenIddictConstants.Claims.Subject), StringComparison.Ordinal))
{
principal.SetClaim(OpenIddictConstants.Claims.Actor, new JsonObject
{
[OpenIddictConstants.Claims.Subject] = actor.GetClaim(OpenIddictConstants.Claims.Subject)
});
}
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
principal.SetScopes(request.GetScopes());
principal.SetResources(await GetResourcesAsync(request.GetScopes()));
await OpenIddictClaimsPrincipalManager.HandleAsync(request, principal);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
}

5
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs

@ -42,6 +42,11 @@ public partial class TokenController : AbpOpenIdDictControllerBase
return await HandleClientCredentialsAsync(request);
}
if (request.IsTokenExchangeGrantType())
{
return await HandleTokenExchangeGrantTypeAsync(request);
}
var extensionGrantsOptions = HttpContext.RequestServices.GetRequiredService<IOptions<AbpOpenIddictExtensionGrantsOptions>>();
var extensionTokenGrant = extensionGrantsOptions.Value.Find<ITokenExtensionGrant>(request.GrantType);
if (extensionTokenGrant != null)

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Tokens/OpenIddictTokenConsts.cs

@ -8,5 +8,5 @@ public class OpenIddictTokenConsts
public static int SubjectMaxLength { get; set; } = 400;
public static int TypeMaxLength { get; set; } = 50;
public static int TypeMaxLength { get; set; } = 150;
}

19
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictDomainModule.cs

@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Caching;
using Volo.Abp.DistributedLocking;
@ -65,15 +66,15 @@ public class AbpOpenIddictDomainModule : AbpModule
.SetDefaultTokenEntity<OpenIddictTokenModel>();
builder
.AddApplicationStore<AbpOpenIddictApplicationStore>()
.AddAuthorizationStore<AbpOpenIddictAuthorizationStore>()
.AddScopeStore<AbpOpenIddictScopeStore>()
.AddTokenStore<AbpOpenIddictTokenStore>();
builder.ReplaceApplicationManager(typeof(AbpApplicationManager));
builder.ReplaceAuthorizationManager(typeof(AbpAuthorizationManager));
builder.ReplaceScopeManager(typeof(AbpScopeManager));
builder.ReplaceTokenManager(typeof(AbpTokenManager));
.ReplaceApplicationStore<OpenIddictApplicationModel, AbpOpenIddictApplicationStore>()
.ReplaceAuthorizationStore<OpenIddictAuthorizationModel, AbpOpenIddictAuthorizationStore>()
.ReplaceScopeStore<OpenIddictScopeModel, AbpOpenIddictScopeStore>()
.ReplaceTokenStore<OpenIddictTokenModel, AbpOpenIddictTokenStore>();
builder.ReplaceApplicationManager<OpenIddictApplicationModel, AbpApplicationManager>();
builder.ReplaceAuthorizationManager<OpenIddictAuthorizationModel, AbpAuthorizationManager>();
builder.ReplaceScopeManager<OpenIddictScopeModel, AbpScopeManager>();
builder.ReplaceTokenManager<OpenIddictTokenModel, AbpTokenManager>();
builder.Services.TryAddScoped(provider => (IAbpApplicationManager)provider.GetRequiredService<IOpenIddictApplicationManager>());

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs

@ -17,7 +17,7 @@ public class AbpApplicationManager : OpenIddictApplicationManager<OpenIddictAppl
[NotNull] IOpenIddictApplicationCache<OpenIddictApplicationModel> cache,
[NotNull] ILogger<AbpApplicationManager> logger,
[NotNull] IOptionsMonitor<OpenIddictCoreOptions> options,
[NotNull] IOpenIddictApplicationStoreResolver resolver,
[NotNull] IOpenIddictApplicationStore<OpenIddictApplicationModel> resolver,
AbpOpenIddictIdentifierConverter identifierConverter)
: base(cache, logger, options, resolver)
{

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/AbpAuthorizationManager.cs

@ -16,7 +16,7 @@ public class AbpAuthorizationManager : OpenIddictAuthorizationManager<OpenIddict
[NotNull] [ItemNotNull] IOpenIddictAuthorizationCache<OpenIddictAuthorizationModel> cache,
[NotNull] [ItemNotNull] ILogger<OpenIddictAuthorizationManager<OpenIddictAuthorizationModel>> logger,
[NotNull] [ItemNotNull] IOptionsMonitor<OpenIddictCoreOptions> options,
[NotNull] IOpenIddictAuthorizationStoreResolver resolver,
[NotNull] IOpenIddictAuthorizationStore<OpenIddictAuthorizationModel> resolver,
AbpOpenIddictIdentifierConverter identifierConverter)
: base(cache, logger, options, resolver)
{

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Scopes/AbpScopeManager.cs

@ -16,7 +16,7 @@ public class AbpScopeManager : OpenIddictScopeManager<OpenIddictScopeModel>
[NotNull] [ItemNotNull] IOpenIddictScopeCache<OpenIddictScopeModel> cache,
[NotNull] [ItemNotNull] ILogger<OpenIddictScopeManager<OpenIddictScopeModel>> logger,
[NotNull] [ItemNotNull] IOptionsMonitor<OpenIddictCoreOptions> options,
[NotNull] IOpenIddictScopeStoreResolver resolver,
[NotNull] IOpenIddictScopeStore<OpenIddictScopeModel> resolver,
AbpOpenIddictIdentifierConverter identifierConverter)
: base(cache, logger, options, resolver)
{

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/AbpTokenManager.cs

@ -16,7 +16,7 @@ public class AbpTokenManager : OpenIddictTokenManager<OpenIddictTokenModel>
[NotNull] [ItemNotNull] IOpenIddictTokenCache<OpenIddictTokenModel> cache,
[NotNull] [ItemNotNull] ILogger<OpenIddictTokenManager<OpenIddictTokenModel>> logger,
[NotNull] [ItemNotNull] IOptionsMonitor<OpenIddictCoreOptions> options,
[NotNull] IOpenIddictTokenStoreResolver resolver,
[NotNull] IOpenIddictTokenStore<OpenIddictTokenModel> resolver,
AbpOpenIddictIdentifierConverter identifierConverter)
: base(cache, logger, options, resolver)
{

6
npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/account.service.ts

@ -1,12 +1,14 @@
import type { RegisterDto, ResetPasswordDto, SendPasswordResetCodeDto } from './models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import type { IdentityUserDto } from '../identity/models';
@Injectable({
providedIn: 'root',
})
export class AccountService {
private restService = inject(RestService);
apiName = 'AbpAccount';
register = (input: RegisterDto) =>
@ -32,6 +34,4 @@ export class AccountService {
body: input,
},
{ apiName: this.apiName });
constructor(private restService: RestService) {}
}

6
npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/profile.service.ts

@ -1,11 +1,13 @@
import type { ChangePasswordInput, ProfileDto, UpdateProfileDto } from './models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ProfileService {
private restService = inject(RestService);
apiName = 'AbpAccount';
changePassword = (input: ChangePasswordInput) =>
@ -30,6 +32,4 @@ export class ProfileService {
body: input,
},
{ apiName: this.apiName });
constructor(private restService: RestService) {}
}

6
npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/web/areas/account/controllers/account.service.ts

@ -1,11 +1,13 @@
import type { AbpLoginResult, UserLoginInfo } from './models/models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AccountService {
private restService = inject(RestService);
apiName = 'AbpAccount';
checkPasswordByLogin = (login: UserLoginInfo) =>
@ -30,6 +32,4 @@ export class AccountService {
url: '/api/account/logout',
},
{ apiName: this.apiName });
constructor(private restService: RestService) {}
}

13
npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts

@ -1,4 +1,4 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
@ -6,6 +6,9 @@ import { ConfigStateService, MultiTenancyService } from '@abp/ng.core';
@Injectable()
export class AuthWrapperService {
readonly multiTenancy = inject(MultiTenancyService);
private configState = inject(ConfigStateService);
isMultiTenancyEnabled$ = this.configState.getDeep$('multiTenancy.isEnabled');
get enableLocalLogin$(): Observable<boolean> {
@ -25,11 +28,9 @@ export class AuthWrapperService {
return this.isTenantBoxVisibleForCurrentRoute && this.multiTenancy.isTenantBoxVisible;
}
constructor(
public readonly multiTenancy: MultiTenancyService,
private configState: ConfigStateService,
injector: Injector,
) {
constructor() {
const injector = inject(Injector);
this.route = injector.get(ActivatedRoute);
}

14
npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts

@ -5,11 +5,16 @@ import {
SessionStateService,
} from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { finalize } from 'rxjs/operators';
@Injectable()
export class TenantBoxService {
private toasterService = inject(ToasterService);
private tenantService = inject(AbpTenantService);
private sessionState = inject(SessionStateService);
private configState = inject(ConfigStateService);
currentTenant$ = this.sessionState.getTenant$();
name?: string;
@ -18,13 +23,6 @@ export class TenantBoxService {
modalBusy!: boolean;
constructor(
private toasterService: ToasterService,
private tenantService: AbpTenantService,
private sessionState: SessionStateService,
private configState: ConfigStateService,
) {}
onSwitch() {
const tenant = this.sessionState.getTenant();
this.name = tenant?.name || '';

16
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts

@ -1,6 +1,6 @@
import { ProfileService } from '@abp/ng.account.core/proxy';
import { ButtonComponent, getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { Component, Injector, OnInit, inject } from '@angular/core';
import {
ReactiveFormsModule,
UntypedFormBuilder,
@ -32,6 +32,12 @@ const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
export class ChangePasswordComponent
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs
{
private fb = inject(UntypedFormBuilder);
private injector = inject(Injector);
private toasterService = inject(ToasterService);
private profileService = inject(ProfileService);
private manageProfileState = inject(ManageProfileStateService);
form!: UntypedFormGroup;
inProgress?: boolean;
@ -44,14 +50,6 @@ export class ChangePasswordComponent
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
constructor(
private fb: UntypedFormBuilder,
private injector: Injector,
private toasterService: ToasterService,
private profileService: ProfileService,
private manageProfileState: ManageProfileStateService,
) {}
ngOnInit(): void {
this.hideCurrentPassword = !this.manageProfileState.getProfile()?.hasPassword;

10
npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts

@ -1,5 +1,5 @@
import { AccountService } from '@abp/ng.account.core/proxy';
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
ReactiveFormsModule,
UntypedFormBuilder,
@ -24,16 +24,16 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
],
})
export class ForgotPasswordComponent {
private fb = inject(UntypedFormBuilder);
private accountService = inject(AccountService);
form: UntypedFormGroup;
inProgress?: boolean;
isEmailSent = false;
constructor(
private fb: UntypedFormBuilder,
private accountService: AccountService,
) {
constructor() {
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
});

11
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts

@ -33,18 +33,23 @@ import { ChangePasswordComponent } from '../change-password/change-password.comp
],
})
export class ManageProfileComponent implements OnInit {
protected profileService = inject(ProfileService);
protected manageProfileState = inject(ManageProfileStateService);
protected readonly ssrService = inject(SSRService);
selectedTab = 0;
changePasswordKey = eAccountComponents.ChangePassword;
personalSettingsKey = eAccountComponents.PersonalSettings;
profile$ = this.manageProfileState.getProfile$();
hideChangePasswordTab?: boolean;
PROFILE_SETTINGS_KEY = makeStateKey<any>('profileSettings');
constructor(
private transferState: TransferState,
protected profileService: ProfileService,
protected manageProfileState: ManageProfileStateService,
private transferState: TransferState
) {}
ngOnInit() {

8
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts

@ -1,4 +1,4 @@
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
EXTENSIONS_FORM_PROP,
FormProp,
@ -24,12 +24,16 @@ import { LocalizationPipe } from '@abp/ng.core';
imports: [ReactiveFormsModule, LocalizationPipe],
})
export class PersonalSettingsHalfRowComponent {
private propData = inject<FormProp>(EXTENSIONS_FORM_PROP);
public displayName: string;
public name: string;
public id: string;
public formGroup!: UntypedFormGroup;
constructor(@Inject(EXTENSIONS_FORM_PROP) private propData: FormProp) {
constructor() {
const propData = this.propData;
this.displayName = propData.displayName;
this.name = propData.name;
this.id = propData.id || '';

18
npm/ng-packs/packages/account/src/lib/components/register/register.component.ts

@ -6,7 +6,7 @@ import {
LocalizationPipe,
} from '@abp/ng.core';
import { ButtonComponent, getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { Component, Injector, OnInit, inject } from '@angular/core';
import {
ReactiveFormsModule,
UntypedFormBuilder,
@ -35,6 +35,13 @@ const { maxLength, required, email } = Validators;
],
})
export class RegisterComponent implements OnInit {
protected fb = inject(UntypedFormBuilder);
protected accountService = inject(AccountService);
protected configState = inject(ConfigStateService);
protected toasterService = inject(ToasterService);
protected authService = inject(AuthService);
protected injector = inject(Injector);
form!: UntypedFormGroup;
inProgress?: boolean;
@ -43,15 +50,6 @@ export class RegisterComponent implements OnInit {
authWrapperKey = eAccountComponents.AuthWrapper;
constructor(
protected fb: UntypedFormBuilder,
protected accountService: AccountService,
protected configState: ConfigStateService,
protected toasterService: ToasterService,
protected authService: AuthService,
protected injector: Injector,
) {}
ngOnInit() {
this.init();
this.buildForm();

16
npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts

@ -1,6 +1,6 @@
import { AccountService } from '@abp/ng.account.core/proxy';
import { ButtonComponent, getPasswordValidators } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { Component, Injector, OnInit, inject } from '@angular/core';
import {
ReactiveFormsModule,
UntypedFormBuilder,
@ -26,6 +26,12 @@ const PASSWORD_FIELDS = ['password', 'confirmPassword'];
],
})
export class ResetPasswordComponent implements OnInit {
private fb = inject(UntypedFormBuilder);
private accountService = inject(AccountService);
private route = inject(ActivatedRoute);
private router = inject(Router);
private injector = inject(Injector);
form!: UntypedFormGroup;
inProgress = false;
@ -38,14 +44,6 @@ export class ResetPasswordComponent implements OnInit {
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
constructor(
private fb: UntypedFormBuilder,
private accountService: AccountService,
private route: ActivatedRoute,
private router: Router,
private injector: Injector,
) {}
ngOnInit(): void {
this.route.queryParams.subscribe(({ userId, resetToken }) => {
if (!userId || !resetToken) this.router.navigateByUrl('/account/login');

35
npm/ng-packs/packages/components/chart.js/src/chart.component.ts

@ -1,16 +1,17 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
SimpleChanges,
ViewChild,
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
SimpleChanges,
ViewChild,
inject
} from '@angular/core';
let Chart: any;
@ -35,6 +36,9 @@ let Chart: any;
exportAs: 'abpChart',
})
export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
el = inject(ElementRef);
private cdr = inject(ChangeDetectorRef);
@Input() type!: string;
@Input() data: any = {};
@ -57,11 +61,6 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
chart: any;
constructor(
public el: ElementRef,
private cdr: ChangeDetectorRef,
) {}
ngAfterViewInit() {
import('chart.js/auto').then(module => {
Chart = module.default;

6
npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts

@ -1,4 +1,4 @@
import { Directive, Injector, Input } from '@angular/core';
import { Directive, Injector, Input, inject } from '@angular/core';
import { ActionData, ActionList, InferredAction } from '../../models/actions';
import { ExtensionsService } from '../../services/extensions.service';
import { EXTENSIONS_ACTION_TYPE, EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token';
@ -16,7 +16,9 @@ export abstract class AbstractActionsComponent<
@Input() record!: InferredData<L>['record'];
protected constructor(injector: Injector) {
protected constructor() {
const injector = inject(Injector);
super();
this.getInjected = injector.get.bind(injector);
const extensions = injector.get(ExtensionsService);

15
npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts

@ -1,9 +1,8 @@
import {
ChangeDetectionStrategy,
Component,
Injector,
Input,
TrackByFunction,
import {
ChangeDetectionStrategy,
Component,
Input,
TrackByFunction,
} from '@angular/core';
import { EntityAction, EntityActionList } from '../../models/entity-actions';
import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token';
@ -43,7 +42,7 @@ export class GridActionsComponent<R = any> extends AbstractActionsComponent<Enti
readonly trackByFn: TrackByFunction<EntityAction<R>> = (_, item) => item.text;
constructor(injector: Injector) {
super(injector);
constructor() {
super();
}
}

12
npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Injector, TrackByFunction } from '@angular/core';
import { ChangeDetectionStrategy, Component, Injector, TrackByFunction, inject } from '@angular/core';
import {
HasCreateInjectorPipe,
ToolbarAction,
@ -35,6 +35,8 @@ export class PageToolbarComponent<R = any>
extends AbstractActionsComponent<ToolbarActionList<R>>
implements HasCreateInjectorPipe<R>
{
readonly injector: Injector;
defaultBtnClass = 'btn btn-sm btn-primary';
getData = () => this.data;
@ -42,8 +44,12 @@ export class PageToolbarComponent<R = any>
readonly trackByFn: TrackByFunction<ToolbarComponent<R>> = (_, item) =>
item.action || item.component;
constructor(public readonly injector: Injector) {
super(injector);
constructor() {
const injector = inject(Injector);
super();
this.injector = injector;
}
asToolbarAction(value: ToolbarActionDefault): { value: ToolbarAction } {

28
npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts

@ -1,12 +1,13 @@
/* eslint-disable @angular-eslint/no-input-rename */
import {
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
import {
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject
} from '@angular/core';
import { PropData, PropList } from '../models/props';
@ -18,6 +19,9 @@ export class PropDataDirective<L extends PropList<any>>
extends PropData<InferredData<L>>
implements OnChanges, OnDestroy
{
private tempRef = inject<TemplateRef<any>>(TemplateRef);
private vcRef = inject(ViewContainerRef);
@Input('abpPropDataFromList') propList?: L;
@Input('abpPropDataWithRecord') record!: InferredData<L>['record'];
@ -26,11 +30,9 @@ export class PropDataDirective<L extends PropList<any>>
readonly getInjected: InferredData<L>['getInjected'];
constructor(
private tempRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
injector: Injector,
) {
constructor() {
const injector = inject(Injector);
super();
this.getInjected = injector.get.bind(injector);

41
npm/ng-packs/packages/components/page/src/page-part.directive.ts

@ -1,18 +1,17 @@
import {
Directive,
TemplateRef,
ViewContainerRef,
Input,
InjectionToken,
Optional,
Inject,
OnInit,
OnDestroy,
Injector,
OnChanges,
SimpleChanges,
SimpleChange,
} from '@angular/core';
import {
Directive,
TemplateRef,
ViewContainerRef,
Input,
InjectionToken,
OnInit,
OnDestroy,
Injector,
OnChanges,
SimpleChanges,
SimpleChange,
inject
} from '@angular/core';
import { Observable, Subscription, of } from 'rxjs';
export interface PageRenderStrategy {
@ -28,6 +27,11 @@ export const PAGE_RENDER_STRATEGY = new InjectionToken<PageRenderStrategy>('PAGE
selector: '[abpPagePart]',
})
export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
private templateRef = inject<TemplateRef<any>>(TemplateRef);
private viewContainer = inject(ViewContainerRef);
private renderLogic = inject<PageRenderStrategy>(PAGE_RENDER_STRATEGY, { optional: true })!;
private injector = inject(Injector);
hasRendered = false;
type!: string;
subscription!: Subscription;
@ -48,13 +52,6 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
}
};
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
@Optional() @Inject(PAGE_RENDER_STRATEGY) private renderLogic: PageRenderStrategy,
private injector: Injector,
) {}
ngOnChanges({ context }: SimpleChanges): void {
if (this.renderLogic?.onContextUpdate) {
this.renderLogic.onContextUpdate(context);

14
npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts

@ -1,8 +1,8 @@
import { Directive, TemplateRef } from '@angular/core';
import { Directive, TemplateRef, inject } from '@angular/core';
@Directive({
selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]',
})
export class ExpandedIconTemplateDirective { template = inject<TemplateRef<any>>(TemplateRef);
@Directive({
selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]',
})
export class ExpandedIconTemplateDirective {
constructor(public template: TemplateRef<any>) {}
}
}

14
npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts

@ -1,8 +1,8 @@
import { Directive, TemplateRef } from '@angular/core';
import { Directive, TemplateRef, inject } from '@angular/core';
@Directive({
selector: '[abpTreeNodeTemplate],[abp-tree-node-template]',
})
export class TreeNodeTemplateDirective { template = inject<TemplateRef<any>>(TemplateRef);
@Directive({
selector: '[abpTreeNodeTemplate],[abp-tree-node-template]',
})
export class TreeNodeTemplateDirective {
constructor(public template: TemplateRef<any>) {}
}
}

6
npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts

@ -1,4 +1,4 @@
import { Component, inject, isDevMode, OnInit, Optional, SkipSelf, Type } from '@angular/core';
import { Component, inject, isDevMode, OnInit, Type } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
@ -39,7 +39,9 @@ export class DynamicLayoutComponent implements OnInit {
protected readonly routerEvents = inject(RouterEvents);
protected readonly environment = inject(EnvironmentService);
constructor(@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent) {
constructor() {
const dynamicLayoutComponent = inject(DynamicLayoutComponent, { optional: true, skipSelf: true })!;
if (dynamicLayoutComponent) {
if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.');
return;

12
npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts

@ -1,4 +1,4 @@
import { Component, OnInit, Type } from '@angular/core';
import { Component, OnInit, Type, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { distinctUntilChanged } from 'rxjs/operators';
import { ReplaceableComponents } from '../models/replaceable-components';
@ -15,18 +15,16 @@ import { CommonModule } from '@angular/common';
imports: [CommonModule],
})
export class ReplaceableRouteContainerComponent implements OnInit {
private route = inject(ActivatedRoute);
private replaceableComponents = inject(ReplaceableComponentsService);
private subscription = inject(SubscriptionService);
defaultComponent!: Type<any>;
componentKey!: string;
externalComponent?: Type<any>;
constructor(
private route: ActivatedRoute,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
) {}
ngOnInit() {
this.defaultComponent = this.route.snapshot.data.replaceableComponent.defaultComponent;
this.componentKey = (

6
npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts

@ -1,9 +1,11 @@
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, inject } from '@angular/core';
@Directive({
selector: '[autofocus]',
})
export class AutofocusDirective implements AfterViewInit {
private elRef = inject(ElementRef);
private _delay = 0;
@Input('autofocus')
@ -15,8 +17,6 @@ export class AutofocusDirective implements AfterViewInit {
return this._delay;
}
constructor(private elRef: ElementRef) {}
ngAfterViewInit(): void {
setTimeout(() => this.elRef.nativeElement.focus(), this.delay as number);
}

10
npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts

@ -1,4 +1,4 @@
import { Directive, ElementRef, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Directive, ElementRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { SubscriptionService } from '../services/subscription.service';
@ -8,15 +8,13 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class InputEventDebounceDirective implements OnInit {
private el = inject(ElementRef);
private subscription = inject(SubscriptionService);
@Input() debounce = 300;
@Output('input.debounce') readonly debounceEvent = new EventEmitter<Event>();
constructor(
private el: ElementRef,
private subscription: SubscriptionService,
) {}
ngOnInit(): void {
const input$ = fromEvent<InputEvent>(this.el.nativeElement, 'input').pipe(
debounceTime(this.debounce),

35
npm/ng-packs/packages/core/src/lib/directives/for.directive.ts

@ -1,15 +1,16 @@
import {
Directive,
EmbeddedViewRef,
Input,
IterableChangeRecord,
IterableChanges,
IterableDiffer,
IterableDiffers,
OnChanges,
TemplateRef,
TrackByFunction,
ViewContainerRef,
import {
Directive,
EmbeddedViewRef,
Input,
IterableChangeRecord,
IterableChanges,
IterableDiffer,
IterableDiffers,
OnChanges,
TemplateRef,
TrackByFunction,
ViewContainerRef,
inject
} from '@angular/core';
import clone from 'just-clone';
import compare from 'just-compare';
@ -36,6 +37,10 @@ class RecordView {
selector: '[abpFor]',
})
export class ForDirective implements OnChanges {
private tempRef = inject<TemplateRef<AbpForContext>>(TemplateRef);
private vcRef = inject(ViewContainerRef);
private differs = inject(IterableDiffers);
// eslint-disable-next-line @angular-eslint/no-input-rename
@Input('abpForOf')
items!: any[];
@ -73,12 +78,6 @@ export class ForDirective implements OnChanges {
return this.trackBy || ((index: number, item: any) => (item as any).id || index);
}
constructor(
private tempRef: TemplateRef<AbpForContext>,
private vcRef: ViewContainerRef,
private differs: IterableDiffers,
) {}
private iterateOverAppliedOperations(changes: IterableChanges<any>) {
const rw: RecordView[] = [];

30
npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts

@ -1,12 +1,12 @@
import {
ChangeDetectorRef,
Directive,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
Self,
import {
ChangeDetectorRef,
Directive,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
inject
} from '@angular/core';
import { FormGroupDirective, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { fromEvent } from 'rxjs';
@ -22,6 +22,11 @@ type Controls = { [key: string]: UntypedFormControl } | UntypedFormGroup[];
providers: [SubscriptionService],
})
export class FormSubmitDirective implements OnInit {
private formGroupDirective = inject(FormGroupDirective, { self: true });
private host = inject<ElementRef<HTMLFormElement>>(ElementRef);
private cdRef = inject(ChangeDetectorRef);
private subscription = inject(SubscriptionService);
@Input()
debounce = 200;
@ -36,13 +41,6 @@ export class FormSubmitDirective implements OnInit {
executedNgSubmit = false;
constructor(
@Self() private formGroupDirective: FormGroupDirective,
private host: ElementRef<HTMLFormElement>,
private cdRef: ChangeDetectorRef,
private subscription: SubscriptionService,
) {}
ngOnInit() {
this.subscription.addOne(this.formGroupDirective.ngSubmit, () => {
if (this.markAsDirtyWhenSubmit) {

6
npm/ng-packs/packages/core/src/lib/directives/init.directive.ts

@ -1,12 +1,12 @@
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit } from '@angular/core';
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit, inject } from '@angular/core';
@Directive({
selector: '[abpInit]',
})
export class InitDirective implements AfterViewInit {
@Output('abpInit') readonly init = new EventEmitter<ElementRef<any>>();
private elRef = inject(ElementRef);
constructor(private elRef: ElementRef) {}
@Output('abpInit') readonly init = new EventEmitter<ElementRef<any>>();
ngAfterViewInit() {
this.init.emit(this.elRef);

35
npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts

@ -1,14 +1,13 @@
import {
AfterViewInit,
ChangeDetectorRef,
Directive,
Inject,
Input,
OnChanges,
OnDestroy,
Optional,
TemplateRef,
ViewContainerRef,
import {
AfterViewInit,
ChangeDetectorRef,
Directive,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject
} from '@angular/core';
import { ReplaySubject, Subscription } from 'rxjs';
import { distinctUntilChanged, take } from 'rxjs/operators';
@ -20,6 +19,12 @@ import { QueueManager } from '../utils/queue';
selector: '[abpPermission]',
})
export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit {
private templateRef = inject<TemplateRef<any>>(TemplateRef, { optional: true })!;
private vcRef = inject(ViewContainerRef);
private permissionService = inject(PermissionService);
private cdRef = inject(ChangeDetectorRef);
queue = inject<QueueManager>(QUEUE_MANAGER);
@Input('abpPermission') condition: string | undefined;
@Input('abpPermissionRunChangeDetection') runChangeDetection = true;
@ -30,14 +35,6 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit
rendered = false;
constructor(
@Optional() private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
private permissionService: PermissionService,
private cdRef: ChangeDetectorRef,
@Inject(QUEUE_MANAGER) public queue: QueueManager,
) {}
private check() {
if (this.subscription) {
this.subscription.unsubscribe();

35
npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts

@ -1,13 +1,14 @@
import {
Directive,
Injector,
Input,
OnChanges,
OnInit,
SimpleChanges,
TemplateRef,
Type,
ViewContainerRef,
import {
Directive,
Injector,
Input,
OnChanges,
OnInit,
SimpleChanges,
TemplateRef,
Type,
ViewContainerRef,
inject
} from '@angular/core';
import compare from 'just-compare';
import { Subscription } from 'rxjs';
@ -22,6 +23,12 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class ReplaceableTemplateDirective implements OnInit, OnChanges {
private injector = inject(Injector);
private templateRef = inject<TemplateRef<any>>(TemplateRef);
private vcRef = inject(ViewContainerRef);
private replaceableComponents = inject(ReplaceableComponentsService);
private subscription = inject(SubscriptionService);
@Input('abpReplaceableTemplate')
data!: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
@ -40,13 +47,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
initialized = false;
constructor(
private injector: Injector,
private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
) {
constructor() {
this.context = {
initTemplate: (ref: any) => {
this.resetDefaultComponent();

10
npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts

@ -1,4 +1,4 @@
import { Directive, ElementRef, EventEmitter, OnInit, Output } from '@angular/core';
import { Directive, ElementRef, EventEmitter, OnInit, Output, inject } from '@angular/core';
import { fromEvent } from 'rxjs';
import { SubscriptionService } from '../services/subscription.service';
@ -7,12 +7,10 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class StopPropagationDirective implements OnInit {
@Output('click.stop') readonly stopPropEvent = new EventEmitter<MouseEvent>();
private el = inject(ElementRef);
private subscription = inject(SubscriptionService);
constructor(
private el: ElementRef,
private subscription: SubscriptionService,
) {}
@Output('click.stop') readonly stopPropEvent = new EventEmitter<MouseEvent>();
ngOnInit(): void {
this.subscription.addOne(fromEvent<MouseEvent>(this.el.nativeElement, 'click'), event => {

10
npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts

@ -1,4 +1,4 @@
import { Injectable, Optional } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Route, Router } from '@angular/router';
import { ABP } from '../models';
import { RoutesService } from '../services/routes.service';
@ -7,10 +7,10 @@ import { RoutesService } from '../services/routes.service';
providedIn: 'root',
})
export class RoutesHandler {
constructor(
private routes: RoutesService,
@Optional() private router: Router,
) {
private routes = inject(RoutesService);
private router = inject(Router, { optional: true })!;
constructor() {
this.addRoutes();
}

5
npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts

@ -1,5 +1,5 @@
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { HttpWaitService } from '../services';
@ -7,7 +7,8 @@ import { HttpWaitService } from '../services';
providedIn: 'root',
})
export class ApiInterceptor implements IApiInterceptor {
constructor(private httpWaitService: HttpWaitService) {}
private httpWaitService = inject(HttpWaitService);
getAdditionalHeaders(existingHeaders?: HttpHeaders) {
return existingHeaders || new HttpHeaders();

5
npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts

@ -1,4 +1,4 @@
import { Injectable, Pipe, PipeTransform } from '@angular/core';
import { Injectable, Pipe, PipeTransform, inject } from '@angular/core';
import { LocalizationWithDefault } from '../models/localization';
import { LocalizationService } from '../services/localization.service';
@ -7,7 +7,8 @@ import { LocalizationService } from '../services/localization.service';
name: 'abpLocalization',
})
export class LocalizationPipe implements PipeTransform {
constructor(private localization: LocalizationService) {}
private localization = inject(LocalizationService);
transform(
value: string | LocalizationWithDefault = '',

13
npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts

@ -1,5 +1,5 @@
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common';
import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core';
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';
import { ConfigStateService } from '../services';
import { getShortDateShortTimeFormat } from '../utils/date-utils';
@ -8,11 +8,12 @@ import { getShortDateShortTimeFormat } from '../utils/date-utils';
pure: true,
})
export class ShortDateTimePipe extends DatePipe implements PipeTransform {
constructor(
private configStateService: ConfigStateService,
@Inject(LOCALE_ID) locale: string,
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null,
) {
private configStateService = inject(ConfigStateService);
constructor() {
const locale = inject(LOCALE_ID);
const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true });
super(locale, defaultTimezone);
}

13
npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts

@ -1,5 +1,5 @@
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common';
import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core';
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';
import { ConfigStateService } from '../services';
import { getShortDateFormat } from '../utils/date-utils';
@ -8,11 +8,12 @@ import { getShortDateFormat } from '../utils/date-utils';
pure: true,
})
export class ShortDatePipe extends DatePipe implements PipeTransform {
constructor(
private configStateService: ConfigStateService,
@Inject(LOCALE_ID) locale: string,
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null,
) {
private configStateService = inject(ConfigStateService);
constructor() {
const locale = inject(LOCALE_ID);
const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true });
super(locale, defaultTimezone);
}

13
npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts

@ -1,5 +1,5 @@
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common';
import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core';
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';
import { ConfigStateService } from '../services';
import { getShortTimeFormat } from '../utils/date-utils';
@ -8,11 +8,12 @@ import { getShortTimeFormat } from '../utils/date-utils';
pure: true,
})
export class ShortTimePipe extends DatePipe implements PipeTransform {
constructor(
private configStateService: ConfigStateService,
@Inject(LOCALE_ID) locale: string,
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null,
) {
private configStateService = inject(ConfigStateService);
constructor() {
const locale = inject(LOCALE_ID);
const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true });
super(locale, defaultTimezone);
}

5
npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts

@ -1,4 +1,4 @@
import { InjectionToken, Injector, Pipe, PipeTransform } from '@angular/core';
import { InjectionToken, Injector, Pipe, PipeTransform, inject } from '@angular/core';
export const INJECTOR_PIPE_DATA_TOKEN = new InjectionToken<PipeTransform>(
'INJECTOR_PIPE_DATA_TOKEN',
@ -8,7 +8,8 @@ export const INJECTOR_PIPE_DATA_TOKEN = new InjectionToken<PipeTransform>(
name: 'toInjector',
})
export class ToInjectorPipe implements PipeTransform {
constructor(private injector: Injector) {}
private injector = inject(Injector);
transform(
value: any,
token: InjectionToken<any> = INJECTOR_PIPE_DATA_TOKEN,

9
npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts

@ -1,10 +1,12 @@
import { LOCALE_ID, Provider } from '@angular/core';
import { LOCALE_ID, Provider, inject } from '@angular/core';
import { differentLocales } from '../constants/different-locales';
import { LocalizationService } from '../services/localization.service';
import { checkHasProp } from '../utils/common-utils';
export class LocaleId extends String {
constructor(private localizationService: LocalizationService) {
private localizationService = inject(LocalizationService);
constructor() {
super();
}
@ -24,5 +26,4 @@ export class LocaleId extends String {
export const LocaleProvider: Provider = {
provide: LOCALE_ID,
useClass: LocaleId,
deps: [LocalizationService],
};
};

8
npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts

@ -1,12 +1,14 @@
import { RestService } from '../../../../services';
import { Rest } from '../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import type { FindTenantResultDto } from '../../../volo/abp/asp-net-core/mvc/multi-tenancy/models';
@Injectable({
providedIn: 'root',
})
export class AbpTenantService {
export class AbpTenantService {
private restService = inject(RestService);
apiName = 'abp';
@ -24,6 +26,4 @@ export class AbpTenantService {
url: `/api/abp/multi-tenancy/tenants/by-name/${name}`,
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

8
npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts

@ -1,12 +1,14 @@
import { RestService } from '../../../../../../services';
import { Rest } from '../../../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import type { ApplicationApiDescriptionModel, ApplicationApiDescriptionModelRequestDto } from '../../../http/modeling/models';
@Injectable({
providedIn: 'root',
})
export class AbpApiDefinitionService {
export class AbpApiDefinitionService {
private restService = inject(RestService);
apiName = 'abp';
@ -17,6 +19,4 @@ export class AbpApiDefinitionService {
params: { includeTypes: model.includeTypes },
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

8
npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts

@ -1,12 +1,14 @@
import type { ApplicationConfigurationDto, ApplicationConfigurationRequestOptions } from './models';
import { RestService } from '../../../../../../services';
import { Rest } from '../../../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AbpApplicationConfigurationService {
export class AbpApplicationConfigurationService {
private restService = inject(RestService);
apiName = 'abp';
@ -17,6 +19,4 @@ export class AbpApplicationConfigurationService {
params: { includeLocalizationResources: options.includeLocalizationResources },
},
{ apiName: this.apiName, ...config });
constructor(private restService: RestService) { }
}

8
npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts

@ -1,12 +1,14 @@
import type { ApplicationLocalizationDto, ApplicationLocalizationRequestDto } from './models';
import { RestService } from '../../../../../../services';
import { Rest } from '../../../../../../models';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AbpApplicationLocalizationService {
export class AbpApplicationLocalizationService {
private restService = inject(RestService);
apiName = 'abp';
@ -17,6 +19,4 @@ export class AbpApplicationLocalizationService {
params: { cultureName: input.cultureName, onlyDynamics: input.onlyDynamics },
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

14
npm/ng-packs/packages/core/src/lib/services/config-state.service.ts

@ -1,4 +1,4 @@
import { Inject, Injectable, Optional } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { map, switchMap, take, tap } from 'rxjs/operators';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
@ -15,6 +15,10 @@ import { InternalStore } from '../utils/internal-store-utils';
providedIn: 'root',
})
export class ConfigStateService {
private abpConfigService = inject(AbpApplicationConfigurationService);
private abpApplicationLocalizationService = inject(AbpApplicationLocalizationService);
private readonly includeLocalizationResources = inject(INCUDE_LOCALIZATION_RESOURCES_TOKEN, { optional: true });
private updateSubject = new Subject<void>();
private readonly store = new InternalStore({} as ApplicationConfigurationDto);
@ -27,13 +31,7 @@ export class ConfigStateService {
get createOnUpdateStream() {
return this.store.sliceUpdate;
}
constructor(
private abpConfigService: AbpApplicationConfigurationService,
private abpApplicationLocalizationService: AbpApplicationLocalizationService,
@Optional()
@Inject(INCUDE_LOCALIZATION_RESOURCES_TOKEN)
private readonly includeLocalizationResources: boolean | null,
) {
constructor() {
this.initUpdateStream();
}

5
npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts

@ -1,9 +1,10 @@
import { Injectable, Injector, TemplateRef, Type } from '@angular/core';
import { Injectable, Injector, TemplateRef, Type, inject } from '@angular/core';
import { ProjectionStrategy } from '../strategies/projection.strategy';
@Injectable({ providedIn: 'root' })
export class ContentProjectionService {
constructor(private injector: Injector) {}
private injector = inject(Injector);
projectContent<T extends Type<any> | TemplateRef<any>>(
projectionStrategy: ProjectionStrategy<T>,

6
npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts

@ -1,4 +1,4 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { HttpRequest } from '@angular/common/http';
import { InternalStore } from '../utils/internal-store-utils';
import { getPathName } from '../utils/http-utils';
@ -26,7 +26,9 @@ export class HttpWaitService {
private delay: number;
private destroy$ = new Subject<void>();
constructor(injector: Injector) {
constructor() {
const injector = inject(Injector);
this.delay = injector.get(LOADER_DELAY, 500);
}

6
npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { concat, Observable, of, pipe, throwError } from 'rxjs';
import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators';
import { LoadingStrategy } from '../strategies';
@ -8,9 +8,9 @@ import { ResourceWaitService } from './resource-wait.service';
providedIn: 'root',
})
export class LazyLoadService {
readonly loaded = new Map<string, HTMLScriptElement | HTMLLinkElement | null>();
private resourceWaitService = inject(ResourceWaitService);
constructor(private resourceWaitService: ResourceWaitService) {}
readonly loaded = new Map<string, HTMLScriptElement | HTMLLinkElement | null>();
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event> {
if (this.loaded.has(strategy.path)) return of(new CustomEvent('load'));

6
npm/ng-packs/packages/core/src/lib/services/list.service.ts

@ -1,4 +1,4 @@
import { Injectable, Injector, OnDestroy } from '@angular/core';
import { Injectable, Injector, OnDestroy, inject } from '@angular/core';
import {
EMPTY,
BehaviorSubject,
@ -119,7 +119,9 @@ export class ListService<QueryParamsType = ABP.PageQueryParams | any> implements
this.next();
};
constructor(injector: Injector) {
constructor() {
const injector = inject(Injector);
const delay = injector.get(LIST_QUERY_DEBOUNCE_TIME, 300);
this.delay = delay ? debounceTime(delay) : tap();
this.get();

17
npm/ng-packs/packages/core/src/lib/services/localization.service.ts

@ -1,5 +1,5 @@
import { registerLocaleData } from '@angular/common';
import { Injectable, Injector, isDevMode, Optional, SkipSelf } from '@angular/core';
import { Injectable, Injector, isDevMode, inject } from '@angular/core';
import { BehaviorSubject, combineLatest, from, Observable, Subject } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { ABP } from '../models/common';
@ -17,6 +17,10 @@ import { SessionStateService } from './session-state.service';
@Injectable({ providedIn: 'root' })
export class LocalizationService {
private sessionState = inject(SessionStateService);
private injector = inject(Injector);
private configState = inject(ConfigStateService);
private latestLang = this.sessionState.getLanguage();
private _languageChange$ = new Subject<string>();
@ -44,14 +48,9 @@ export class LocalizationService {
return this._languageChange$.asObservable();
}
constructor(
private sessionState: SessionStateService,
private injector: Injector,
@Optional()
@SkipSelf()
otherInstance: LocalizationService,
private configState: ConfigStateService,
) {
constructor() {
const otherInstance = inject(LocalizationService, { optional: true, skipSelf: true })!;
if (otherInstance) throw new Error('LocalizationService should have only one instance.');
this.listenToSetLanguage();

16
npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { map, switchMap } from 'rxjs/operators';
import { AbpTenantService } from '../proxy/pages/abp/multi-tenancy';
import {
@ -7,11 +7,15 @@ import {
} from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { TENANT_KEY } from '../tokens/tenant-key.token';
import { ConfigStateService } from './config-state.service';
import { RestService } from './rest.service';
import { SessionStateService } from './session-state.service';
@Injectable({ providedIn: 'root' })
export class MultiTenancyService {
private sessionState = inject(SessionStateService);
private tenantService = inject(AbpTenantService);
private configStateService = inject(ConfigStateService);
tenantKey = inject(TENANT_KEY);
domainTenant: CurrentTenantDto | null = null;
isTenantBoxVisible = true;
@ -23,14 +27,6 @@ export class MultiTenancyService {
return this.configStateService.refreshAppState().pipe(map(_ => tenant));
};
constructor(
private restService: RestService,
private sessionState: SessionStateService,
private tenantService: AbpTenantService,
private configStateService: ConfigStateService,
@Inject(TENANT_KEY) public tenantKey: string,
) { }
setTenantByName(tenantName: string) {
return this.tenantService
.findTenantByName(tenantName)

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save