Browse Source

Merge branch 'dev' into openiddict6

pull/20979/head
maliming 1 year ago
committed by GitHub
parent
commit
e9057ad52e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 20
      .github/workflows/auto-pr.yml
  2. 2
      CONTRIBUTING.md
  3. 138
      Directory.Packages.props
  4. 94
      README.md
  5. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  6. 4
      common.props
  7. 2
      docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md
  8. 2
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md
  9. 2
      docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md
  10. 2
      docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/POST.md
  11. 2
      docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/Post.md
  12. BIN
      docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/signalr-activity-dashboard.png
  13. 94
      docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/Post.md
  14. BIN
      docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/cover.png
  15. 113
      docs/en/cli/index.md
  16. 2
      docs/en/deployment/ssl.md
  17. 98
      docs/en/docs-nav.json
  18. 2
      docs/en/framework/api-development/index.md
  19. 2
      docs/en/framework/api-development/standard-apis/index.md
  20. 2
      docs/en/framework/architecture/best-practices/application-layer-overview.md
  21. 2
      docs/en/framework/architecture/best-practices/data-access-overview.md
  22. 2
      docs/en/framework/architecture/best-practices/domain-layer-overview.md
  23. 2
      docs/en/framework/architecture/domain-driven-design/application-layer.md
  24. 2
      docs/en/framework/architecture/domain-driven-design/domain-layer.md
  25. 11
      docs/en/framework/architecture/multi-tenancy/index.md
  26. 2
      docs/en/framework/fundamentals/index.md
  27. 2
      docs/en/framework/infrastructure/index.md
  28. 143
      docs/en/framework/ui/angular/entity-filters.md
  29. 16
      docs/en/framework/ui/angular/environment.md
  30. BIN
      docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-custom-content-above-filter.png
  31. BIN
      docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-form.png
  32. BIN
      docs/en/framework/ui/angular/images/angular-advanced-entity-filters-without-form.png
  33. BIN
      docs/en/framework/ui/angular/images/angular-advanced-entity-filters.png
  34. BIN
      docs/en/framework/ui/angular/images/angular-lookup-select.gif
  35. BIN
      docs/en/framework/ui/angular/images/angular-lookup-typeahead.gif
  36. BIN
      docs/en/framework/ui/angular/images/grouped-menu-mobile.png
  37. BIN
      docs/en/framework/ui/angular/images/grouped-menu.png
  38. BIN
      docs/en/framework/ui/angular/images/manage-profile-page-new-tab.png
  39. BIN
      docs/en/framework/ui/angular/images/manage-profile-page.png
  40. 98
      docs/en/framework/ui/angular/lookup-components.md
  41. 77
      docs/en/framework/ui/angular/manage-profile-page-tabs.md
  42. 13
      docs/en/framework/ui/angular/modifying-the-menu.md
  43. 14
      docs/en/framework/ui/angular/overview.md
  44. 2
      docs/en/framework/ui/angular/quick-start.md
  45. 2
      docs/en/framework/ui/blazor/components/submit-button.md
  46. 2
      docs/en/framework/ui/index.md
  47. BIN
      docs/en/images/abp-generate-razor-page.png
  48. BIN
      docs/en/images/abp-overall-diagram-1600.png
  49. BIN
      docs/en/images/abp-overall-diagram.png
  50. BIN
      docs/en/images/linkedaccountloginmulti-tenancy.png
  51. BIN
      docs/en/images/linkedaccountmodalactionindirectlink.png
  52. BIN
      docs/en/images/linkedaccountmodalactionmulti-tenancy.png
  53. BIN
      docs/en/images/suite-add-existing-solution-8.3.png
  54. 10
      docs/en/index.md
  55. 20
      docs/en/modules/account/linkedaccounts.md
  56. 2
      docs/en/modules/cms-kit-pro/index.md
  57. 2
      docs/en/modules/openiddict.md
  58. 2
      docs/en/release-info/index.md
  59. 2
      docs/en/solution-templates/application-module/index.md
  60. 4
      docs/en/solution-templates/layered-web-application/index.md
  61. 6
      docs/en/solution-templates/single-layer-web-application/index.md
  62. 8
      docs/en/studio/release-notes.md
  63. 1
      docs/en/studio/version-compatibility.md
  64. 4
      docs/en/suite/add-solution.md
  65. 54
      docs/en/suite/create-solution.md
  66. 2
      docs/en/tools.md
  67. 3
      docs/en/tutorials/book-store/part-09.md
  68. 13
      docs/en/tutorials/modular-crm/part-06.md
  69. 13
      docs/en/tutorials/modular-crm/part-07.md
  70. 5
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo.Abp.AspNetCore.MultiTenancy.csproj
  71. 15
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
  72. 96
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.Designer.cs
  73. 28
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.cshtml
  74. 14
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPageModel.cs
  75. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  76. 50
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs
  77. 22
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml
  78. 19
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs
  79. 283
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AbpCompilationRazorPageBase.cs
  80. 34
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AttributeValue.cs
  81. 19
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/HelperResult.cs
  82. 78
      framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs
  83. 1
      framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
  84. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  85. 227
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateRazorPage.cs
  86. 7
      framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs
  87. 3
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
  88. 7
      modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor
  89. 7
      npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts
  90. 7
      npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts
  91. 17
      npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts
  92. 9
      npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts
  93. 48
      npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts
  94. 35
      npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts
  95. 35
      npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts
  96. 13
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  97. 6
      npm/ng-packs/packages/core/src/lib/utils/string-utils.ts
  98. 14
      npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts
  99. 11
      npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts
  100. 16
      npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts

20
.github/workflows/auto-pr.yml

@ -1,13 +1,13 @@
name: Merge branch dev with rel-8.3
name: Merge branch dev with prerel-9.0
on:
push:
branches:
- rel-8.3
- prerel-9.0
permissions:
contents: read
jobs:
merge-dev-with-rel-8-3:
merge-dev-with-prerel-9-0:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
@ -18,19 +18,19 @@ jobs:
ref: dev
- name: Reset promotion branch
run: |
git fetch origin rel-8.3:rel-8.3
git reset --hard rel-8.3
git fetch origin prerel-9.0:prerel-9.0
git reset --hard prerel-9.0
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-8-3/${{github.run_number}}
title: Merge branch dev with rel-8.3
body: This PR generated automatically to merge dev with rel-8.3. Please review the changed files before merging to prevent any errors that may occur.
branch: auto-merge/prerel-9-0/${{github.run_number}}
title: Merge branch dev with prerel-9.0
body: This PR generated automatically to merge dev with prerel-9.0. Please review the changed files before merging to prevent any errors that may occur.
reviewers: maliming
token: ${{ github.token }}
- name: Merge Pull Request
env:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
gh pr review auto-merge/rel-8-3/${{github.run_number}} --approve
gh pr merge auto-merge/rel-8-3/${{github.run_number}} --merge --auto --delete-branch
gh pr review auto-merge/prerel-9-0/${{github.run_number}} --approve
gh pr merge auto-merge/prerel-9-0/${{github.run_number}} --merge --auto --delete-branch

2
CONTRIBUTING.md

@ -1,3 +1,3 @@
## Contribution
See the [contribution guide](docs/en/Contribution/Index.md).
The contribution guide is available at [contribution guide](docs/en/contribution/index.md).

138
Directory.Packages.props

@ -3,53 +3,53 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="2.0.24" />
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="3.0.0" />
<PackageVersion Include="aliyun-net-sdk-sts" Version="3.1.2" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AsyncKeyedLock" Version="7.0.1" />
<PackageVersion Include="Autofac" Version="8.0.0" />
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Autofac" Version="8.1.0" />
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
<PackageVersion Include="AutoMapper" Version="13.0.1" />
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="AWSSDK.S3" Version="3.7.300.2" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.300.2" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.17.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageVersion Include="Blazorise" Version="1.5.2" />
<PackageVersion Include="Blazorise.Components" Version="1.5.2" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.5.2" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.5.2" />
<PackageVersion Include="AWSSDK.S3" Version="3.7.400.30" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.400.30" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.22.1" />
<PackageVersion Include="Blazorise" Version="1.6.1" />
<PackageVersion Include="Blazorise.Components" Version="1.6.1" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.6.1" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.6.1" />
<PackageVersion Include="Castle.Core" Version="5.1.1" />
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
<PackageVersion Include="Confluent.Kafka" Version="2.3.0" />
<PackageVersion Include="Dapper" Version="2.1.21" />
<PackageVersion Include="Dapr.AspNetCore" Version="1.12.0" />
<PackageVersion Include="Dapr.Client" Version="1.12.0" />
<PackageVersion Include="DeviceDetector.NET" Version="6.1.4" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.3.10.8" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.5" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.2" />
<PackageVersion Include="DeepL.net" Version="1.8.0" />
<PackageVersion Include="Confluent.Kafka" Version="2.5.3" />
<PackageVersion Include="Dapper" Version="2.1.44" />
<PackageVersion Include="Dapr.AspNetCore" Version="1.14.0" />
<PackageVersion Include="Dapr.Client" Version="1.14.0" />
<PackageVersion Include="DeviceDetector.NET" Version="6.3.3" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.3.21.8" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.7" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
<PackageVersion Include="DeepL.net" Version="1.10.0" />
<PackageVersion Include="EphemeralMongo.Core" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<PackageVersion Include="FluentValidation" Version="11.8.0" />
<PackageVersion Include="FluentValidation" Version="11.10.0" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageVersion Include="HtmlSanitizer" Version="8.0.746" />
<PackageVersion Include="IdentityModel" Version="6.2.0" />
<PackageVersion Include="HtmlSanitizer" Version="8.1.870" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="IdentityServer4" Version="4.1.2" />
<PackageVersion Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageVersion Include="LdapForNet" Version="2.7.15" />
<PackageVersion Include="LibGit2Sharp" Version="0.28.0" />
<PackageVersion Include="LibGit2Sharp" Version="0.30.0" />
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="13.4.0" />
<PackageVersion Include="MailKit" Version="4.7.1.1" />
<PackageVersion Include="MailKit" Version="4.8.0" />
<PackageVersion Include="Markdig.Signed" Version="0.37.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0-rc.1.24452.1" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0-rc.1.24452.1" />
@ -65,7 +65,7 @@
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0-rc.1.24452.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0-rc.1.24452.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0-rc.1.24452.1" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.25" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.33" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.0-rc.1.24452.1" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.0-rc.1.24452.1" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0-rc.1.24431.7" />
@ -103,62 +103,65 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0-rc.1.24457.2" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.0.2" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.0.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.2" />
<PackageVersion Include="Minio" Version="6.0.2" />
<PackageVersion Include="MongoDB.Driver" Version="2.22.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.1.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.1.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.1.0" />
<PackageVersion Include="Minio" Version="6.0.3" />
<PackageVersion Include="MongoDB.Driver" Version="2.29.0" />
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Nito.AsyncEx.Context" Version="5.1.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0-rc.1" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="NuGet.Versioning" Version="6.7.0" />
<PackageVersion Include="NUglify" Version="1.21.0" />
<PackageVersion Include="NuGet.Versioning" Version="6.11.1" />
<PackageVersion Include="NUglify" Version="1.21.9" />
<PackageVersion Include="Nullable" Version="1.3.1" />
<PackageVersion Include="Octokit" Version="9.0.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.23.40" />
<PackageVersion Include="Polly" Version="8.2.0" />
<PackageVersion Include="Octokit" Version="13.0.1" />
<PackageVersion Include="OpenIddict.Abstractions" Version="6.0.0-preview1.24504.78" />
<PackageVersion Include="OpenIddict.Core" Version="6.0.0-preview1.24504.78" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="6.0.0-preview1.24504.78" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="6.0.0-preview1.24504.78" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="6.0.0-preview1.24504.78" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.23.40" />
<PackageVersion Include="Polly" Version="8.2.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.23.60" />
<PackageVersion Include="Polly" Version="8.4.2" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.1" />
<PackageVersion Include="Quartz" Version="3.7.0" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.7.0" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.7.0" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.7.0" />
<PackageVersion Include="RabbitMQ.Client" Version="6.6.0" />
<PackageVersion Include="Rebus" Version="8.4.2" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.1.2" />
<PackageVersion Include="Scriban" Version="5.9.0" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageVersion Include="Quartz" Version="3.13.0" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.13.0" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.13.0" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.13.0" />
<PackageVersion Include="RabbitMQ.Client" Version="6.8.1" />
<PackageVersion Include="Rebus" Version="8.6.0" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.2.0" />
<PackageVersion Include="Scriban" Version="5.10.0" />
<PackageVersion Include="Serilog" Version="4.0.2" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.0.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.6" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.6" />
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.6" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" />
<PackageVersion Include="SkiaSharp" Version="2.88.8" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" />
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.8" />
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
<PackageVersion Include="Spectre.Console" Version="0.47.0" />
<PackageVersion Include="StackExchange.Redis" Version="2.7.4" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Spectre.Console" Version="0.49.1" />
<PackageVersion Include="StackExchange.Redis" Version="2.8.16" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.8.1" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.3.5" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.4.5" />
<PackageVersion Include="System.Linq.Queryable" Version="4.3.0" />
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
<PackageVersion Include="System.Security.Permissions" Version="9.0.0-rc.1.24431.7" />
@ -166,15 +169,14 @@
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="System.Text.Json" Version="9.0.0-rc.1.24431.7" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.1.0" />
<PackageVersion Include="TimeZoneConverter" Version="6.1.0" />
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />
<PackageVersion Include="xunit" Version="2.6.1" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.6.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.0" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
<PackageVersion Include="Fody" Version="6.8.2" />
</ItemGroup>
</Project>

94
README.md

@ -3,92 +3,79 @@
![build and test](https://img.shields.io/github/actions/workflow/status/abpframework/abp/build-and-test.yml?branch=dev&style=flat-square) 🔹 [![codecov](https://codecov.io/gh/abpframework/abp/branch/dev/graph/badge.svg?token=jUKLCxa6HF)](https://codecov.io/gh/abpframework/abp) 🔹 [![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![NuGet (with prereleases)](https://img.shields.io/nuget/vpre/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![MyGet (nightly builds)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://abp.io/docs/latest/release-info/nightly-builds) 🔹
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/abpframework/abp/blob/dev/CODE_OF_CONDUCT.md) 🔹 [![CLA Signed](https://cla-assistant.io/readme/badge/abpframework/abp)](https://cla-assistant.io/abpframework/abp) 🔹 [![Discord Shield](https://discord.com/api/guilds/951497912645476422/widget.png?style=shield)](https://discord.gg/abp)
ABP Framework is a complete **infrastructure** based on **ASP.NET Core** that creates **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**.
[ABP](https://abp.io/) offers an **opinionated architecture** to build enterprise software solutions with **best practices** on top of the **.NET** and the **ASP.NET Core** platforms. It provides the fundamental infrastructure, production-ready startup templates, pre-built application modules, UI themes, tooling, guides and documentation to implement that architecture properly and **automate the details** and repetitive works as much as possible.
[![ABP Platform](https://github.com/user-attachments/assets/200653c0-0e69-4b47-b76a-3a83460aaab6)](https://abp.io)
## Getting Started
- [The Getting Started guide](https://abp.io/docs/latest/get-started) can be used to create and run ABP-based solutions with different options and details.
- [Quick Start](https://abp.io/docs/latest/tutorials/todo) is a single-part, quick-start tutorial to build a simple application with the ABP Framework. Start with this tutorial if you want to understand how ABP works quickly.
- [Getting Started guide](https://abp.io/docs/latest/get-started) can be used to create and run ABP-based solutions with different options and details.
- [Web Application Development Tutorial](https://abp.io/docs/latest/tutorials/book-store) is a complete tutorial on developing a full-stack web application with all aspects of a real-life solution.
### Quick Start
Install the ABP CLI:
````bash
> dotnet tool install -g Volo.Abp.Cli
````
Create a new solution:
````bash
> abp new BookStore -u mvc -d ef
````
> See the [CLI documentation](https://abp.io/docs/latest/cli) for all available options.
### UI Framework Options
<img width="500" src="docs/en/images/ui-options.png">
### Database Provider Options
<img width="500" src="docs/en/images/db-options.png">
- [Modular Monolith Application](https://abp.io/docs/latest/tutorials/modular-crm/index): A multi-part tutorial that demonstrates how to create application modules, compose and communicate them to build a monolith modular web application.
## What ABP Provides?
ABP provides a **full stack developer experience**.
ABP bridges the gap between ASP.NET Core and real-world business application requirements, and makes you focus on your own business code.
The following diagram contains the core components of the **ABP Platform** and shows how ABP sits between **ASP.NET Core** and **Your Application**:
![abp-overall-diagram](docs/en/images/abp-overall-diagram.png)
### Architecture
<img src="docs/en/images/ddd-microservice-simple.png">
ABP offers a complete architectural model to build modern enterprise software solutions. Here, the fundamental architectural structures offered and first-class supported by ABP:
ABP offers a complete, **modular** and **layered** software architecture based on **[Domain Driven Design](https://abp.io/docs/latest/framework/architecture/domain-driven-design)** principles and patterns. It also provides the necessary infrastructure and guidance to [implement this architecture](https://abp.io/books/implementing-domain-driven-design).
* [Domain Driven Design](https://abp.io/docs/latest/framework/architecture/domain-driven-design)
* [Microservices](https://abp.io/docs/latest/framework/architecture/microservices)
* [Modularity](https://abp.io/docs/latest/framework/architecture/modularity/basics)
* [Multi-Tenancy](https://abp.io/docs/latest/framework/architecture/multi-tenancy)
ABP Framework is suitable for **[microservice solutions](https://abp.io/docs/latest/framework/architecture/microservices)** as well as monolithic applications.
### Infrastructure
There are a lot of infrastructure features provided by the ABP Framework to achieve real-world scenarios easier, like [Event Bus](https://abp.io/docs/latest/framework/infrastructure/event-bus), [Background Job System](https://abp.io/docs/latest/framework/infrastructure/background-jobs), [Audit Logging](https://abp.io/docs/latest/framework/infrastructure/audit-logging), [BLOB Storing](https://abp.io/docs/latest/framework/infrastructure/blob-storing), [Data Seeding](https://abp.io/docs/latest/framework/infrastructure/data-seeding), [Data Filtering](https://abp.io/docs/latest/framework/infrastructure/data-filtering), and much more.
[See ABP Framework features](https://abp.io/framework)
### Infrastructure
#### Cross-Cutting Concerns
There are a lot of features provided by the ABP Framework to achieve real-world scenarios easier, like [Event Bus](https://abp.io/docs/latest/framework/infrastructure/event-bus), [Background Job System](https://abp.io/docs/latest/framework/infrastructure/background-jobs), [Audit Logging](https://abp.io/docs/latest/framework/infrastructure/audit-logging), [BLOB Storing](https://abp.io/docs/latest/framework/infrastructure/blob-storing), [Data Seeding](https://abp.io/docs/latest/framework/infrastructure/data-seeding), [Data Filtering](https://abp.io/docs/latest/framework/infrastructure/data-filtering), etc.
ABP also simplifies (and even automates wherever possible) cross-cutting concerns and common non-functional requirements like [Exception Handling](https://abp.io/docs/latest/framework/fundamentals/exception-handling), [Validation](https://abp.io/docs/latest/framework/fundamentals/validation), [Authorization](https://abp.io/docs/latest/framework/fundamentals/authorizationn), [Localization](https://abp.io/docs/latest/framework/fundamentals/localization), [Caching](https://abp.io/docs/latest/framework/fundamentals/caching), [Dependency Injection](https://abp.io/docs/latest/framework/fundamentals/dependency-injection), [Setting Management](https://abp.io/docs/latest/framework/infrastructure/settings), etc.
### Application Modules
ABP is a modular framework and the [application modules](https://abp.io/modules) provide **pre-built application functionalities**.
### Cross-Cutting Concerns
Some examples:
ABP also simplifies (and even automates wherever possible) cross-cutting concerns and common non-functional requirements like [Exception Handling](https://abp.io/docs/latest/framework/fundamentals/exception-handling), [Validation](https://abp.io/docs/latest/framework/fundamentals/validation), [Authorization](https://abp.io/docs/latest/framework/fundamentals/authorizationn), [Localization](https://abp.io/docs/latest/framework/fundamentals/localization), [Caching](https://abp.io/docs/latest/framework/fundamentals/caching), [Dependency Injection](https://abp.io/docs/latest/framework/fundamentals/dependency-injection), [Setting Management](https://abp.io/docs/latest/framework/infrastructure/settings), etc.
- [**Account**](https://abp.io/modules/Volo.Account.Pro): Provides UI for the account management and allows user to login/register to the application.
- [CMS Kit](https://abp.io/modules/Volo.CmsKit): Brings CMS (Content Management System) capabilities to your application.
- **[Identity](https://abp.io/modules/Volo.Identity.Pro)**: Manages organization units, roles, users and their permissions based on the Microsoft Identity library.
- [**OpenIddict**](https://abp.io/modules/Volo.OpenIddict.Pro): Integrates to OpenIddict library and provides a management UI.
- [**SaaS**](https://abp.io/modules/Volo.Saas): Manages tenants and editions for a [multi-tenant](https://abp.io/docs/latest/framework/architecture/multi-tenancy) (SaaS) application.
See [all official modules](https://abp.io/modules).
### Startup Templates
### Application Modules
The [Startup templates](https://abp.io/docs/latest/solution-templates) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
ABP is a modular framework and the Application Modules provide **pre-built application functionalities**;
### Tooling
- [**Account**](https://abp.io/docs/latest/modules/account): Provides UI for the account management and allows user to login/register to the application.
- **[Identity](https://abp.io/docs/latest/modules/identity)**: Manages organization units, roles, users and their permissions based on the Microsoft Identity library.
- [**OpenIddict**](https://abp.io/docs/latest/modules/openiddict): Integrates to OpenIddict.
- [**Tenant Management**](https://abp.io/docs/latest/modules/tenant-management): Manages tenants for a [multi-tenant](https://abp.io/docs/latest/framework/architecture/multi-tenancy) (SaaS) application.
ABP provides CLI and UI tools to simplify your daily development work flows.
See the [Application Modules](https://abp.io/docs/latest/modules) document for all pre-built modules.
#### ABP Studio
[ABP Studio](https://abp.io/studio) is a cross-platform desktop application for ABP developers.
It is well integrated to the ABP Framework and aims to provide a comfortable development environment for you by automating things, providing insights about your solution, making develop, run and deploy your solutions much easier.
### Startup Templates
#### ABP Suite
The [Startup templates](https://abp.io/docs/latest/solution-templates) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
[ABP Suite](https://abp.io/suite) allows you to automatically generate web pages in a matter of minutes.
#### ABP CLI
[ABP CLI](https://abp.io/cli) is a command line tool to perform common operations for ABP based solutions.
## Mastering ABP Framework Book
@ -102,7 +89,7 @@ This book will help you to gain a complete understanding of the ABP Framework an
### ABP Community Web Site
The [ABP Community](https://abp.io/community) is a website to publish **articles** and share **knowledge** about the ABP Framework. You can also create content for the community!
The [ABP Community](https://abp.io/community) is a central hub to publish **articles** and share **knowledge** about the ABP Framework.
### Blog
@ -116,8 +103,6 @@ See the [sample projects](https://abp.io/docs/latest/samples) built with the ABP
ABP is a community-driven open-source project. See [the contribution guide](https://abp.io/docs/latest/contribution) if you want to participate in this project.
## Official Links
* [Home Website](https://abp.io)
@ -130,15 +115,10 @@ ABP is a community-driven open-source project. See [the contribution guide](http
* [Stackoverflow](https://stackoverflow.com/questions/tagged/abp)
* [Twitter](https://twitter.com/abpframework)
## Support ABP
GitHub repository stars are an important indicator of popularity and the size of the community. If you like ABP Framework, support us by clicking the star :star: on the repository.
## Discord Server
We have a Discord server where you can chat with other ABP users. Share your ideas, report technical issues, showcase your creations, share the tips that worked for you and catch up with the latest news and announcements about ABP Framework. Join 👉 https://discord.gg/abp.

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

@ -735,7 +735,7 @@
"Layout_Title": "{0} | ABP Community",
"Layout_MetaDescription": "A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.",
"Index_Page_CommunityIntroduction": "This is a hub for ABP Framework, .NET and software development. You can read the articles, watch the video tutorials, get informed about ABP’s development progress and ABP-related events, help other developers and share your expertise with the ABP community.",
"TagsInArticle": "Tags in article",
"TagsInArticle": "Tags in Article",
"IConsentToMedium": "I consent to the publication of this post at https://medium.com/volosoft.",
"SearchResultsFor": "Search results for <span class=\"fw-bold\">\"{0}\"</span>",
"SeeMoreVideos": "See More Videos",

4
common.props

@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>9.0.0-preview</Version>
<LeptonXVersion>4.0.0-preview</LeptonXVersion>
<Version>9.1.0-preview</Version>
<LeptonXVersion>4.1.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

2
docs/en/Community-Articles/2020-08-18-Using-DevExtreme-Components-With-The-ABP-Framework/Using-DevExtreme-Components-With-The-ABP-Framework.md

@ -1,4 +1,4 @@
## Using DevExtreme Components With the ABP Framework
# Using DevExtreme Components With the ABP Framework
Hi, in this step by step article, I will show you how to integrate [DevExtreme](https://js.devexpress.com/) components into ABP Framework-based applications.

2
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-DevExpress-Blazor-Component/POST.md

@ -1,4 +1,4 @@
## Using DevExpress Blazor UI Components With the ABP Framework
# Using DevExpress Blazor UI Components With the ABP Framework
Hi, in this step by step article, I will show you how to integrate [DevExpress](https://demos.devexpress.com/blazor/) blazor UI components into ABP Framework-based applications.

2
docs/en/Community-Articles/2021-01-20-How-to-Integrate-the-MatBlazor-Blazor-Component/POST.md

@ -1,4 +1,4 @@
## Using MatBlazor UI Components With the ABP Framework
# Using MatBlazor UI Components With the ABP Framework
Hi, in this step by step article, I will show you how to integrate [MatBlazor](https://www.matblazor.com/), a blazor UI components into ABP Framework-based applications.

2
docs/en/Community-Articles/2021-04-22-How-to-Integrate-the-Syncfusion-Blazor-Component/POST.md

@ -1,4 +1,4 @@
## Integrating SyncFusion UI with ABP Framework Blazor UI
# Integrating SyncFusion UI with ABP Framework Blazor UI
Hi, in this step by step article, I will show you how to integrate [Syncfusion](https://www.syncfusion.com/blazor-components), a blazor UI components into ABP Framework-based applications.

2
docs/en/Community-Articles/2023-03-20- IdentityUser-Relationships/Post.md

@ -1,4 +1,4 @@
## Introduction
# Introduction
In this article, I will talk about the relationships of IdentityUser in every web application that can be created with the ABP framework. When you read this article, you will learn how to extend the user entity in the applications you develop using the ABP framework with a primitive type, extending the user by associating the user with another entity (User-many-to-one-X).

BIN
docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/signalr-activity-dashboard.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 44 KiB

94
docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/Post.md

@ -0,0 +1,94 @@
# .NET 9 Performance Improvements Summary
With every release, .NET becomes faster & faster! You get these improvements for free by just updating your project to the latest .NET!
![Cover Image](cover.png)
It’s very interesting that **20% of these improvements** are implemented by **open-source volunteers** rather than Microsoft employees. These improvements mostly focus on cloud-native and high-throughput applications. I’ll briefly list them below.
## 1. Dynamic PGO with JIT Compiler
* ### What is dynamic PGO?
With “Profile Guided Optimization” the compiler optimizes the code, based on the flow and the way the code executes. It is predicated on the idea that every potential behavior of the code will always transpire.
* ### What’s Improved?
The tiered compilation, inlining, and dynamic PGO are three ways that .NET 9 optimizes the JIT compiler. This enhances runtime performance and speeds up the time for apps to launch.
* ### Performance Gains
CPU use is lower during execution; therefore, **startup times are about 15% faster**.
* ### As a Developer
Faster, smoother deployments with reduced warm-up times... These enhancements reduce latency for applications with complex workflows, particularly in microservices and high-throughput environments.
* ### How to activate Dynamic PGO?
Add the following to your `csproj` file, or if you have several `csproj` files, you can add it once in `Directory.Build.props` file. Check out [this link](https://learn.microsoft.com/en-us/dotnet/core/runtime-config/compilation#profile-guided-optimization) to understand PGO.
```xml
<PropertyGroup>
<TieredPGO>true</TieredPGO>
</PropertyGroup>
```
## 2. Library Improvements
* ### What’s Improved?
LINQ and JSON serialization, collections and libraries are significantly improved with .NET 9.
* ### Performance Gains
**JSON serialization** performance **increases by about 35%**. This helps with heavy data parsing and API requests. Less memory is allocated to `Span` operations as well, and LINQ techniques such as `Where` and `Select` are now faster.
* ### As a Developer
This means that apps will be faster, especially those that handle data primarily in JSON or manipulate data with LINQ.
## 3. ASP.NET Core
* ### What’s Improved?
Kestrel server has undergone significant modifications, mostly in processing the HTTP/2 and HTTP/3 protocols.
* ### Performance Gains
Now, **Kestrel handles requests up to 20% faster** and **has a 25% reduction in average latency**. Improved connection management and SSL processing also result in overall efficiency gains.
* ### As a Developer
These modifications result in less resource use, quicker response times for web applications, and more seamless scaling in high-traffic situations.
## 4. Garbage Collection & Memory Management
* ### What’s Improved?
NET 9’s garbage collection (GC) is more effective, especially for apps with high allocation rates.
* ### Performance Gains
Applications experience smoother **garbage collection cycles with 8–12% less memory overhead**, which lowers latency and delays.
* ### As a Developer
The performance will be more reliable and predictable for developers as there will be fewer memory-related bottlenecks, particularly in applications that involve frequent object allocations.
## 5. Native AOT Compilation
* ### What’s Improved?
Native AOT (Ahead-of-Time) compilation is now more efficient by lowering memory footprint and cold-start times. This leads to better support for cloud-native applications.
* ### Performance Gains
Native AOT apps now have faster cold launches and use **30–40% less memory**. This improvement focuses on containerized applications.
---
**References:**
* [Microsoft .NET blog post](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/).
* [What’s new in the .NET 9 runtime?](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/runtime#performance-improvements)

BIN
docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

113
docs/en/cli/index.md

@ -70,6 +70,7 @@ Here, is the list of all available commands before explaining their details:
* **`clear-download-cache`**: Clears the templates download cache.
* **`check-extensions`**: Checks the latest version of the ABP CLI extensions.
* **`install-old-cli`**: Installs old ABP CLI.
* **`generate-razor-page`**: Generates a page class that you can use it in the ASP NET Core pipeline to return an HTML page.
### help
@ -964,6 +965,118 @@ Usage:
abp install-old-cli [options]
```
### generate-razor-page
`generate-razor-page` command to generate a page class and then use it in the ASP NET Core pipeline to return an HTML page.
Usage:
1. Create a new `Razor Page(MyPage.cshtml)` that inherits from `AbpCompilationRazorPageBase` in `Views` folder.
2. Create a `MyPageModel` class in the same folder.
3. Create a `MyPage.js` and `MyPage.css` files in the same folder.
4. Add the following code to the `MyPage.cshtml`, `MyPage.css` and `MyPage.js` files.
```cs
public class MyPageModel
{
public string Message { get; set; }
public MyPageModel(string message)
{
Message = message;
}
}
```
```cs
@using System.Globalization
@using Volo.Abp.AspNetCore.RazorViews
@inherits AbpCompilationRazorPageBase
@{
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 200;
}
@functions{
public MyPage(MyPageModel model)
{
Model = model;
}
public MyPageModel Model { get; set; }
}
<html lang="@HtmlEncoder.Encode(CultureInfo.CurrentCulture.Name)">
<head>
<meta charset="utf-8" />
<style>
<%$ include: MyPage.css %>
</style>
<title>@HtmlEncoder.Encode(Model.Message)</title>
</head>
<body>
<h3>@HtmlEncoder.Encode(Model.Message)</h3>
<ul class="list-group">
@for(int i = 0; i < 10; i++)
{
<li class="list-group-item">@i item</li>
}
</ul>
<script>
//<!--
<%$ include: MyPage.js %>
//-->
</script>
</body>
</html>
```
```css
body {
background-color: #65b2ff;
color: #495057;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
```
```js
console.log('MyPage.js loaded!');
```
5. Finally, run the `generate-razor-page` command under the `Views` folder:
```bash
> abp generate-razor-page
Generating code files for pages in /MyProject/Views
Generating code file for page MyPage.cshtml ...
Inlining file MyPage.css
Inlining file MyPage.js
Done!
1 files successfully generated.
```
The output will be like in the above command output, and `MyPage.Designer.cs` file will be created in the same folder. It's a standard C# class that you can use it in the pipeline to return an HTML page:
```cs
app.Use(async (httpContext, next) =>
{
if (true) // Your condition
{
var page = new MyPage(new MyPageModel("Test message"));
await page.ExecuteAsync(httpContext);
}
else
{
await next();
}
});
```
![Razor Page](./../images/abp-generate-razor-page.png)
#### Options
* ```--version``` or ```-v```: Specifies the version for ABP CLI to be installed.

2
docs/en/deployment/ssl.md

@ -1,4 +1,4 @@
# Configuring Configuring SSL certificate(HTTPS)
# Configuring SSL certificate(HTTPS)
A website needs an SSL certificate in order to keep user data secure, verify ownership of the website, prevent attackers from creating a fake version of the site, and gain user trust.

98
docs/en/docs-nav.json

@ -1,41 +1,41 @@
{
"items": [
{
"text": "Get started",
"text": "Get Started",
"items": [
{
"text": "Overview",
"path": "get-started"
},
{
"text": "Single-layer solution",
"text": "Single Layer Web Application",
"path": "get-started/single-layer-web-application.md"
},
{
"text": "Layered solution",
"text": "Layered Web Application",
"path": "get-started/layered-web-application.md"
},
{
"text": "Microservice solution",
"text": "Microservice Solution",
"path": "get-started/microservice.md"
},
{
"text": "Others",
"items": [
{
"text": "Empty ASP.NET Core application",
"text": "Empty ASP.NET Core MVC / Razor Pages Application",
"path": "get-started/empty-aspnet-core-application.md"
},
{
"text": "MAUI application",
"text": "MAUI Application Startup Template",
"path": "get-started/maui.md"
},
{
"text": "WPF application",
"text": "WPF Application Startup Template",
"path": "get-started/wpf.md"
},
{
"text": "Console application",
"text": "Console Application Startup Template",
"path": "get-started/console.md"
}
]
@ -57,36 +57,36 @@
"path": "tutorials/todo"
},
{
"text": "Single-layer solution",
"text": "Single-Layer Solution",
"path": "tutorials/todo/single-layer"
},
{
"text": "Layered solution",
"text": "Layered Solution",
"path": "tutorials/todo/layered"
}
]
},
{
"text": "Book Store Application",
"text": "Web Application Development",
"items": [
{
"text": "Overview",
"path": "tutorials/book-store"
},
{
"text": "1: Creating the server side",
"text": "1: Creating the Server Side",
"path": "tutorials/book-store/part-01.md"
},
{
"text": "2: The Book List page",
"text": "2: The Book List Page",
"path": "tutorials/book-store/part-02.md"
},
{
"text": "3: Creating, updating and deleting books",
"text": "3: Creating, Updating and Deleting Books",
"path": "tutorials/book-store/part-03.md"
},
{
"text": "4: Integration tests",
"text": "4: Integration Tests",
"path": "tutorials/book-store/part-04.md"
},
{
@ -94,23 +94,23 @@
"path": "tutorials/book-store/part-05.md"
},
{
"text": "6: Authors: Domain layer",
"text": "6: Authors: Domain Layer",
"path": "tutorials/book-store/part-06.md"
},
{
"text": "7: Authors: Database integration",
"text": "7: Authors: Database Integration",
"path": "tutorials/book-store/part-07.md"
},
{
"text": "8: Authors: Application layer",
"text": "8: Authors: Application Layer",
"path": "tutorials/book-store/part-08.md"
},
{
"text": "9: Authors: User interface",
"text": "9: Authors: User Interface",
"path": "tutorials/book-store/part-09.md"
},
{
"text": "10: Book to author relation",
"text": "10: Book to Author Relation",
"path": "tutorials/book-store/part-10.md"
}
]
@ -123,35 +123,35 @@
"path": "tutorials/modular-crm/index.md"
},
{
"text": "1: Creating the initial solution",
"text": "1: Creating the Initial Solution",
"path": "tutorials/modular-crm/part-01.md"
},
{
"text": "2: Creating the initial Products module",
"text": "2: Creating the Initial Products Module",
"path": "tutorials/modular-crm/part-02.md"
},
{
"text": "3: Building the Products module",
"text": "3: Building the Products Module",
"path": "tutorials/modular-crm/part-03.md"
},
{
"text": "4: Creating the initial Ordering module",
"text": "4: Creating the Initial Ordering Module",
"path": "tutorials/modular-crm/part-04.md"
},
{
"text": "5: Building the Ordering module",
"text": "5: Building the Ordering Module",
"path": "tutorials/modular-crm/part-05.md"
},
{
"text": "6: Integrating the modules: Implementing Integration Services",
"text": "6: Integrating the Modules: Implementing Integration Services",
"path": "tutorials/modular-crm/part-06.md"
},
{
"text": "7: Integrating the modules: Communication via Messages (Events)",
"text": "7: Integrating the Modules: Communication via Messages (Events)",
"path": "tutorials/modular-crm/part-07.md"
},
{
"text": "8: Integrating the modules: Joining the Products and Orders Data",
"text": "8: Integrating the Modules: Joining the Products and Orders Data",
"path": "tutorials/modular-crm/part-08.md"
}
]
@ -177,7 +177,7 @@
"path": "cli"
},
{
"text": "Examples for the new command",
"text": "New Solution Sample Commands",
"path": "cli/new-command-samples.md"
}
]
@ -256,7 +256,7 @@
"path": "suite/add-solution.md"
},
{
"text": "Creating new ABP solution",
"text": "Creating New ABP Solution",
"path": "suite/create-solution.md"
},
{
@ -271,7 +271,7 @@
"path": "suite/creating-many-to-many-relationship.md"
},
{
"text": "Generating from an Existing Database",
"text": "Generating From an Existing Database",
"path": "suite/generating-entities-from-an-existing-database-table.md"
},
{
@ -301,7 +301,7 @@
"path": "suite/editing-templates.md"
},
{
"text": "How to uninstall",
"text": "How to Uninstall",
"path": "suite/how-to-uninstall.md"
}
]
@ -336,7 +336,7 @@
]
},
{
"text": "Caching",
"text": "Distributed Caching",
"items": [
{
"text": "Overview",
@ -657,7 +657,7 @@
"path": "framework/architecture"
},
{
"text": "Best Practices",
"text": "Module Development Best Practices & Conventions",
"items": [
{
"text": "Overview",
@ -1188,7 +1188,7 @@
"text": "Other Components",
"items": [
{
"text": "SubmitButton",
"text": "SubmitButton Component",
"path": "framework/ui/blazor/components/submit-button.md"
}
]
@ -1230,6 +1230,10 @@
{
"text": "Angular",
"items": [
{
"text": "Overview",
"path": "framework/ui/angular/overview.md"
},
{
"text": "Quick Start",
"path": "framework/ui/angular/quick-start.md"
@ -1282,7 +1286,7 @@
"path": "framework/ui/angular/http-requests.md"
},
{
"text": "Customize Error Handling",
"text": "Error Handling",
"path": "framework/ui/angular/http-error-handling.md"
}
]
@ -1442,6 +1446,10 @@
"text": "Modifying the Menu",
"path": "framework/ui/angular/modifying-the-menu.md"
},
{
"text": "Manage Profile Page Tabs",
"path": "framework/ui/angular/manage-profile-page-tabs.md"
},
{
"text": "Sorting Navigation Elements",
"path": "framework/ui/angular/sorting-navigation-elements.md"
@ -1495,6 +1503,18 @@
{
"text": "Card",
"path": "framework/ui/angular/card-component.md"
},
{
"text": "Password Complexity Indicator",
"path": "framework/ui/angular/password-complexity-indicator-component.md"
},
{
"text": "Lookup Components(Pro)",
"path": "framework/ui/angular/lookup-components.md"
},
{
"text": "Entity Filters(Pro)",
"path": "framework/ui/angular/entity-filters.md"
}
]
}
@ -1635,7 +1655,7 @@
"path": "solution-templates/microservice"
},
{
"text": "Application Module",
"text": "Module Solution",
"path": "solution-templates/application-module"
}
]
@ -1950,7 +1970,7 @@
"path": "others/why-abp-platform.md"
},
{
"text": "Free license vs Pro licenses?",
"text": "Free (Open Source) License vs Commercial (Pro) Licenses",
"path": "others/free-licenses-vs-pro-licenses.md"
},
{
@ -1958,7 +1978,7 @@
"path": "others/penetration-test-report.md"
},
{
"text": "ASP.NET Zero vs ABP",
"text": "ABP vs ASP.NET Zero",
"path": "others/aspnet-zero-vs-abp.md"
},
{

2
docs/en/framework/api-development/index.md

@ -1,4 +1,4 @@
## API Development
# API Development
The following documents explain the concepts, infrastructure and integrations to build HTTP APIs with ABP:

2
docs/en/framework/api-development/standard-apis/index.md

@ -1,4 +1,4 @@
## Standard HTTP API Endpoints
# Standard HTTP API Endpoints
ABP provides some pre-built and standard endpoints that contains useful information about the application or service, such as localization entries and settings. Here, is the list of the pre-built endpoints:

2
docs/en/framework/architecture/best-practices/application-layer-overview.md

@ -1,4 +1,4 @@
## Best Practices: Application Layer
# Best Practices: Application Layer
The following documents suggest some best-practices that you can use while implementing the application layer of your solution by following the Domain Driven Design principles:

2
docs/en/framework/architecture/best-practices/data-access-overview.md

@ -1,4 +1,4 @@
## Best Practices: Data Access
# Best Practices: Data Access
The following documents suggest some best-practices that you can use while implementing the database integration layer of your solution by following Domain Driven Design principles:

2
docs/en/framework/architecture/best-practices/domain-layer-overview.md

@ -1,4 +1,4 @@
## Best Practices: Domain Layer
# Best Practices: Domain Layer
The following documents suggest some best-practices that you can use while implementing the domain layer of your solution by following the Domain Driven Design principles:

2
docs/en/framework/architecture/domain-driven-design/application-layer.md

@ -1,4 +1,4 @@
## Domain Driven Design: Application Layer
# Domain Driven Design: Application Layer
Application Layer mediates between the Presentation and Domain Layers. Orchestrates business objects to perform specific application tasks and implements use cases as the application logic. Here, is the list of the building blocks of the Application Layer:

2
docs/en/framework/architecture/domain-driven-design/domain-layer.md

@ -1,4 +1,4 @@
## Domain Driven Design: Domain Layer
# Domain Driven Design: Domain Layer
Domain Layer contains business objects and the core (domain) business rules of the application and it's the heart of the application. Here, is the list of building blocks of the Domain Layer:

11
docs/en/framework/architecture/multi-tenancy/index.md

@ -40,7 +40,7 @@ ABP supports all the following approaches to store the tenant data in the databa
- **Database per Tenant**: Every tenant has a separate, dedicated database to store the data related to that tenant.
- **Hybrid**: Some tenants share a single database while some tenants may have their own databases.
[Tenant management module](../../../modules/tenant-management.md) (which comes pre-installed with the startup projects) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches.
[Saas module (PRO)](../../../modules/saas.md) allows you to set a connection string for any tenant (as optional), so you can achieve any of the approaches.
## Usage
@ -400,7 +400,7 @@ app.UseMultiTenancy();
#### Tenant Management Module
The [tenant management module](../../../modules/tenant-management.md) is **included in the startup templates** and implements the `ITenantStore` interface to get the tenants and their configuration from a database. It also provides the necessary functionality and UI to manage the tenants and their connection strings.
The [tenant management module](../../../modules/tenant-management.md) is **included in the startup templates** and implements the `ITenantStore` interface to get the tenants and their configuration from a database. It also provides the necessary functionality and UI to manage the tenants.
#### Configuration Data Store
@ -438,7 +438,12 @@ BLOB Storing, Caching, Data Filtering, Data Seeding, Authorization and all the o
ABP provides all the the infrastructure to create a multi-tenant application, but doesn't make any assumption about how you manage (create, delete...) your tenants.
The [Tenant Management module](../../../modules/tenant-management.md) provides a basic UI to manage your tenants and set their connection strings. It is pre-configured for the [application startup template](../../../solution-templates/layered-web-application).
The [Tenant Management module](../../../modules/tenant-management.md) provides a basic UI to manage your tenants. It is pre-configured for the [application startup template](../../../solution-templates/layered-web-application).
### A note about separate database per tenant approach in open source version
While ABP fully supports this option, managing connection strings of tenants from the UI is not available in open source version. You need to have [Saas module (PRO)](../../../modules/saas.md).
Alternatively you can implement this feature yourself by customizing the tenant management module and tenant application service to create and migrate the database on the fly.
## See Also

2
docs/en/framework/fundamentals/index.md

@ -1,4 +1,4 @@
## ABP Fundamentals: Overview
# ABP Fundamentals: Overview
The following documents explains the fundamental building blocks to create ABP solutions:

2
docs/en/framework/infrastructure/index.md

@ -1,4 +1,4 @@
## ABP Infrastructure
# ABP Infrastructure
ABP provides a complete infrastructure for creating real world software solutions with modern architectures based on the .NET platform. Each of the following documents explains an infrastructure feature:

143
docs/en/framework/ui/angular/entity-filters.md

@ -0,0 +1,143 @@
# Entity Filters
Every CRUD page includes some sort of inputs to filter the listed data. Some of the inputs are common among all of the entities like the `Search` box. In addition, every entity has its own advanced filters depending on its fields. To reduce the amount of code written on every CRUD page, the Angular UI of ABP Commercial introduces a new type of component called `abp-advanced-entity-filters`
## Setup
The components are in the _@volo/abp.commercial.ng.ui_ package, which is included in the ABP templates. So, as long as your project is a product of these templates and unless you delete the package, you have access to the entity filter components.
You can either import the `CommercialUiModule` which contains other components as well as `AdvancedEntityFilters` or you can directly import the `AdvancedEntityFiltersModule` if you do not need other components. Here is how you import them in your Angular module:
```javascript
import {
CommercialUiModule,
AdvancedEntityFiltersModule,
} from "@volo/abp.commercial.ng.ui";
@NgModule({
imports: [
// other imports
CommercialUiModule,
// OR
AdvancedEntityFiltersModule,
],
// rest of the module metadata
})
export class YourModule {}
```
## Usage
Let's take a look at the `Users` page from the `Identity` module.
![ABP Angular UI Users Page with Advanced Entity Filters](./images/angular-advanced-entity-filters.png)
As shown in the screenshot, `abp-advanced-entity-filters` usually contain two parts, an entity filter (common among entities), i.e. `abp-entity-filter`, and entity-specific filters which are encapsulated within the `abp-advanced-entity-filters-form` component.
`users.component.html`
```html
<abp-advanced-entity-filters [list]="list" localizationSourceName="AbpIdentity">
<abp-advanced-entity-filters-form>
<form #filterForm (keyup.enter)="list.get()">
<div class="row">
<!-- Form elements are omitted -->
<div class="col-12 col-sm-auto align-self-end mb-3">
<div class="row">
<div class="col-6 col-sm-auto d-grid">
<button
type="button"
class="btn btn-outline-primary"
(click)="clearFilters()"
>
<span>{%{{{ 'AbpUi::Clear' | abpLocalization }}}%}</span>
</button>
</div>
<div class="col-6 col-sm-auto d-grid">
<button
type="button"
class="btn btn-primary"
(click)="list.get()"
>
<span>{%{{{ 'AbpUi::Refresh' | abpLocalization }}}%}</span>
</button>
</div>
</div>
</div>
</div>
</form>
</abp-advanced-entity-filters-form>
</abp-advanced-entity-filters>
```
The `abp-advanced-entity-filters` already contains the `abp-entity-filter` component so you do not need to pass it. However, the `abp-entity-filter` component needs an instance of `ListService` which is usually stored in the `list` field of the page. You can also change the placeholder of the component via `entityFilterPlaceholder` input which is passed into the `abpLocalization` pipe so that it uses the translated text. Default is `'AbpUi::PagerSearch'`
E.g
```html
<abp-advanced-entity-filters
[list]="list"
entityFilterPlaceholder="AbpUi::PagerSearch"
>
<!-- ... -->
</abp-advanced-entity-filters>
```
### Inputs
- `list`: an instance of `ListService`
- `entityFilterPlaceholder`: the placeholder of `abp-entity-filter` component. Default: `'AbpUi::PagerSearch'`
- `localizationSourceName`: the localization source of the current page. E.g: `AbpIdentity`
### Inner components
Some entities are simple and do not require any filter other than the `abp-entity-filter`. In this case, you can simply use the `abp-advanced-entity-filters` without anything in between.
E.g.
Let's remove `form` from the `Users` page
```html
<abp-advanced-entity-filters [list]="list" localizationSourceName="AbpIdentity">
</abp-advanced-entity-filters>
```
![ABP Angular UI Users Page with Advanced Entity Filters without form](./images/angular-advanced-entity-filters-without-form.png)
If your component needs other filters, you can pass your own `form` within the `abp-advanced-entity-filters-form` component. This will render your form as well as a toggle (`abp-advanced-entity-filters-toggle`) to show and hide the form
E.g.
```html
<abp-advanced-entity-filters [list]="list" localizationSourceName="AbpIdentity">
<abp-advanced-entity-filters-form>
<form>
<!-- Content is omitted for sake of simplicity -->
</form>
</abp-advanced-entity-filters-form>
</abp-advanced-entity-filters>
```
![ABP Angular UI Users Page with Advanced Entity Filters with form](./images/angular-advanced-entity-filters-with-form.png)
Last but not least, if you need to render some content above the `abp-entity-filter` component, you can use the `abp-advanced-entity-filters-above-search`.
E.g.
```html
<abp-advanced-entity-filters [list]="list" localizationSourceName="AbpIdentity">
<abp-advanced-entity-filters-above-search>
<h3>Custom Content above entity-filter</h3>
</abp-advanced-entity-filters-above-search>
<abp-advanced-entity-filters-form>
<form>
<!-- Content is omitted for sake of simplicity -->
</form>
</abp-advanced-entity-filters-form>
</abp-advanced-entity-filters>
```
![ABP Angular UI Users Page with Advanced Entity Filters with custom content above filter](./images/angular-advanced-entity-filters-with-custom-content-above-filter.png)

16
docs/en/framework/ui/angular/environment.md

@ -101,6 +101,22 @@ export interface RemoteEnv {
- `method`: HTTP method to be used when retrieving environment config. Default: `GET`
- `headers`: If extra headers are needed for the request, it can be set through this field.
## Provide Environment Variable to Core Module
`environment` variable comes from angular host application.
```js
import { environment } from '../environments/environment';
@NgModule({
imports: [
//...other imports
CoreModule.forRoot({
environment
}),
]
})
```
## EnvironmentService
` EnvironmentService` is a singleton service, i.e. provided in root level of your application, and keeps the environment in the internal store.

BIN
docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-custom-content-above-filter.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/en/framework/ui/angular/images/angular-advanced-entity-filters-without-form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
docs/en/framework/ui/angular/images/angular-advanced-entity-filters.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/en/framework/ui/angular/images/angular-lookup-select.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
docs/en/framework/ui/angular/images/angular-lookup-typeahead.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

BIN
docs/en/framework/ui/angular/images/grouped-menu-mobile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/framework/ui/angular/images/grouped-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/framework/ui/angular/images/manage-profile-page-new-tab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/en/framework/ui/angular/images/manage-profile-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

98
docs/en/framework/ui/angular/lookup-components.md

@ -0,0 +1,98 @@
# Lookup Components
The Angular UI of ABP Commercial introduces some components with `abp-lookup-...` selector prefix. These components are used for retrieving relational entity data.
## Setup
The components are in the _@volo/abp.commercial.ng.ui_ package, which is included in the ABP templates. So, as long as your project is a product of these templates and unless you delete the package, you have access to the lookup components. Here is how you import them in your Angular module:
```javascript
import { CommercialUiModule } from '@volo/abp.commercial.ng.ui';
@NgModule({
imports: [
// other imports
CommercialUiModule,
],
// rest of the module metadata
})
export class YourModule {}
```
Now you can use the lookup components in your components declared by this module.
## Lookup HTTP Requests
The lookup requests are used by all lookup components to get the related entity records. Because of lexical this, _they must be arrow functions_.
```javascript
@Injectable({
providedIn: 'root'
})
export class AuthorService {
getCountryLookup = (input: LookupRequestDto) =>
this.restService.request<any, PagedResultDto<LookupDto<string>>>({
method: 'GET',
url: '/api/app/authors/country-lookup',
params: { filter: input.filter, skipCount: input.skipCount, maxResultCount: input.maxResultCount },
},
{ apiName: this.apiName });
// rest of the service is removed for brevity
}
```
## Lookup Typeahead Component
Typeahead is a good choice when you have an unknown number of records for the related entity or you want to improve the UX with a search ability. Although not the best scenario, the country picker below shows how the lookup typeahead works:
![ABP Angular UI Typeahead Lookup](./images/angular-lookup-typeahead.gif)
Here is how it is used in the template.
```html
<abp-lookup-typeahead
cid="author-country-id"
formControlName="countryId"
displayNameProp="name"
[editingData]="selected?.country"
[getFn]="service.getCountryLookup"
></abp-lookup-typeahead>
```
The available properties are as follows:
- **cid:** The id of the form control (e.g. an input or a select element) inside the lookup component. Lets form controls respond to `<label>` events.
- **editingData:** The related entity data if a record is being updated.
- **displayNameProp:** The property of the updated record to use as a display name in the form control.
- **lookupNameProp:** The property of the entity to use as a display name in options. Should macth the lookup HTTP request interface. _(default: displayName)_
- **lookupIdProp:** The property of the entity to use as the unique key in options. Should macth the lookup HTTP request interface. _(default: id)_
- **maxResultCount:** The maximum number of options to display. _(default: 10)_
- **getFn:** A function to get the related entity records with HTTP requests. Because of lexical this, _it must be a an arrow function_.
- **disabled:** This property lets you disable/enable a lookup component. _(default: false)_.
## Lookup Select Component
Select is a good choice when you have a low (and usually fixed) number of records for the related entity and search is not necessary. The country picker below shows how the lookup select works:
![ABP Angular UI Select Lookup](./images/angular-lookup-select.gif)
Here is how it is used in the template.
```html
<abp-lookup-select
cid="author-country-id"
formControlName="countryId"
displayNameProp="name"
[getFn]="service.getCountryLookup"
></abp-lookup-select>
```
The available properties are as follows:
- **cid:** The id of the form control (e.g. an input or a select element) inside the lookup component. Lets form controls respond to `<label>` events.
- **displayNameProp:** The property of the updated record to use as a display name in the form control.
- **lookupNameProp:** The property of the entity to use as a display name in options. Should macth the lookup HTTP request interface. _(default: displayName)_
- **lookupIdProp:** The property of the entity to use as the unique key in options. Should macth the lookup HTTP request interface. _(default: id)_
- **getFn:** A function to get the related entity records with HTTP requests. Because of lexical this, _it must be a an arrow function_.
- **disabled:** This property lets you disable/enable a lookup component. _(default: false)_.

77
docs/en/framework/ui/angular/manage-profile-page-tabs.md

@ -0,0 +1,77 @@
# Manage Profile Page Tabs
![manage profile page](./images/manage-profile-page.png)
The tabs in the manage profile page can be managed via `ManageProfileTabsService` which is exposed by the `@volo/abp.ng.account/public/config` package. You can add, remove, or edit a tab with using this service.
See the example below, covers all features:
```ts
// manage-profile-tabs.provider.ts
import { APP_INITIALIZER, Component } from "@angular/core";
import { TwoFactorTabComponent } from "@volo/abp.ng.account/public";
import {
eAccountManageProfileTabNames,
ManageProfileTabsService,
} from "@volo/abp.ng.account/public/config";
import { MyAwesomeTabComponent } from "./my-awesome-tab/my-awesome-tab.component";
@Component({
standalone: true,
selector: "abp-my-awesome-tab",
template: `My Awesome Tab`,
})
class MyAwesomeTabComponent {}
export const MANAGE_PROFILE_TAB_PROVIDER = {
provide: APP_INITIALIZER,
useFactory: configureManageProfileTabs,
deps: [ManageProfileTabsService],
multi: true,
};
export function configureManageProfileTabs(tabs: ManageProfileTabsService) {
return () => {
tabs.add([
{
name: "::MyAwesomeTab", // supports localization keys
order: 5,
component: MyAwesomeTabComponent,
},
]);
tabs.patch(eAccountManageProfileTabNames.TwoFactor, {
name: "Two factor authentication",
component: TwoFactorTabComponent,
});
tabs.remove([eAccountManageProfileTabNames.ProfilePicture]);
};
}
```
```ts
//app.module.ts
import { MANAGE_PROFILE_TAB_PROVIDER } from "./manage-profile-tabs.provider";
@NgModule({
providers: [MANAGE_PROFILE_TAB_PROVIDER],
})
export class AppModule {}
```
What we have done above;
- Created the `manage-profile-page-tabs.provider.ts`.
- Determined the `configureManageProfileTabs` function to perform manage profile tabs actions.
- Added a new tab labeled "My awesome tab".
- Renamed the "Two factor" tab label.
- Removed the "Profile picture" tab.
- Determined the `MANAGE_PROFILE_TAB_PROVIDER` to be able to run the `configureManageProfileTabs` function on initialization.
- Registered the `MANAGE_PROFILE_TAB_PROVIDER` to the `AppModule` providers.
See the result:
![my awesome tab](./images/manage-profile-page-new-tab.png)

13
docs/en/framework/ui/angular/modifying-the-menu.md

@ -344,3 +344,16 @@ export class AppComponent {
* Patched the languages dropdown element with new `requiredPolicy` and new `order`.
* Removed the current user dropdown element.
## Grouped Menu (Pro)
**This feature is only applied for [LeptonX](https://abp.io/docs/latest/themes/lepton-x/angular) theme**
### Web
![groupped-menu](./images/grouped-menu.png)
### Mobile
![groupped-menu-mobile](./images/grouped-menu-mobile.png)

14
docs/en/framework/ui/angular/overview.md

@ -0,0 +1,14 @@
# Angular UI
[Angular](https://angular.dev/) is a framework for building interactive, client-side web UIs using [TypeScript](https://www.typescriptlang.org) and [NodeJS](https://nodejs.org).
ABP Angular provides the infrastructure to communicate with the ABP backend and offers utilities to simplify frontend development. We’ll explore and dive into the details under the following main topics:
- Development
- Core Functionality
- Utilities
- Customization
- Components
You can also make a [Quick Start](./quick-start.md)

2
docs/en/framework/ui/angular/quick-start.md

@ -1,5 +1,7 @@
# ABP Angular Quick Start
**In this version ABP uses Angular [17.3.x](https://github.com/angular/angular/tree/17.3.x) version. You don't have to install Angular CLI globally**
## How to Prepare Development Environment
Please follow the steps below to prepare your development environment for Angular.

2
docs/en/framework/ui/blazor/components/submit-button.md

@ -1,4 +1,4 @@
# Blazor UI: SubmitButton component
# Blazor UI: SubmitButton Component
`SubmitButton` is a simple wrapper around `Button` component. It is used to be placed inside of page Form or Modal dialogs where it can response to user actions and to be activated as a default button by pressing an ENTER key. Once clicked it will go into the `disabled` state and also it will show a small loading indicator until clicked event is finished.

2
docs/en/framework/ui/index.md

@ -1,4 +1,4 @@
## ABP UI Options
# ABP UI Options
ABP provides several options for building the user interface (UI) in your applications. Here are some of the officially supported UI options you can use with ABP:

BIN
docs/en/images/abp-generate-razor-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
docs/en/images/abp-overall-diagram-1600.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

BIN
docs/en/images/abp-overall-diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
docs/en/images/linkedaccountloginmulti-tenancy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
docs/en/images/linkedaccountmodalactionindirectlink.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/en/images/linkedaccountmodalactionmulti-tenancy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/images/suite-add-existing-solution-8.3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

10
docs/en/index.md

@ -29,10 +29,20 @@ ABP can work with any database provider, while the following providers are suppo
<img width="340" src="images/db-options.png" alt="ABP Database Providers" />
## The ABP Platform
ABP **bridges the gap** between ASP.NET Core and **real-world business application requirements**, allowing you to focus on your business code.
The following diagram contains the core components of the **ABP Platform** and shows how ABP sits between **ASP.NET Core** and **Your Application**:
![abp-overall-diagram-1600](images/abp-overall-diagram-1600.png)
## Exploring the Documentation
ABP has a **comprehensive documentation** that not only explains ABP, but also includes **guides** and **samples** to help you on creating a **maintainable solution** by introducing and discussing common **software development principle and best practices**.
The next sections highlight important ABP components and provides links to related documents.
### The Framework
#### Architecture

20
docs/en/modules/account/linkedaccounts.md

@ -28,3 +28,23 @@ Enter the username and password of the account you want to link and click the `L
You can login with linked accounts or delete it from the `Linked accounts` modal.
![linkedaccountmodalaction](../../images/linkedaccountmodalaction.png)
## Multi-tenancy
Linked accounts can be used in multi-tenancy. You can link accounts from different tenants.
Enter the tenant name to link the account from another tenant's account:
![linkedaccountloginmulti-tenancy](../../images/linkedaccountloginmulti-tenancy.png)
You can see and login with linked accounts from different tenants:
![linkedaccountmodalactionmulti-tenancy](../../images/linkedaccountmodalactionmulti-tenancy.png)
## Indirect link
You can link accounts indirectly. For example, if account A is linked to account B and account B is linked to account C, account A is indirectly linked to account C.
You can see the indirect link in the `Linked accounts` modal:
![linkedaccountmodalactionindirectlink](../../images/linkedaccountmodalactionindirectlink.png)

2
docs/en/modules/cms-kit-pro/index.md

@ -1,4 +1,4 @@
# CMS Kit Pro Module (Pro)
# CMS Kit Module (Pro)
> You must have an ABP Team or a higher license to use this module.

2
docs/en/modules/openiddict.md

@ -1,4 +1,4 @@
## ABP OpenIddict Module
# ABP OpenIddict Module
OpenIddict module provides an integration with the [OpenIddict](https://github.com/openiddict/openiddict-core) which provides advanced authentication features like single sign-on, single log-out, and API access control. This module persists applications, scopes, and other OpenIddict-related objects to the database.

2
docs/en/release-info/index.md

@ -1,4 +1,4 @@
## Release Information
# Release Information
* [Release Notes](./release-notes.md)
* [Migration Guides](./migration-guides/index.md)

2
docs/en/solution-templates/application-module/index.md

@ -1,4 +1,4 @@
# Module Startup Template
# Module Solution Template
This template can be used to create a **reusable [application module](../../modules)** based on the [module development best practices & conventions](../../framework/architecture/best-practices). It is also suitable for creating **microservices** (with or without UI).

4
docs/en/solution-templates/layered-web-application/index.md

@ -1,4 +1,4 @@
# Application Startup Template
# Layered Application Solution Template
## Introduction
@ -6,7 +6,7 @@ This template provides a layered application structure based on the [Domain Driv
This document explains **the solution structure** and projects in details. If you want to start quickly, follow the guides below:
* [The getting started document](../../get-started/single-layer-web-application.md) explains how to create a new application in a few minutes.
* [The getting started document](../../get-started/layered-web-application.md) explains how to create a new application in a few minutes.
* [The application development tutorial](../../tutorials/book-store/part-01.md) explains step by step application development.
## How to Start With?

6
docs/en/solution-templates/single-layer-web-application/index.md

@ -1,12 +1,12 @@
# Application (Single Layer) Startup Template
# Single Layer Application Solution Template
## Introduction
This template provides a simple solution structure with a single project. This document explains that solution structure in details.
### The Difference Between the Application Startup Templates
### The Difference Between the Application Solution Templates
ABP's [Application Startup Template](../layered-web-application) provides a well-organized and layered solution to create maintainable business applications based on the [Domain Driven Design](../../framework/architecture/domain-driven-design) (DDD) practices. However, some developers find this template a little bit complex for simple and short-term applications. The single-layer application template has been created to provide a simpler development model for such applications. This template has the same functionality, features and modules on runtime with the [Application Startup Template](../layered-web-application) but the development model is minimal and everything is in a single project (`.csproj`).
ABP's [Layered Application Solution Template](../layered-web-application) provides a well-organized and layered solution to create maintainable business applications based on the [Domain Driven Design](../../framework/architecture/domain-driven-design) (DDD) practices. However, some developers find this template a little bit complex for simple and short-term applications. The single-layer application template has been created to provide a simpler development model for such applications. This template has the same functionality, features and modules on runtime with the [Layered Application Solution Template](../layered-web-application) but the development model is minimal and everything is in a single project (`.csproj`).
## How to Start with It?

8
docs/en/studio/release-notes.md

@ -2,6 +2,14 @@
This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version.
## 0.8.4 (2024-10-07)
* Fixed the ABP Suite does not open problem for macOS
* Made improvements on the new microservice creation
* Allowed using browser shortcuts (copy, paste, new tab etc.) for macOS
* Prevented application being crashed on solution runner exceptions
* Included `WebApp.Client` project styles in the main application to enable CSS in Isolation
## 0.8.3 (2024-09-24)
* Allowed to set Execution Order (or dependency) from Solution Runner

1
docs/en/studio/version-compatibility.md

@ -4,6 +4,7 @@ This document provides an overview of the compatibility between various versions
| **ABP Studio Version** | **ABP Version** |
|------------------------|---------------------------|
| 0.8.4 | 8.3.1 |
| 0.8.1 to 0.8.3 | 8.3.0 |
| 0.8.0 | 8.2.3 |
| 0.7.8 - 0.7.9 | 8.2.2 |

4
docs/en/suite/add-solution.md

@ -14,10 +14,10 @@
}
````
ABP Suite requires an ABP solution to work on, that's why when you start it, you will see two actions: `Add an existing solution` and `Create a new solution`
ABP Suite requires an ABP solution to work on, that's why when you start it, you need an ABP Solution and it should be added in ABP Suite UI.
## Add an existing solution
Add your existing solution which was created from the [ABP Studio](../studio/index.md) or [ABP CLI](../cli/index.md). You have to enter your `YourProject.sln` file path. Also, it works if you enter the directory of the `YourProject.sln` when there's single solution inside.
![Add an existing solution](../images/suite-add-existing-solution-8.1.png)
![Add an existing solution](../images/suite-add-existing-solution-8.3.png)

54
docs/en/suite/create-solution.md

@ -16,56 +16,6 @@
## Create a new solution
Creates a new ABP solution. It's an alternative way of creating an ABP project rather than [ABP CLI](../cli/index.md#new). To create a new ABP Solution, click the **Create a new solution** button. It will open a new dialog for the options.
From ABP Suite v8.3, **create a new solution** option has been removed. Instead, it's suggessted to use [ABP Studio](../studio/index.md) or [ABP CLI](../cli/index.md) to create a new solution. After, creating an ABP Solution, then you can generate CRUD pages via ABP Suite as always.
![Create a new ABP Solution](../images/suite-create-a-new-solution.png)
You need to provide your *template type*, *project name*, *output folder* where the project will be created in, *UI Framework* as your front-end, *mobile* option (**React Native** or **none**), *database provider*, *database management system* (if the database provider is **Entity Framework Core**) and *connection string*.
Also, you can add a *public web site* project to your solution, separate tenant schema by choosing the `Separated tenant schema` option and create the solution as tiered with the *Tiered* option which separates `Web` and `HTTP API` projects.
![Create a new solution](../images/suite-new-solution.png)
- **Template type:** This is the startup template type of your solution. You can check the all available templates from [here](../solution-templates).
- **Project name:** This is the solution name and also the prefix for the namespace of your solution. In this example `Acme.BookStore` is the project name. The solution file will be named as `Acme.BookStore.sln`. And the namespaces of `c#` files will start with `Acme.BookStore.*`
- **Output folder:** This is the directory where the new project will be created. Suite automatically creates the output directory if not exists and places the project folder inside the output directory. See the below folder view for `Acme.BookStore` project.
![New Solution Directory](../images/suite-new-solution-directory.png)
- **Theme:** You can generate your project with one of the 3 themes: `LeptonX`, `Lepton`, and `Basic theme`.
- `LeptonX` is a new modern and stylish Bootstrap UI theme with different color options. It's is the newest theme and is the default.
- `Lepton` is a modern, mature, responsive UI theme with different style and color options.
- `Basic theme` is a minimalist UI theme with plain Bootstrap colors and styles. Ideal if you will build your own UI theme.
> If you choose **LeptonX**, another option named **Theme style** will show up, you can choose one out of 4 options: `System`, `Dim`, `Dark` or `Light`.
![suite-new-solution-leptonx-theme](../images/suite-new-solution-leptonx-theme.png)
- **UI framework:** There are 4 types of UI Frameworks: `MVC`, `Angular`, `Blazor WebAssembly`, `Blazor Server`. Suite works with all of these frameworks.
- If you choose `Angular` or `Blazor WebAssembly`, two checkbox options will appear at the bottom:
- `Separate Auth Server`: Separates the Auth Server application from the API host application. If not checked, the server-side will have a single endpoint.
- `Progressive web application`: Optional Progressive Web Application checkbox.
- **Mobile:** You can create the template with React Native and MAUI as a mobile framework or without any mobile application support.
- **Database Provider:** ABP supports 2 database providers: `Entity Framework Core` and `MongoDB`. `Entity Framework Core` supports a variety of database management systems like `MS SQL Server`, `Oracle`, `MySQL` or `PostgreSQL`. See the full list https://docs.microsoft.com/ef/core/providers/?tabs=dotnet-core-cli. On the other hand [MongoDB](https://www.mongodb.com/) is a document-oriented NoSQL database used for high volume data storage. If you have a requirement to work with relational database systems, choose `Entity Framework Core` otherwise choose `MongoDB`.
- **Database management system:** You can choose your **Database Management System (DBMS)** while creating a new solution. There are 6 types of database management system: `SqlServer` (default), `MySQL`, `SQLite`, `Oracle`, `Oracle-Devart` and `PostgreSQL`.
- **Connection string:** You can define the connection string for connecting to the database by filling this area.
- **Public web site:** Creates the solution with an additional public web site project.
- **Separated tenant schema:** Creates the solution with the separated tenant schema support. Also, it creates a separate DbContext & migration path for tenants to not include host-related tables in tenant databases.
- **Tiered:** Creates a tiered solution where `Web` and `HTTP API ` layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. You can leave it unchecked, if you are not sure.
- **Preview:** Creates the solution in the latest preview version.
> ABP Studio has a shortcut for running [ABP Suite](./index.md) to allow using it without starting it externally and using it on a browser, which means you can create, manage, deploy your applications in a single desktop application and also generate CRUD pages via Suite as a pre-integrated application to ABP Studio.

2
docs/en/tools.md

@ -1,4 +1,4 @@
## Tools
# Tools
ABP provides a comprehensive set of tools designed to enhance the development experience and improve productivity:

3
docs/en/tutorials/book-store/part-09.md

@ -558,6 +558,7 @@ Open the `/src/app/route.provider.ts` and add `'BookStore.Books || BookStore.Aut
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Books || BookStore.Authors',
},
````
The final `configureRoutes` function declaration should be following:
@ -1240,4 +1241,4 @@ That's all! This is a fully working CRUD page, you can create, edit and delete t
> **Tip**: If you run the `.DbMigrator` console application after defining a new permission, it automatically grants these new permissions to the admin role and you don't need to manually grant the permissions yourself.
{{end}}
{{end}}

13
docs/en/tutorials/modular-crm/part-06.md

@ -185,15 +185,14 @@ public class OrderAppService : ApplicationService, IOrderAppService
.GetProductsByIdsAsync(productIds))
.ToDictionary(p => p.Id, p => p.Name);
var result = ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
result = result.Select(a =>
var orderDtos = ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
orderDtos.ForEach(orderDto =>
{
a.ProductName = products[a.ProductId];
return a;
})
.ToList();
orderDto.ProductName = products[orderDto.ProductId];
});
return result;
return orderDtos;
}
public async Task CreateAsync(OrderCreationDto input)

13
docs/en/tutorials/modular-crm/part-07.md

@ -101,15 +101,14 @@ public class OrderAppService : ApplicationService, IOrderAppService
.GetProductsByIdsAsync(productIds))
.ToDictionary(p => p.Id, p => p.Name);
var result = ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
result = result.Select(a =>
var orderDtos = ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
orderDtos.ForEach(orderDto =>
{
a.ProductName = products[a.ProductId];
return a;
})
.ToList();
orderDto.ProductName = products[orderDto.ProductId];
});
return result;
return orderDtos;
}
public async Task CreateAsync(OrderCreationDto input)

5
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo.Abp.AspNetCore.MultiTenancy.csproj

@ -23,4 +23,9 @@
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
</ItemGroup>
<ItemGroup>
<Content Remove="Volo\Abp\AspNetCore\MultiTenancy\Views\MultiTenancyMiddlewareErrorPage.cshtml" />
<None Include="Volo\Abp\AspNetCore\MultiTenancy\Views\MultiTenancyMiddlewareErrorPage.cshtml" />
</ItemGroup>
</Project>

15
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs

@ -16,6 +16,8 @@ using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Volo.Abp.AspNetCore.MultiTenancy.Views;
using Volo.Abp.AspNetCore.RazorViews;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
@ -68,7 +70,7 @@ public class AbpAspNetCoreMultiTenancyOptions
}
}
context.Response.Headers.Add("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
context.Response.Headers.Append("Abp-Tenant-Resolve-Error", HtmlEncoder.Default.Encode(exception.Message));
if (isCookieAuthentication && context.Request.Method.Equals("Get", StringComparison.OrdinalIgnoreCase) && !context.Request.IsAjax())
{
context.Response.Redirect(context.Request.GetEncodedUrl());
@ -133,18 +135,11 @@ public class AbpAspNetCoreMultiTenancyOptions
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.ContentType = "text/html";
var message = exception.Message;
var details = exception is BusinessException businessException ? businessException.Details : string.Empty;
await context.Response.WriteAsync($"<html lang=\"{HtmlEncoder.Default.Encode(CultureInfo.CurrentCulture.Name)}\"><body>\r\n");
await context.Response.WriteAsync($"<h3>{HtmlEncoder.Default.Encode(message)}</h3>{HtmlEncoder.Default.Encode(details!)}<br>\r\n");
await context.Response.WriteAsync("</body></html>\r\n");
// Note the 500 spaces are to work around an IE 'feature'
await context.Response.WriteAsync(new string(' ', 500));
var errorPage = new MultiTenancyMiddlewareErrorPage(new MultiTenancyMiddlewareErrorPageModel(message, details!));
await errorPage.ExecuteAsync(context);
}
return true;

96
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.Designer.cs

@ -0,0 +1,96 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Volo.Abp.AspNetCore.RazorViews
{
#line hidden
using System;
using System.Threading.Tasks;
#nullable restore
#line 1 "MultiTenancyMiddlewareErrorPage.cshtml"
using System.Globalization;
#line default
#line hidden
#nullable disable
#nullable restore
#line 2 "MultiTenancyMiddlewareErrorPage.cshtml"
using Volo.Abp.AspNetCore.MultiTenancy.Views;
#line default
#line hidden
#nullable disable
#nullable restore
#line 3 "MultiTenancyMiddlewareErrorPage.cshtml"
using Volo.Abp.AspNetCore.RazorViews;
#line default
#line hidden
#nullable disable
internal class MultiTenancyMiddlewareErrorPage : AbpCompilationRazorPageBase
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
#nullable restore
#line 5 "MultiTenancyMiddlewareErrorPage.cshtml"
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 404;
#line default
#line hidden
#nullable disable
WriteLiteral("\n");
WriteLiteral("\n<html");
BeginWriteAttribute("lang", " lang=\"", 453, "\"", 512, 1);
#nullable restore
#line 19 "MultiTenancyMiddlewareErrorPage.cshtml"
WriteAttributeValue("", 460, HtmlEncoder.Encode(CultureInfo.CurrentCulture.Name), 460, 52, false);
#line default
#line hidden
#nullable disable
EndWriteAttribute();
WriteLiteral(">\n <head>\n <meta charset=\"utf-8\" />\n <title>");
#nullable restore
#line 22 "MultiTenancyMiddlewareErrorPage.cshtml"
Write(HtmlEncoder.Encode(Model.Message));
#line default
#line hidden
#nullable disable
WriteLiteral("</title>\n </head>\n <body>\n <h3>");
#nullable restore
#line 25 "MultiTenancyMiddlewareErrorPage.cshtml"
Write(HtmlEncoder.Encode(Model.Message));
#line default
#line hidden
#nullable disable
WriteLiteral("</h3>\n <p>");
#nullable restore
#line 26 "MultiTenancyMiddlewareErrorPage.cshtml"
Write(HtmlEncoder.Encode(Model.Details));
#line default
#line hidden
#nullable disable
WriteLiteral("<p/>\n </body>\n</html>\n");
}
#pragma warning restore 1998
#nullable restore
#line 10 "MultiTenancyMiddlewareErrorPage.cshtml"
public MultiTenancyMiddlewareErrorPage(MultiTenancyMiddlewareErrorPageModel model)
{
Model = model;
}
public MultiTenancyMiddlewareErrorPageModel Model { get; set; }
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591

28
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPage.cshtml

@ -0,0 +1,28 @@
@using System.Globalization
@using Volo.Abp.AspNetCore.MultiTenancy.Views
@using Volo.Abp.AspNetCore.RazorViews
@inherits AbpCompilationRazorPageBase
@{
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 404;
}
@functions{
public MultiTenancyMiddlewareErrorPage(MultiTenancyMiddlewareErrorPageModel model)
{
Model = model;
}
public MultiTenancyMiddlewareErrorPageModel Model { get; set; }
}
<html lang="@HtmlEncoder.Encode(CultureInfo.CurrentCulture.Name)">
<head>
<meta charset="utf-8" />
<title>@HtmlEncoder.Encode(Model.Message)</title>
</head>
<body>
<h3>@HtmlEncoder.Encode(Model.Message)</h3>
<p>@HtmlEncoder.Encode(Model.Details)<p/>
</body>
</html>

14
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/Views/MultiTenancyMiddlewareErrorPageModel.cs

@ -0,0 +1,14 @@
namespace Volo.Abp.AspNetCore.MultiTenancy.Views;
public class MultiTenancyMiddlewareErrorPageModel
{
public string Message { get; set; }
public string Details { get; set; }
public MultiTenancyMiddlewareErrorPageModel(string message, string details)
{
Message = message;
Details = details;
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -35,4 +35,9 @@
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" />
</ItemGroup>
<ItemGroup>
<Content Remove="Volo\Abp\AspNetCore\Mvc\Libs\AbpMvcLibsErrorPage.cshtml" />
<None Include="Volo\Abp\AspNetCore\Mvc\Libs\AbpMvcLibsErrorPage.cshtml" />
</ItemGroup>
</Project>

50
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs

@ -0,0 +1,50 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Volo.Abp.AspNetCore.RazorViews
{
#line hidden
using System;
using System.Threading.Tasks;
#nullable restore
#line 1 "AbpMvcLibsErrorPage.cshtml"
using Volo.Abp.AspNetCore.RazorViews;
#line default
#line hidden
#nullable disable
internal class AbpMvcLibsErrorPage : AbpCompilationRazorPageBase
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
#nullable restore
#line 3 "AbpMvcLibsErrorPage.cshtml"
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 500;
#line default
#line hidden
#nullable disable
WriteLiteral(@"
<html>
<head>
<meta charset=""utf-8"" />
<title>Error - The Libs folder is missing!</title>
</head>
<body>
<h1> &#9888;&#65039; The Libs folder under the <code style=""background-color: #e7e7e7;"">wwwroot/libs</code> directory is empty!</h1>
<p>The Libs folder contains mandatory NPM Packages for running the project.</p>
<p>Make sure you run the <code style=""background-color: #e7e7e7;"">abp install-libs</code> CLI tool command.</p>
<p>For more information, check out the <a href=""https://abp.io/docs/latest/CLI#install-libs"">ABP CLI documentation</a></p>
</body>
</html>
");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591

22
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml

@ -0,0 +1,22 @@
@using Volo.Abp.AspNetCore.RazorViews
@inherits AbpCompilationRazorPageBase
@{
Response.ContentType = "text/html; charset=utf-8";
Response.StatusCode = 500;
}
<html>
<head>
<meta charset="utf-8" />
<title>Error - The Libs folder is missing!</title>
</head>
<body>
<h1> &#9888;&#65039; The Libs folder under the <code style="background-color: #e7e7e7;">wwwroot/libs</code> directory is empty!</h1>
<p>The Libs folder contains mandatory NPM Packages for running the project.</p>
<p>Make sure you run the <code style="background-color: #e7e7e7;">abp install-libs</code> CLI tool command.</p>
<p>For more information, check out the <a href="https://abp.io/docs/latest/CLI#install-libs">ABP CLI documentation</a></p>
</body>
</html>

19
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.AspNetCore.RazorViews;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.Libs;
@ -35,22 +36,8 @@ public class AbpMvcLibsService : IAbpMvcLibsService, ITransientDependency
{
if (!await CheckLibsAsyncOnceAsync(httpContext))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
httpContext.Response.ContentType = "text/html";
await httpContext.Response.WriteAsync(
"<html>" +
" <head>" +
" <title>Error - The Libs folder is missing!</title>" +
" </head>" +
" <body>" +
" <h1> &#9888;&#65039; The Libs folder under the <code style='background-color: #e7e7e7;'>wwwroot/libs</code> directory is empty!</h1>" +
" <p>The Libs folder contains mandatory NPM Packages for running the project.</p>" +
" <p>Make sure you run the <code style='background-color: #e7e7e7;'>abp install-libs</code> CLI tool command.</p>" +
" <p>For more information, check out the <a href='https://abp.io/docs/latest/CLI#install-libs'>ABP CLI documentation</a></p>" +
" </body>" +
"</html>",
Encoding.UTF8
);
var errorPage = new AbpMvcLibsErrorPage();
await errorPage.ExecuteAsync(httpContext);
return;
}

283
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AbpCompilationRazorPageBase.cs

@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Volo.Abp.AspNetCore.RazorViews;
public abstract class AbpCompilationRazorPageBase
{
private readonly static Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private readonly static char[] NewLineChars = new[] { '\r', '\n' };
private readonly Stack<TextWriter> _textWriterStack = new Stack<TextWriter>();
/// <summary>
/// The request context
/// </summary>
protected HttpContext Context { get; private set; } = default!;
/// <summary>
/// The request
/// </summary>
protected HttpRequest Request { get; private set; } = default!;
/// <summary>
/// The response
/// </summary>
protected HttpResponse Response { get; private set; } = default!;
/// <summary>
/// The output stream
/// </summary>
protected TextWriter Output { get; private set; } = default!;
/// <summary>
/// Html encoder used to encode content.
/// </summary>
protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default;
/// <summary>
/// Url encoder used to encode content.
/// </summary>
protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default;
/// <summary>
/// JavaScript encoder used to encode content.
/// </summary>
protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default;
/// <summary>
/// Execute an individual request
/// </summary>
/// <param name="stream">The stream to write to</param>
public async Task ExecuteAsync(Stream stream)
{
// We technically don't need this intermediate buffer if this method accepts a memory stream.
var buffer = new MemoryStream();
Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true);
await ExecuteAsync();
await Output.FlushAsync();
await Output.DisposeAsync();
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(stream);
}
/// <summary>
/// Execute an individual request
/// </summary>
/// <param name="context"></param>
public async Task ExecuteAsync(HttpContext context)
{
Context = context;
Request = Context.Request;
Response = Context.Response;
var buffer = new MemoryStream();
Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true);
await ExecuteAsync();
await Output.FlushAsync();
await Output.DisposeAsync();
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(Response.Body);
}
/// <summary>
/// Execute an individual request
/// </summary>
public abstract Task ExecuteAsync();
protected virtual void PushWriter(TextWriter writer)
{
ArgumentNullException.ThrowIfNull(writer);
_textWriterStack.Push(Output);
Output = writer;
}
protected virtual TextWriter PopWriter()
{
Output = _textWriterStack.Pop();
return Output;
}
/// <summary>
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="object"/> to write.</param>
protected void WriteLiteral(object value)
{
WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture));
}
/// <summary>
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to write.</param>
protected void WriteLiteral(string? value)
{
if (!string.IsNullOrEmpty(value))
{
Output.Write(value);
}
}
private List<string>? AttributeValues { get; set; }
protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno)
{
if (AttributeValues == null)
{
AttributeValues = new List<string>();
}
AttributeValues.Add(value.ToString()!);
}
private string? AttributeEnding { get; set; }
protected void BeginWriteAttribute(string name, string beginning, int startPosition, string ending, int endPosition, int thingy)
{
Debug.Assert(string.IsNullOrEmpty(AttributeEnding));
Output.Write(beginning);
AttributeEnding = ending;
}
protected void EndWriteAttribute()
{
Debug.Assert(AttributeValues != null);
Debug.Assert(!string.IsNullOrEmpty(AttributeEnding));
var attributes = string.Join(" ", AttributeValues);
Output.Write(attributes);
AttributeValues = null;
Output.Write(AttributeEnding);
AttributeEnding = null;
}
/// <summary>
/// Writes the given attribute to the given writer
/// </summary>
/// <param name="name">The name of the attribute to write</param>
/// <param name="leader">The value of the prefix</param>
/// <param name="trailer">The value of the suffix</param>
/// <param name="values">The <see cref="AttributeValue"/>s to write.</param>
protected void WriteAttribute(
string name,
string leader,
string trailer,
params AttributeValue[] values)
{
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(leader);
ArgumentNullException.ThrowIfNull(trailer);
WriteLiteral(leader);
foreach (var value in values)
{
WriteLiteral(value.Prefix);
// The special cases here are that the value we're writing might already be a string, or that the
// value might be a bool. If the value is the bool 'true' we want to write the attribute name
// instead of the string 'true'. If the value is the bool 'false' we don't want to write anything.
// Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself.
string? stringValue;
if (value.Value is bool)
{
if ((bool)value.Value)
{
stringValue = name;
}
else
{
continue;
}
}
else
{
stringValue = value.Value as string;
}
// Call the WriteTo(string) overload when possible
if (value.Literal && stringValue != null)
{
WriteLiteral(stringValue);
}
else if (value.Literal)
{
WriteLiteral(value.Value);
}
else if (stringValue != null)
{
Write(stringValue);
}
else
{
Write(value.Value);
}
}
WriteLiteral(trailer);
}
/// <summary>
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked
/// </summary>
/// <param name="result">The <see cref="HelperResult"/> to invoke</param>
protected void Write(HelperResult result)
{
result.WriteTo(Output);
}
/// <summary>
/// Writes the specified <paramref name="value"/> to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="object"/> to write.</param>
/// <remarks>
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to
/// <see cref="Output"/>.
/// </remarks>
protected void Write(object value)
{
if (value is HelperResult helperResult)
{
helperResult.WriteTo(Output);
}
else
{
Write(Convert.ToString(value, CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to write.</param>
protected void Write(string? value)
{
if (!string.IsNullOrEmpty(value))
{
WriteLiteral(HtmlEncoder.Encode(value));
}
}
protected string HtmlEncodeAndReplaceLineBreaks(string input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
// Split on line breaks before passing it through the encoder.
return string.Join("<br />" + Environment.NewLine,
input.Split("\r\n", StringSplitOptions.None)
.SelectMany(s => s.Split(NewLineChars, StringSplitOptions.None))
.Select(HtmlEncoder.Encode));
}
}

34
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/AttributeValue.cs

@ -0,0 +1,34 @@
using System;
namespace Volo.Abp.AspNetCore.RazorViews;
public class AttributeValue
{
public AttributeValue(string prefix, object value, bool literal)
{
Prefix = prefix;
Value = value;
Literal = literal;
}
public string Prefix { get; }
public object Value { get; }
public bool Literal { get; }
public static AttributeValue FromTuple(Tuple<string, object, bool> value)
{
return new AttributeValue(value.Item1, value.Item2, value.Item3);
}
public static AttributeValue FromTuple(Tuple<string, string, bool> value)
{
return new AttributeValue(value.Item1, value.Item2, value.Item3);
}
public static implicit operator AttributeValue(Tuple<string, object, bool> value)
{
return FromTuple(value);
}
}

19
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/RazorViews/HelperResult.cs

@ -0,0 +1,19 @@
using System;
using System.IO;
namespace Volo.Abp.AspNetCore.RazorViews;
public class HelperResult
{
public HelperResult(Action<TextWriter> action)
{
WriteAction = action;
}
public Action<TextWriter> WriteAction { get; }
public void WriteTo(TextWriter writer)
{
WriteAction(writer);
}
}

78
framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs

@ -27,6 +27,10 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Autofac.Builder;
using Autofac.Core;
using Autofac.Core.Activators;
using Autofac.Core.Activators.Delegate;
using Autofac.Core.Activators.Reflection;
using Autofac.Core.Resolving.Pipeline;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
@ -115,12 +119,84 @@ public static class AutofacRegistration
.SingleInstance();
// Shims for keyed service compatibility.
builder.RegisterServiceMiddlewareSource(new KeyedServiceMiddlewareSource());
builder.RegisterSource<AnyKeyRegistrationSource>();
builder.ComponentRegistryBuilder.Registered += AddFromKeyedServiceParameterMiddleware;
Register(builder, services, lifetimeScopeTagForSingletons);
}
/// <summary>
/// Inspect each component registration, and determine whether or not we can avoid adding the
/// <see cref="FromKeyedServicesAttribute"/> parameter to the resolve pipeline.
/// </summary>
private static void AddFromKeyedServiceParameterMiddleware(object? sender, ComponentRegisteredEventArgs e)
{
var needFromKeyedServiceParameter = false;
// We can optimise quite significantly in the case where we are using the reflection activator.
// In that state we can inspect the constructors ahead of time and determine whether the parameter will even need to be added.
if (e.ComponentRegistration.Activator is ReflectionActivator reflectionActivator)
{
var constructors = reflectionActivator.ConstructorFinder.FindConstructors(reflectionActivator.LimitType);
// Go through all the constructors; if any have a FromKeyedServices, then we must add our component middleware to
// the pipeline.
foreach (var constructor in constructors)
{
foreach (var constructorParameter in constructor.GetParameters())
{
if (constructorParameter.GetCustomAttribute<FromKeyedServicesAttribute>() is not null)
{
// One or more of the constructors we will use to activate has a FromKeyedServicesAttribute,
// we must add our middleware.
needFromKeyedServiceParameter = true;
break;
}
}
if (needFromKeyedServiceParameter)
{
break;
}
}
}
else if (e.ComponentRegistration.Activator is DelegateActivator)
{
// For delegate activation there are very few paths that would let the FromKeyedServicesAttribute
// actually work, and none that MSDI supports directly.
// We're explicitly choosing here not to support [FromKeyedServices] on the Autofac-specific generic
// delegate resolve methods, to improve performance for the 99% case of other delegates that only
// receive an IComponentContext or an IServiceProvider.
needFromKeyedServiceParameter = false;
}
else if (e.ComponentRegistration.Activator is InstanceActivator)
{
// Instance activators don't use parameters.
needFromKeyedServiceParameter = false;
}
else
{
// Unknown activator, assume we need the parameter.
needFromKeyedServiceParameter = true;
}
e.ComponentRegistration.PipelineBuilding += (_, pipeline) =>
{
var keyedServiceMiddlewareType = typeof(AutofacServiceProvider).Assembly.GetType("Autofac.Extensions.DependencyInjection.KeyedServiceMiddleware");
var instanceWithFromKeyedServicesParameter = (IResolveMiddleware)keyedServiceMiddlewareType!.GetProperty("InstanceWithFromKeyedServicesParameter", BindingFlags.Public | BindingFlags.Static)!.GetValue(null, null)!;
var instanceWithoutFromKeyedServicesParameter = (IResolveMiddleware)keyedServiceMiddlewareType!.GetProperty("InstanceWithoutFromKeyedServicesParameter", BindingFlags.Public | BindingFlags.Static)!.GetValue(null, null)!;
if (needFromKeyedServiceParameter)
{
pipeline.Use(instanceWithFromKeyedServicesParameter, MiddlewareInsertionMode.StartOfPhase);
}
else
{
pipeline.Use(instanceWithoutFromKeyedServicesParameter, MiddlewareInsertionMode.StartOfPhase);
}
};
}
/// <summary>
/// Configures the exposed service type on a service registration.
/// </summary>

1
framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj

@ -24,6 +24,7 @@
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="StackExchange.Redis" />
<PackageReference Include="DeepL.net" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" />
</ItemGroup>
<ItemGroup>

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs

@ -70,6 +70,7 @@ public class AbpCliCoreModule : AbpModule
options.Commands[CliCommand.Name] = typeof(CliCommand);
options.Commands[ClearDownloadCacheCommand.Name] = typeof(ClearDownloadCacheCommand);
options.Commands[RecreateInitialMigrationCommand.Name] = typeof(RecreateInitialMigrationCommand);
options.Commands[GenerateRazorPage.Name] = typeof(GenerateRazorPage);
options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Pro");
options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Lite");

227
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateRazorPage.cs

@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands;
public class GenerateRazorPage : IConsoleCommand, ITransientDependency
{
public const string Name = "generate-razor-page";
public ILogger<GenerateRazorPage> Logger { get; set; }
public GenerateRazorPage()
{
Logger = NullLogger<GenerateRazorPage>.Instance;
}
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var targetProjectDirectory = Directory.GetCurrentDirectory();
var projectEngine = CreateProjectEngine(targetProjectDirectory);
var results = MainCore(projectEngine, targetProjectDirectory);
foreach (var result in results)
{
File.WriteAllText(result.FilePath, result.GeneratedCode);
}
Logger.LogInformation($"{results.Count} files successfully generated.");
return Task.CompletedTask;
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine("abp generate-razor-page");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://abp.io/docs/latest/cli");
return sb.ToString();
}
public string GetShortDescription()
{
return "Generates code files for Razor page.";
}
private RazorProjectEngine CreateProjectEngine(string targetProjectDirectory, Action<RazorProjectEngineBuilder>? configure = null)
{
var fileSystem = RazorProjectFileSystem.Create(targetProjectDirectory);
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
{
builder
.SetNamespace("Volo.Abp.AspNetCore.RazorViews")
.ConfigureClass((document, @class) =>
{
@class.ClassName = Path.GetFileNameWithoutExtension(document.Source.FilePath);
@class.Modifiers.Clear();
@class.Modifiers.Add("internal");
});
SectionDirective.Register(builder);
builder.Features.Add(new SuppressChecksumOptionsFeature());
builder.Features.Add(new SuppressMetadataAttributesFeature());
if (configure != null)
{
configure(builder);
}
builder.AddDefaultImports(@"
@using System
@using System.Threading.Tasks");
});
return projectEngine;
}
private List<RazorPageGeneratorResult> MainCore(RazorProjectEngine projectEngine, string targetProjectDirectory)
{
var results = new List<RazorPageGeneratorResult>();
Logger.LogInformation("Generating code files for pages in {0}", targetProjectDirectory);
var cshtmlFiles = projectEngine.FileSystem.EnumerateItems(targetProjectDirectory)
.Where(x => File.ReadAllText(x.PhysicalPath).Contains("@inherits AbpCompilationRazorPageBase"))
.ToList();
if (!cshtmlFiles.Any())
{
Logger.LogInformation("No .cshtml or .razor files were found.");
return results;
}
foreach (var item in cshtmlFiles)
{
Logger.LogInformation(" Generating code file for page {0} ...", item.FileName);
results.Add(GenerateCodeFile(projectEngine, item));
Logger.LogInformation(" Done!");
}
return results;
}
private RazorPageGeneratorResult GenerateCodeFile(RazorProjectEngine projectEngine, RazorProjectItem projectItem)
{
var projectItemWrapper = new FileSystemRazorProjectItemWrapper(Logger, projectItem);
var codeDocument = projectEngine.Process(projectItemWrapper);
var cSharpDocument = codeDocument.GetCSharpDocument();
if (cSharpDocument.Diagnostics.Any())
{
var diagnostics = string.Join(Environment.NewLine, cSharpDocument.Diagnostics);
Logger.LogInformation($"One or more parse errors encountered. This will not prevent the generator from continuing: {Environment.NewLine}{diagnostics}.");
}
var generatedCodeFilePath = Path.ChangeExtension(projectItem.PhysicalPath, ".Designer.cs");
return new RazorPageGeneratorResult
{
FilePath = generatedCodeFilePath,
GeneratedCode = cSharpDocument.GeneratedCode,
};
}
private class SuppressChecksumOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
Check.NotNull(options, nameof(options));
options.SuppressChecksum = true;
}
}
private class SuppressMetadataAttributesFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
Check.NotNull(options, nameof(options));
options.SuppressMetadataAttributes = true;
}
}
private class FileSystemRazorProjectItemWrapper : RazorProjectItem
{
private readonly ILogger<GenerateRazorPage> _logger;
private readonly RazorProjectItem _source;
public FileSystemRazorProjectItemWrapper(ILogger<GenerateRazorPage> logger, RazorProjectItem item)
{
_logger = logger;
_source = item;
// Mask the full name since we don't want a developer's local file paths to be committed.
PhysicalPath = $"{_source.FileName}";
}
public override string BasePath => _source.BasePath;
public override string FilePath => _source.FilePath;
public override string PhysicalPath { get; }
public override bool Exists => _source.Exists;
public override Stream Read()
{
var processedContent = ProcessFileIncludes();
return new MemoryStream(Encoding.UTF8.GetBytes(processedContent));
}
private string ProcessFileIncludes()
{
var basePath = Path.GetDirectoryName(_source.PhysicalPath);
var cshtmlContent = File.ReadAllText(_source.PhysicalPath);
var startMatch = "<%$ include: ";
var endMatch = " %>";
var startIndex = 0;
while (startIndex < cshtmlContent.Length)
{
startIndex = cshtmlContent.IndexOf(startMatch, startIndex, StringComparison.Ordinal);
if (startIndex == -1)
{
break;
}
var endIndex = cshtmlContent.IndexOf(endMatch, startIndex, StringComparison.Ordinal);
if (endIndex == -1)
{
throw new InvalidOperationException($"Invalid include file format in {_source.PhysicalPath}. Usage example: <%$ include: ErrorPage.js %>");
}
var includeFileName = cshtmlContent.Substring(startIndex + startMatch.Length, endIndex - (startIndex + startMatch.Length));
_logger.LogInformation(" Inlining file {0}", includeFileName);
var includeFileContent = File.ReadAllText(Path.Combine(basePath, includeFileName));
cshtmlContent = string.Concat(cshtmlContent.Substring(0, startIndex), includeFileContent, cshtmlContent.Substring(endIndex + endMatch.Length));
startIndex += includeFileContent.Length;
}
return cshtmlContent;
}
}
private class RazorPageGeneratorResult
{
public string FilePath { get; set; }
public string GeneratedCode { get; set; }
}
}

7
framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Minio;
using Minio.ApiEndpoints;
using Minio.DataModel.Args;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
@ -72,10 +73,8 @@ public class AbpBlobStoringMinioTestModule : AbpModule
var minioClient = new MinioClient().WithEndpoint(_endPoint).WithCredentials(_accessKey, _secretKey).Build();
if (await minioClient.BucketExistsAsync(new BucketExistsArgs().WithBucket(_randomContainerName)))
{
var objects = await minioClient.ListObjectsAsync(new ListObjectsArgs().WithBucket(_randomContainerName)
.WithPrefix(null).WithRecursive(true)).ToList();
foreach (var item in objects)
await foreach (var item in minioClient.ListObjectsEnumAsync(new ListObjectsArgs().WithBucket(_randomContainerName)
.WithPrefix(null).WithRecursive(true)).ConfigureAwait(false))
{
await minioClient.RemoveObjectAsync(new RemoveObjectArgs().WithBucket(_randomContainerName)
.WithObject(item.Key));

3
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json

@ -87,6 +87,7 @@
"Views": "views",
"Biography": "Biography",
"Social": "Social",
"NewBlogPost" : "New Blog Post"
"NewBlogPost" : "New Blog Post",
"BlogMemberMetaDescription" : "ABP Blog for .NET development, cross-platform, ASP.NET application templates, ABP-related news and more..."
}
}

7
modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor

@ -54,11 +54,10 @@
if (feature.ValueType is SelectionStringValueType)
{
var items = ((SelectionStringValueType)feature.ValueType).ItemSource.Items;
var selectedValue = SelectionStringValues[feature.Name];
<Field Style="@GetFeatureStyles(feature)">
<FieldLabel>@feature.DisplayName</FieldLabel>
<Select TValue="string"
@bind-SelectedValue="@SelectionStringValues[feature.Name]">
<Select TValue="string" SelectedValue="selectedValue" SelectedValueChanged="s => SelectionStringValues[feature.Name] = s">
@foreach (var item in items)
{
<SelectItem Value="@item.Value">
@ -102,4 +101,4 @@
}
</ModalContent>
</Modal>
</Modal>

7
npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts

@ -1,9 +1,7 @@
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { ConfigStateService, IAbpGuard } from '@abp/ng.core';
import { ConfigStateService, IAbpGuard, PermissionService } from '@abp/ng.core';
import {
ExtensionsService,
getObjectExtensionEntitiesFromStore,
@ -23,6 +21,7 @@ import { eAccountComponents } from '../enums/components';
@Injectable()
export class AccountExtensionsGuard implements IAbpGuard {
protected readonly configState = inject(ConfigStateService);
protected readonly permmission = inject(PermissionService);
protected readonly extensions = inject(ExtensionsService);
canActivate(): Observable<boolean> {
@ -34,7 +33,7 @@ export class AccountExtensionsGuard implements IAbpGuard {
map(entities => ({
[eAccountComponents.PersonalSettings]: entities.User,
})),
mapEntitiesToContributors(this.configState, 'AbpIdentity'),
mapEntitiesToContributors(this.configState, this.permmission, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultProps(
this.extensions.editFormProps,

7
npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts

@ -1,6 +1,7 @@
import { inject } from '@angular/core';
import { ConfigStateService } from '@abp/ng.core';
import { ResolveFn } from '@angular/router';
import { map, tap } from 'rxjs';
import { ConfigStateService, PermissionService } from '@abp/ng.core';
import {
ExtensionsService,
getObjectExtensionEntitiesFromStore,
@ -9,10 +10,10 @@ import {
} from '@abp/ng.components/extensible';
import { eAccountComponents } from '../enums';
import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, DEFAULT_ACCOUNT_FORM_PROPS } from '../tokens';
import { ResolveFn } from '@angular/router';
export const accountExtensionsResolver: ResolveFn<any> = () => {
const configState = inject(ConfigStateService);
const permission = inject(PermissionService);
const extensions = inject(ExtensionsService);
const config = { optional: true };
@ -23,7 +24,7 @@ export const accountExtensionsResolver: ResolveFn<any> = () => {
map(entities => ({
[eAccountComponents.PersonalSettings]: entities.User,
})),
mapEntitiesToContributors(configState, 'AbpIdentity'),
mapEntitiesToContributors(configState, permission, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultProps(
extensions.editFormProps,

17
npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts

@ -61,6 +61,23 @@ export interface ExtensionPropertyDto {
configuration: Record<string, any>;
defaultValue: any;
formText?: string;
policy?: Policy;
}
export interface BaseDefinition {
requiresAll: boolean;
}
export interface FeatureDefinition extends BaseDefinition {
features?: string[];
}
export interface PermissionDefinition extends BaseDefinition {
permissionNames?: string[];
}
export interface Policy {
globalFeatures: FeatureDefinition;
features: FeatureDefinition;
permissions: PermissionDefinition;
}
export interface ExtensionPropertyUiDto {

9
npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts

@ -24,7 +24,14 @@ export function generateFormFromProps<R = any>(data: PropData<R>) {
props.forEach(({ value: prop }) => {
const name = prop.name;
const isExtraProperty = prop.isExtra || name in extraProperties;
let value = isExtraProperty ? extraProperties[name] : name in record ? record[name] : undefined;
let value = undefined;
if (isExtraProperty) {
value = extraProperties[name];
} else if (name in record) {
value = record[name];
}
if (typeof value === 'undefined') value = prop.defaultValue;

48
npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts

@ -1,3 +1,4 @@
import { ConfigStateService, PermissionService } from '@abp/ng.core';
import { Observable, of } from 'rxjs';
import { EXTRA_PROPERTIES_KEY } from '../constants/extra-properties';
import {
@ -22,6 +23,8 @@ import {
PropList,
PropsFactory,
} from '../models/props';
import { Policy } from '../models/internal/object-extensions';
import { ObjectExtensions } from '../models/object-extensions';
export function createExtraPropertyValueResolver<T>(
name: string,
@ -47,6 +50,51 @@ export function mergeWithDefaultProps<F extends PropsFactory<any>>(
);
});
}
export function checkPolicies(
properties: ObjectExtensions.EntityExtensionProperties,
configState: ConfigStateService,
permissionService: PermissionService,
) {
const props = Object.entries(properties);
const checkPolicy = (policy: Policy): boolean => {
const { permissions, globalFeatures, features } = policy;
const checks = [
{
items: permissions?.permissionNames,
requiresAll: permissions?.requiresAll,
check: (item: string) => permissionService.getGrantedPolicy(item),
},
{
items: globalFeatures?.features,
requiresAll: globalFeatures?.requiresAll,
check: (item: string) => configState.getGlobalFeatureIsEnabled(item),
},
{
items: features?.features,
requiresAll: features?.requiresAll,
check: (item: string) => configState.getFeatureIsEnabled(item),
},
];
return checks.every(({ items, requiresAll, check }) => {
if (!items?.length) {
return true;
}
return requiresAll ? items.every(check) : items.some(check);
});
};
props.forEach(([name, property]) => {
if (property.policy && !checkPolicy(property.policy)) {
delete properties[name];
}
});
}
type InferredPropDefaults<F> =
F extends EntityPropsFactory<infer RE>
? EntityPropDefaults<RE>

35
npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts

@ -5,6 +5,7 @@ import {
ExtensionEnumDto,
ExtensionPropertyUiLookupDto,
ObjectExtensionsDto,
PermissionService,
} from '@abp/ng.core';
import { Observable, pipe, zip } from 'rxjs';
import { filter, map, switchMap, take } from 'rxjs/operators';
@ -15,7 +16,7 @@ import { ObjectExtensions } from '../models/object-extensions';
import { PropCallback } from '../models/props';
import { createEnum, createEnumOptions, createEnumValueResolver } from './enum.util';
import { createDisplayNameLocalizationPipeKeyGenerator } from './localization.util';
import { createExtraPropertyValueResolver } from './props.util';
import { checkPolicies, createExtraPropertyValueResolver } from './props.util';
import {
createTypeaheadDisplayNameGenerator,
createTypeaheadOptions,
@ -39,15 +40,18 @@ function selectEnums(
): Observable<Record<string, ExtensionEnumDto>> {
return selectObjectExtensions(configState).pipe(
map((extensions: ObjectExtensionsDto) =>
Object.keys(extensions.enums).reduce((acc, key) => {
const { fields, localizationResource } = extensions.enums[key];
acc[key] = {
fields,
localizationResource,
transformed: createEnum(fields),
};
return acc;
}, {} as Record<string, ObjectExtensions.ExtensionEnumDto>),
Object.keys(extensions.enums).reduce(
(acc, key) => {
const { fields, localizationResource } = extensions.enums[key];
acc[key] = {
fields,
localizationResource,
transformed: createEnum(fields),
};
return acc;
},
{} as Record<string, ObjectExtensions.ExtensionEnumDto>,
),
),
);
}
@ -71,6 +75,7 @@ export function getObjectExtensionEntitiesFromStore(
export function mapEntitiesToContributors<T = any>(
configState: ConfigStateService,
permissionService: PermissionService,
resource: string,
) {
return pipe(
@ -86,10 +91,16 @@ export function mapEntitiesToContributors<T = any>(
acc.editForm[key] = [];
const entity: ObjectExtensions.EntityExtensionDto = entities[key];
if (!entity) return acc;
if (!entity) {
return acc;
}
const properties = entity.properties;
if (!properties) return acc;
if (!properties) {
return acc;
}
checkPolicies(properties, configState, permissionService);
const mapPropertiesToContributors = createPropertiesToContributorsMapper<T>(
generateDisplayName,

35
npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts

@ -1,30 +1,30 @@
import {ConfigStateService} from '@abp/ng.core';
import {firstValueFrom, of} from 'rxjs';
import {take} from 'rxjs/operators';
import {ePropType} from '../lib/enums/props.enum';
import {EntityPropList} from '../lib/models/entity-props';
import {FormPropList} from '../lib/models/form-props';
import {ObjectExtensions} from '../lib/models/object-extensions';
import { ConfigStateService, PermissionService } from '@abp/ng.core';
import { firstValueFrom, lastValueFrom, of } from 'rxjs';
import { take } from 'rxjs/operators';
import { ePropType } from '../lib/enums/props.enum';
import { EntityPropList } from '../lib/models/entity-props';
import { FormPropList } from '../lib/models/form-props';
import { ObjectExtensions } from '../lib/models/object-extensions';
import {
getObjectExtensionEntitiesFromStore,
mapEntitiesToContributors,
} from '../lib/utils/state.util';
const fakeAppConfigService = {get: () => of(createMockState())} as any;
const fakeLocalizationService = {get: () => of(createMockState())} as any;
const fakeAppConfigService = { get: () => of(createMockState()) } as any;
const fakeLocalizationService = { get: () => of(createMockState()) } as any;
const configState = new ConfigStateService(fakeAppConfigService, fakeLocalizationService, false);
configState.refreshAppState();
const permissionService = new PermissionService(configState);
describe('State Utils', () => {
describe('#getObjectExtensionEntitiesFromStore', () => {
it('should return observable entities of an existing module', async () => {
const objectExtensionEntitiesFromStore$ = getObjectExtensionEntitiesFromStore(
configState,
'Identity',
)
);
const entities = await firstValueFrom(objectExtensionEntitiesFromStore$)
const entities = await firstValueFrom(objectExtensionEntitiesFromStore$);
expect('Role' in entities).toBe(true);
});
@ -48,9 +48,12 @@ describe('State Utils', () => {
describe('#mapEntitiesToContributors', () => {
it('should return contributors from given entities', async () => {
const contributors = await of(createMockEntities())
.pipe(mapEntitiesToContributors(configState, 'AbpIdentity'), take(1))
.toPromise();
const contributors = await lastValueFrom(
of(createMockEntities()).pipe(
mapEntitiesToContributors(configState, permissionService, 'AbpIdentity'),
take(1),
),
);
const propList = new EntityPropList();
contributors.prop.Role.forEach(callback => callback(propList));
@ -118,7 +121,7 @@ function createMockState() {
},
defaultResourceName: 'Default',
currentCulture: {
cultureName: 'en'
cultureName: 'en',
},
languages: [],
},

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

@ -5,6 +5,7 @@ import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-co
import { AbpApplicationLocalizationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service';
import {
ApplicationConfigurationDto,
ApplicationFeatureConfigurationDto,
ApplicationGlobalFeatureConfigurationDto,
} from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models';
import { INCUDE_LOCALIZATION_RESOURCES_TOKEN } from '../tokens/include-localization-resources.token';
@ -160,6 +161,18 @@ export class ConfigStateService {
});
}
private isFeatureEnabled(key: string, features: ApplicationFeatureConfigurationDto) {
return features.values[key] === 'true';
}
getFeatureIsEnabled(key: string) {
return this.isFeatureEnabled(key, this.store.state.features);
}
getFeatureIsEnabled$(key: string) {
return this.store.sliceState(state => this.isFeatureEnabled(key, state.features));
}
getSetting(key: string) {
return this.store.state.setting?.values?.[key];
}

6
npm/ng-packs/packages/core/src/lib/utils/string-utils.ts

@ -17,8 +17,10 @@ export function createTokenParser(format: string) {
}
export function interpolate(text: string, params: string[]) {
return text
.replace(/(['"]?\{\s*(\d+)\s*\}['"]?)/g, (_, match, digit) => params[digit] ?? match)
return text
.replace(/(['"])?\{\s*(\d+)\s*\}\1/g, (_, quote, digit) =>
(quote ? quote : '') + (params[digit] ?? `{${digit}}`) + (quote ? quote : '')
)
.replace(/\s+/g, ' ');
}

14
npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts

@ -1,9 +1,3 @@
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { ConfigStateService, IAbpGuard } from '@abp/ng.core';
import {
ExtensionsService,
getObjectExtensionEntitiesFromStore,
@ -11,6 +5,11 @@ import {
mergeWithDefaultActions,
mergeWithDefaultProps,
} from '@abp/ng.components/extensible';
import { ConfigStateService, IAbpGuard, PermissionService } from '@abp/ng.core';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { eIdentityComponents } from '../enums/components';
import {
@ -32,6 +31,7 @@ import {
@Injectable()
export class IdentityExtensionsGuard implements IAbpGuard {
protected readonly configState = inject(ConfigStateService);
protected readonly permission = inject(PermissionService);
protected readonly extensions = inject(ExtensionsService);
canActivate(): Observable<boolean> {
@ -48,7 +48,7 @@ export class IdentityExtensionsGuard implements IAbpGuard {
[eIdentityComponents.Roles]: entities.Role,
[eIdentityComponents.Users]: entities.User,
})),
mapEntitiesToContributors(this.configState, 'AbpIdentity'),
mapEntitiesToContributors(this.configState, this.permission, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultActions(
this.extensions.entityActions,

11
npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts

@ -1,6 +1,3 @@
import { inject } from '@angular/core';
import { map, tap } from 'rxjs';
import { ConfigStateService } from '@abp/ng.core';
import {
ExtensionsService,
getObjectExtensionEntitiesFromStore,
@ -8,6 +5,10 @@ import {
mergeWithDefaultActions,
mergeWithDefaultProps,
} from '@abp/ng.components/extensible';
import { ConfigStateService, PermissionService } from '@abp/ng.core';
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { map, tap } from 'rxjs';
import { eIdentityComponents } from '../enums';
import {
IDENTITY_ENTITY_ACTION_CONTRIBUTORS,
@ -21,10 +22,10 @@ import {
DEFAULT_IDENTITY_CREATE_FORM_PROPS,
DEFAULT_IDENTITY_EDIT_FORM_PROPS,
} from '../tokens';
import { ResolveFn } from '@angular/router';
export const identityExtensionsResolver: ResolveFn<any> = () => {
const configState = inject(ConfigStateService);
const permission = inject(PermissionService);
const extensions = inject(ExtensionsService);
const config = { optional: true };
@ -40,7 +41,7 @@ export const identityExtensionsResolver: ResolveFn<any> = () => {
[eIdentityComponents.Roles]: entities.Role,
[eIdentityComponents.Users]: entities.User,
})),
mapEntitiesToContributors(configState, 'AbpIdentity'),
mapEntitiesToContributors(configState, permission, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultActions(
extensions.entityActions,

16
npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts

@ -1,9 +1,3 @@
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { ConfigStateService, IAbpGuard } from '@abp/ng.core';
import {
ExtensionsService,
getObjectExtensionEntitiesFromStore,
@ -11,6 +5,11 @@ import {
mergeWithDefaultActions,
mergeWithDefaultProps,
} from '@abp/ng.components/extensible';
import { ConfigStateService, IAbpGuard, PermissionService } from '@abp/ng.core';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { eTenantManagementComponents } from '../enums/components';
import {
@ -32,6 +31,7 @@ import {
@Injectable()
export class TenantManagementExtensionsGuard implements IAbpGuard {
protected readonly configState = inject(ConfigStateService);
protected readonly permission = inject(PermissionService);
protected readonly extensions = inject(ExtensionsService);
canActivate(): Observable<boolean> {
@ -49,7 +49,7 @@ export class TenantManagementExtensionsGuard implements IAbpGuard {
map(entities => ({
[eTenantManagementComponents.Tenants]: entities.Tenant,
})),
mapEntitiesToContributors(this.configState, 'TenantManagement'),
mapEntitiesToContributors(this.configState, this.permission, 'TenantManagement'),
tap(objectExtensionContributors => {
mergeWithDefaultActions(
this.extensions.entityActions,
@ -84,5 +84,3 @@ export class TenantManagementExtensionsGuard implements IAbpGuard {
);
}
}

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

Loading…
Cancel
Save