Browse Source

Merge branch 'dev' into pr/21236

pull/21236/head
enisn 2 years ago
parent
commit
456cef284d
No known key found for this signature in database GPG Key ID: A052619F04155D1C
  1. 4
      .github/workflows/auto-pr.yml
  2. 2
      .github/workflows/build-and-test.yml
  3. 155
      Directory.Packages.props
  4. 4
      README.md
  5. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
  6. 22
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  7. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  8. 1
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  9. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  10. 2
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md
  11. BIN
      docs/en/Blog-Posts/2024-10-23 v9_0_Preview/docs-image-larger.png
  12. BIN
      docs/en/Blog-Posts/2024-10-23 v9_0_Preview/suite-navigation-properties.png
  13. BIN
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/community-talks.png
  14. BIN
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/cover-image.png
  15. 93
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md
  16. BIN
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/switch-to-stable.png
  17. 67
      docs/en/Blog-Posts/2024-12-15-ABP-Studio-R2R/POST.md
  18. 4
      docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md
  19. 16
      docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md
  20. BIN
      docs/en/Community-Articles/2024-10-09-Cookies-vs-Local-Storage/cover.png
  21. BIN
      docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/cited-from-microsoft-blog-post.png
  22. BIN
      docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/cover.png
  23. BIN
      docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-add-existing-package.png
  24. BIN
      docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-add-new-microservice.png
  25. BIN
      docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-solution-runner.png
  26. BIN
      docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-vs-dotnet-aspire-comparison-table.png
  27. BIN
      docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/dotnet-aspire-dashboard.png
  28. BIN
      docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/dog-food.png
  29. BIN
      docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/net-support-policy.png
  30. 125
      docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md
  31. BIN
      docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/cover-image.png
  32. BIN
      docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/debug-hybrid-cache.png
  33. 86
      docs/en/Community-Articles/2024-11-04-EF Core 9 Read-only-Primitive-Collections/POST.md
  34. 54
      docs/en/Community-Articles/2024-11-05-.NET Aspire 9.0 Features/Post.md
  35. BIN
      docs/en/Community-Articles/2024-11-05-.NET Aspire 9.0 Features/aspire_resource_lifecycle.jpg
  36. BIN
      docs/en/Community-Articles/2024-11-05-.NET Aspire 9.0 Features/aspire_trace_filter.jpg
  37. 113
      docs/en/Community-Articles/2024-11-05-SignalR-supports-trimming-and-Native-AOT/POST.md
  38. BIN
      docs/en/Community-Articles/2024-11-05-SignalR-supports-trimming-and-Native-AOT/chat.png
  39. 58
      docs/en/Community-Articles/2024-11-06-Keyed-DI-in-Middlewares-Net-9/post.md
  40. 108
      docs/en/Community-Articles/2024-11-06-Optimize-static-web-asset-delivery/POST.md
  41. BIN
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/cover.png
  42. BIN
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img1.png
  43. BIN
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img2.png
  44. BIN
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img3.png
  45. BIN
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img4.png
  46. BIN
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img5.png
  47. 164
      docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/post.md
  48. 202
      docs/en/Community-Articles/2024-11-14-Csharp-13-Features/Post.md
  49. 165
      docs/en/Community-Articles/2024-11-14-EF-Core-9-Linq-SQL-Translation/POST.md
  50. 309
      docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md
  51. BIN
      docs/en/Community-Articles/2024-11-25-Global-Assets/image.png
  52. 385
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md
  53. BIN
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/chat-example.gif
  54. BIN
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/cover-image.png
  55. BIN
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/image-generation-example.gif
  56. BIN
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-1.gif
  57. BIN
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-2.gif
  58. BIN
      docs/en/Community-Articles/2024-12-01-OpenAI-Integration/sample-page.png
  59. 234
      docs/en/Community-Articles/2024-12-09-Unit-Test/POST.md
  60. 12
      docs/en/cli/index.md
  61. 75
      docs/en/cli/new-command-samples.md
  62. 6
      docs/en/contribution/index.md
  63. 6
      docs/en/deployment/configuring-openIddict.md
  64. 84
      docs/en/deployment/forwarded-headers.md
  65. 1
      docs/en/deployment/index.md
  66. 534
      docs/en/docs-nav.json
  67. 2
      docs/en/docs-params.json
  68. 44
      docs/en/framework/architecture/best-practices/application-services.md
  69. 6
      docs/en/framework/architecture/best-practices/data-transfer-objects.md
  70. 10
      docs/en/framework/architecture/best-practices/domain-services.md
  71. 33
      docs/en/framework/architecture/best-practices/entities.md
  72. 18
      docs/en/framework/architecture/best-practices/entity-framework-core-integration.md
  73. 4
      docs/en/framework/architecture/best-practices/module-architecture.md
  74. 18
      docs/en/framework/architecture/best-practices/mongodb-integration.md
  75. 10
      docs/en/framework/architecture/best-practices/repositories.md
  76. 2
      docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md
  77. 2
      docs/en/framework/architecture/domain-driven-design/domain-services.md
  78. 44
      docs/en/framework/fundamentals/caching.md
  79. 2
      docs/en/framework/fundamentals/dependency-injection.md
  80. 18
      docs/en/framework/infrastructure/audit-logging.md
  81. 7
      docs/en/framework/infrastructure/background-jobs/index.md
  82. 25
      docs/en/framework/infrastructure/event-bus/index.md
  83. 12
      docs/en/framework/infrastructure/features.md
  84. 2
      docs/en/framework/ui/angular/http-error-handling.md
  85. 2
      docs/en/framework/ui/angular/quick-start.md
  86. 109
      docs/en/framework/ui/blazor/global-scripts-styles.md
  87. 3
      docs/en/framework/ui/maui/index.md
  88. 2
      docs/en/framework/ui/mvc-razor-pages/client-side-package-management.md
  89. 6
      docs/en/framework/ui/mvc-razor-pages/tag-helpers/dynamic-forms.md
  90. 2
      docs/en/framework/ui/react-native/index.md
  91. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-docker-dependencies.png
  92. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png
  93. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png
  94. BIN
      docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch.png
  95. BIN
      docs/en/get-started/images/abp-studio-no-layers-new-solution-additional-options-0.9.13.png
  96. BIN
      docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-0.9.13.png
  97. BIN
      docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore-0.9.13.png
  98. BIN
      docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo-0.9.13.png
  99. BIN
      docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore-0.9.13.png
  100. BIN
      docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo-0.9.13.png

4
.github/workflows/auto-pr.yml

@ -27,10 +27,12 @@ jobs:
title: Merge branch dev with rel-9.0
body: This PR generated automatically to merge dev with rel-9.0. Please review the changed files before merging to prevent any errors that may occur.
reviewers: maliming
draft: true
token: ${{ github.token }}
- name: Merge Pull Request
env:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
gh pr ready
gh pr review auto-merge/rel-9-0/${{github.run_number}} --approve
gh pr merge auto-merge/rel-9-0/${{github.run_number}} --merge --auto --delete-branch
gh pr merge auto-merge/rel-9-0/${{github.run_number}} --merge --auto --delete-branch

2
.github/workflows/build-and-test.yml

@ -51,7 +51,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
dotnet-version: 9.0.100-rc.2.24474.11
dotnet-version: 9.0.100
- name: chown
run: |

155
Directory.Packages.props

@ -6,21 +6,21 @@
<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.14.1" />
<PackageVersion Include="AsyncKeyedLock" Version="7.0.2" />
<PackageVersion Include="AsyncKeyedLock" Version="7.1.3" />
<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.400.30" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.400.30" />
<PackageVersion Include="AWSSDK.S3" Version="3.7.410.9" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.401.16" />
<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="Blazorise" Version="1.6.2" />
<PackageVersion Include="Blazorise.Components" Version="1.6.2" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.6.2" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.6.2" />
<PackageVersion Include="Castle.Core" Version="5.1.1" />
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
@ -29,7 +29,7 @@
<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="Devart.Data.Oracle.EFCore" Version="10.4.190.9" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.7" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
<PackageVersion Include="DeepL.net" Version="1.10.0" />
@ -39,8 +39,8 @@
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<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="Hangfire.AspNetCore" Version="1.8.17" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.17" />
<PackageVersion Include="HtmlSanitizer" Version="8.1.870" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="IdentityServer4" Version="4.1.2" />
@ -51,84 +51,85 @@
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="13.4.0" />
<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.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.33" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0-rc.2.24474.1" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.7.24406.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="9.0.0-rc.2.24474.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0-rc.2.24508.2" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.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="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.3.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.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.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageVersion Include="NSubstitute" Version="5.1.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="13.0.1" />
<PackageVersion Include="OpenIddict.Abstractions" Version="5.8.0" />
<PackageVersion Include="OpenIddict.Core" Version="5.8.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="5.8.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="5.8.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="5.8.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.23.60" />
<PackageVersion Include="OpenIddict.Abstractions" Version="6.0.0" />
<PackageVersion Include="OpenIddict.Core" Version="6.0.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="6.0.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="6.0.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="6.0.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.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="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.2.efcore.9.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" />
@ -155,19 +156,19 @@
<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.2.24473.5" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.0" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.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.2.24473.5" />
<PackageVersion Include="System.Security.Permissions" Version="9.0.0" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="System.Text.Json" Version="9.0.0-rc.2.24473.5" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.0" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.1.0" />
<PackageVersion Include="TencentCloudSDK.Sms" Version="3.0.1142" />
<PackageVersion Include="TimeZoneConverter" Version="6.1.0" />
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
@ -177,4 +178,4 @@
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.2" />
</ItemGroup>
</Project>
</Project>

4
README.md

@ -1,7 +1,7 @@
# ABP Framework
![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)
[![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://abp.io/join-discord)
[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.
@ -121,4 +121,4 @@ GitHub repository stars are an important indicator of popularity and the size of
## 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.
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://abp.io/join-discord.

5
abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json

@ -13,6 +13,9 @@
"ManageAccount": "My Account | ABP.IO",
"ManageYourProfile": "Manage your profile",
"ReturnToApplication": "Return to application",
"IdentityUserNotAvailable:Deleted": "This email address is not available. Reason: Already deleted."
"IdentityUserNotAvailable:Deleted": "This email address is not available. Reason: Already deleted.",
"SelectYourOrganization": "Select your organization",
"PleaseSelectOrganization": "Please select an organization to continue",
"Continue": "Continue"
}
}

22
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -22,6 +22,7 @@
"Permission:Accounting": "Accounting",
"Permission:Accounting:Quotation": "Quotation",
"Permission:Accounting:Invoice": "Invoice",
"Permission:Export" : "Export",
"Menu:Organizations": "Organizations",
"Menu:Accounting": "Accounting",
"Menu:Packages": "Packages",
@ -511,6 +512,7 @@
"QuotationTemplate.BankAccount": "Our bank account information can be found at {0}",
"Permission:Raffles": "Raffle",
"Permission:Draw": "Draw",
"Permission:ExportAttendeesAsExcel": "Export at attendees as Excel",
"Menu:Raffles": "Raffles",
"RaffleIsNotDrawable": "Raffle is not drawable",
"WinnerCountMustBeGreaterThanZero": "Winner count must be greater than zero",
@ -649,6 +651,22 @@
"Permission:HeroSections": "Hero Sections",
"RedirectLink": "Redirect link",
"HeroSectionsDeletionConfirmationMessage": "Are you sure you want to delete the hero section?",
"AbpStudioName": "Abp Studio name"
"AbpStudioName": "ABP Studio name",
"Permission:EditAttendees": "Edit Attendees",
"AttendeesCount": "Attendees Count",
"CreateQRCode": "Create QR Code",
"DrawTV": "Public draw on the TV",
"DrawModal": "Private draw on the modal",
"SetAsDrawable": "Set as drawable",
"SetAsNoDrawable": "Set as non-drawable",
"SetAsCompleted": "Set as completed",
"RemoveAllWinners": "Remove all winners",
"EditWinners": "Edit winners",
"EditAttendees": "Edit attendees",
"ExportAttendeesAsExcel": "Export attendees as Excel",
"DuplicateRaffle": "Duplicate raffle",
"Menu:RedisManagement": "Redis Management",
"RedisManagement": "Redis Management",
"Permission:RedisManagement": "Redis Management"
}
}
}

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

@ -242,14 +242,14 @@
"ReturnOnInvestment": "Return on Investment",
"PromotionalOffers": "Promotional Offers",
"PromotionalOffersDefinition": "Discounts, seasonal campaigns, etc.",
"EventsDefinition": "Community Talks, Webinars, ABP .NET Conference, etc.",
"EventsDefinition": "Community Talks, Webinars, ABP DOTNET Conference, etc.",
"ReleaseNotesDefinition": "ABP.IO Platform releases, new products, etc.",
"Newsletter": "Newsletter",
"NewsletterDefinition": "Blog posts, community news, etc.",
"OrganizationOverview": "Organization Overview",
"EmailPreferences": "Email Preferences",
"VideoCourses": "Essential Videos",
"DoYouAgreePrivacyPolicy": "By clicking <b>Subscribe</b> button you agree to the <a href=\"https://account.abp.io/Account/TermsConditions\">Terms & Conditions</a> and <a href=\"https://account.abp.io/Account/Privacy\">Privacy Policy</a>.",
"DoYouAgreePrivacyPolicy": "By clicking <b>Subscribe</b> button you agree to the <a href=\"/terms-conditions\">Terms & Conditions</a> and <a href=\"/privacy\">Privacy Policy</a>.",
"AbpConferenceDescription": "ABP Conference is a virtual event for .NET developers to learn and connect with the community.",
"Mobile": "Mobile",
"MetaTwitterCard": "summary_large_image"

1
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json

@ -1211,6 +1211,5 @@
"TrainingDescription": "We are offering the following training packages for who want to get expertise on the ABP Framework and the ABP.",
"PurchaseDevelopers": "developers",
"LinkExpiredMessage": "The payment link has expired! Contact us at <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> to update the link or <a href='https://abp.io/contact'>click here</a> to navigate to the contact page."
}
}

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

@ -459,7 +459,7 @@
"FullName": "Full name",
"CompanySize": "Company size",
"TestimonialTitle": "Let's hear your testimonial",
"TestimonialInfo": "What our customers say matters! Tell us about your experience with our product and service. It is recommended to write the testimonial in English to reach a wider audience.",
"TestimonialInfo": "What you say matters! Tell us about your experience with ABP in a few sentences. Please write it in English to reach a wider audience.",
"Country": "Country",
"TestimonialTextPlaceholder": "Write a brief story about how ABP helped you build and deliver your project.",
"PositionPlaceholder": "Your position at your company",
@ -1691,8 +1691,8 @@
"HurryUpLastDay": "Hurry Up! Last Day: {0}",
"CreatingCRUDPagesWithABPSuite": "Creating CRUD pages with ABP Suite",
"MultipleYearDiscount": "Multiple Year Discount",
"CampaignDiscountText": "New Platform Discount",
"CampaignDiscountName": "New Platform",
"CampaignDiscountText": "Black Friday Discount",
"CampaignDiscountName": "Black Friday",
"CampaignName:BlackFriday": "Black Friday",
"MultipleOrganizationInfo": "See All Your Organizations",
"AbpStudioBetaAccessInfoTitle": "ABP Studio Beta Access",
@ -1867,6 +1867,7 @@
"NewsletterEmailFooterTemplateDeleteSubscription": "<a style=\"color: #007bff;\" href=\"{0}\" data-root=\"{1}\">If you change your mind, you're always welcome to resubscribe!</a>",
"GenerateQuote" : "Generate Quote" ,
"GeneratePriceQuote": "Generate a Price Quote",
"Qa:QuestionPageTitle": "Support"
"Qa:QuestionPageTitle": "Support",
"SelectedTrainingName" : "Trainings"
}
}

2
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md

@ -255,4 +255,4 @@ We've created an official ABP Discord server so the ABP Community can interact w
Thanks to the ABP Community, **700+** people joined our Discord Server so far and it grows every day.
You can join our Discord Server from [here](https://discord.gg/abp), if you haven't yet.
You can join our Discord Server from [here](https://abp.io/join-discord), if you haven't yet.

BIN
docs/en/Blog-Posts/2024-10-23 v9_0_Preview/docs-image-larger.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 212 KiB

BIN
docs/en/Blog-Posts/2024-10-23 v9_0_Preview/suite-navigation-properties.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/community-talks.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

93
docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md

@ -0,0 +1,93 @@
# ABP.IO Platform 9.0 Has Been Released Based on .NET 9.0
![](cover-image.png)
Today, [ABP](https://abp.io/) 9.0 stable version has been released based on [.NET 9.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). You can create solutions with ABP 9.0 starting from ABP Studio v0.9.11 or by using the ABP CLI as explained in the following sections.
## What's New With Version 9.0?
All the new features were explained in detail in the [9.0 RC Announcement Post](https://abp.io/blog/announcing-abp-9-0-release-candidate), so there is no need to review them again. You can check it out for more details.
## Getting Started with 9.0
### Creating New Solutions
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli) to create new solutions.
By default, ABP Studio uses stable versions to create solutions. Therefore, it will be creating the solution with the latest stable version, which is v9.0 for now, so you don't need to specify the version. **You can create solutions with ABP 9.0 starting from v0.9.11.**
### How to Upgrade an Existing Solution
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained:
### Upgrading via ABP Studio
If you are already using the ABP Studio, you can upgrade it to the latest version to align it with ABP v9.0. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info.
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Switch to stable** action button to instantly upgrade your solution:
![](switch-to-stable.png)
> Please note that ABP CLI & ABP Studio only upgrade the related ABP packages, so you need to upgrade the other packages for .NET 9.0 manually.
### Upgrading via ABP CLI
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version.
If you haven't installed it yet, you can run the following command:
```bash
dotnet tool install -g Volo.Abp.Studio.Cli
```
Or to update the existing CLI, you can run the following command:
```bash
dotnet tool update -g Volo.Abp.Studio.Cli
```
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows:
```bash
abp update
```
You can run this command in the root folder of your solution to update all ABP related packages.
> Please note that ABP CLI & ABP Studio only upgrade the related ABP packages, so you need to upgrade the other packages for .NET 9.0 manually.
## Migration Guides
There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v8.x: [ABP Version 9.0 Migration Guide](https://abp.io/docs/9.0/release-info/migration-guides/abp-9-0)
## Community News
### Highlights from .NET 9.0
Our team has closely followed the ASP.NET Core and Entity Framework Core 9.0 releases, read Microsoft's guides and documentation, and adapted the changes to our ABP.IO Platform. We are proud to say that we've shipped the ABP 9.0 based on .NET 9.0 just after Microsoft's .NET 9.0 release.
In addition to the ABP's .NET 9.0 upgrade, our team has created many great articles to highlight the important features coming with ASP.NET Core 9.0 and Entity Framework Core 9.0.
> You can read [this post](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-9-0) to see the list of all articles.
### New ABP Community Articles
In addition to [the articles to highlight .NET 9.0 features written by our team](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-9-0), here are some of the recent posts added to the [ABP Community](https://abp.io/community):
* [Video: Building Modular Monolith Applications with ASP.NET Core & ABP Studio](https://abp.io/community/videos/building-modular-monolith-applications-with-asp.net-core-abp-studio-66znukvf) by [Halil İbrahim Kalkan](https://x.com/hibrahimkalkan)
* [How to create your Own AI Bot on WhatsApp Using an ABP.io Template](https://abp.io/community/articles/how-to-create-your-own-ai-bot-on-whatsapp-using-the-abp-framework-c6jgvt9c) by [Michael Kokula](https://abp.io/community/members/Michal_Kokula)
* [ABP Now Supports .NET 9](https://abp.io/community/articles/abp-now-supports-.net-9-zpkznc4f) by [Alper Ebiçoğlu](https://x.com/alperebicoglu)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
### ABP Community Talks 2024.7: What’s New with .NET 9 & ABP 9?
![](community-talks.png)
In this episode of ABP Community Talks, 2024.7; we will dive into the features that came with .NET 9.0 with [Alper Ebicoglu](https://github.com/ebicoglu), [Engincan Veske](https://github.com/EngincanV), [Berkan Sasmaz](https://github.com/berkansasmaz) and [Ahmet Faruk Ulu](https://github.com/ahmetfarukulu).
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/9.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.0 and provide feedback to help us release more stable versions.
Thanks for being a part of this community!

BIN
docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/switch-to-stable.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

67
docs/en/Blog-Posts/2024-12-15-ABP-Studio-R2R/POST.md

@ -0,0 +1,67 @@
# ABP Studio Goes AOT: Faster Startups with Ready-to-Run (R2R) Publishing
We're excited that [ABP Studio](https://abp.io/studio) now supports [Ready-to-Run (R2R) publishing](https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run) (starting from v0.9.16+), a hybrid form of ahead-of-time (AOT) compilation. This enhancement significantly improves the startup time and overall performance of ABP Studio, making it faster and more performant than ever before.
Let's dive into what R2R publishing is, how it works, and the benefits it brings to ABP Studio.
## What is Ready-to-Run (R2R) Publishing?
Ready-to-Run (R2R) is a form of AOT compilation available in the .NET ecosystem. Unlike traditional just-in-time (JIT) compilation, R2R precompiles parts of your application to native code before deployment. This precompiled code helps reduce the startup time by minimizing the work needed during runtime.
However, R2R isn't a complete AOT compilation. Instead, it's a hybrid approach because it stores both:
* **Native code for precompiled methods** (to improve startup time and performance)
* **Intermediate Language (IL) code** for methods that may need further JIT compilation
This hybrid nature is why R2R binaries are typically larger. For ABP Studio, the storage size increased by ~150 MB with R2R enabled, but the trade-off is well worth it for the performance and startup-time gains.
## How R2R (Ready-to-Run) Improves ABP Studio
### Faster Startup Time 🚀
One of the biggest advantages of R2R publishing is its impact on startup times. In our local tests, enabling R2R resulted in startup times being **reduced by 2.5x** ⬇️.
This means you can get to work faster, without waiting for the application to being startup from the beginning. Whether you're launching ABP Studio to manage projects, generate code, or deploy applications, the improved responsiveness is noticeable.
### Performance Enhancements 📈
In addition to faster startups, R2R publishing contributes to overall performance improvements. By precompiling frequently used methods, R2R reduces the workload on the JIT compiler during execution, leading to smoother and more efficient operations.
### Trade-offs: Increased Storage Size 🆙
With great performance comes a slight trade-off: storage size. R2R binaries include both **native** and **IL code**, which increases the file size. In the case of ABP Studio, the storage footprint increased by ~150 MB. However, the substantial improvements in speed and responsiveness make this a worthwhile investment.
## How to Enable R2R Publishing in Your Applications?
If you're developing applications and want to benefit from R2R, here's a quick guide on how to enable it in your .NET projects:
1. You can add the following configuration to your final project's `.csproj` file:
```xml
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
```
2. Then, publish your application with the `dotnet publish` command:
```bash
dotnet publish -c Release
```
Alternatively, you can specify the _PublishReadyToRun_ flag directly to the `dotnet publish` command as follows:
```bash
dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true
```
That's it! Your application will now include precompiled native code for faster startup and great performance benefits.
> Please refer to the [official documentation](https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run) before publishing your application with R2R.
## Conclusion
As ABP team, we're always looking for ways to improve the developer experience. By adopting **Ready-to-Run (R2R) publishing** for ABP Studio, we're aiming to deliver a faster and more efficient tool for your development needs.
Stay tuned for more updates and enhancements as we continue to optimize ABP Studio and please provide us with your invaluable feedback.

4
docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md

@ -240,3 +240,7 @@ gRPC on .NET has different approaches, features, configurations and more details
* You can find the completed source code here: https://github.com/abpframework/abp-samples/tree/master/GrpcDemo2
* You can also see all the changes I've done in this article here: https://github.com/abpframework/abp-samples/pull/200/files
## See Also
* [Consuming gRPC Services from Blazor WebAssembly Application Using gRPC-Web](https://abp.io/community/articles/consuming-grpc-services-from-blazor-webassembly-application-using-grpcweb-dqjry3rv)

16
docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md

@ -187,11 +187,21 @@ On the other hand, resolving keyed services from `LazyServiceProvider` is not su
### Automatically Registering Keyed Services
Currently, if you want to register a keyed service, you need to do it manually as we see in the previous sections by using one of the overloads (`.AddKeyedTransient`, `.AddKeyedScoped` and `.AddKeyedSingleton`).
ABP provides the `ExposeKeyedServiceAttribute` to control which keyed services are provided by the related class.
It would be good if we could make this process automatically and not need to manually register services, and for that purpose, I have [created an issue](https://github.com/abpframework/abp/issues/18794) that aims to introduce an attribute, which allows us to automatically register multiple services as keyed services.
For example, if you want to register a keyed service as a transient dependency, you can do it as follows:
You can [follow the issue](https://github.com/abpframework/abp/issues/18794) if you are considering using keyed services in your application and don't want to register them manually.
```csharp
[ExposeKeyedService<ITaxCalculator>("taxCalculator")]
[ExposeKeyedService<ICalculator>("calculator")]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{
}
```
> Notice that the ExposeKeyedServiceAttribute only exposes the keyed services. So, you can not inject the ITaxCalculator or ICalculator interfaces in your application without using the FromKeyedServicesAttribute as shown in the example above. If you want to expose both keyed and non-keyed services, you can use the ExposeServicesAttribute and ExposeKeyedServiceAttribute attributes altogether.
Please refer to the [Dependency Injection document](https://abp.io/docs/latest/framework/fundamentals/dependency-injection#exposekeyedservice-attribute) for further info.
## Summary

BIN
docs/en/Community-Articles/2024-10-09-Cookies-vs-Local-Storage/cover.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 KiB

After

Width:  |  Height:  |  Size: 524 KiB

BIN
docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/cited-from-microsoft-blog-post.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 106 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 KiB

After

Width:  |  Height:  |  Size: 432 KiB

BIN
docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-add-existing-package.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-add-new-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-solution-runner.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 108 KiB

BIN
docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/abp-studio-vs-dotnet-aspire-comparison-table.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/dotnet-aspire-dashboard.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/dog-food.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 KiB

After

Width:  |  Height:  |  Size: 93 KiB

BIN
docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/net-support-policy.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 46 KiB

125
docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md

@ -0,0 +1,125 @@
# Hybrid Cache in .NET 9
.NET 9 introduces an exciting feature: **HybridCache**, an advanced caching mechanism that seamlessly combines multiple caching strategies to maximize performance and scalability.
It offers a flexible caching solution that combines the best aspects of local and distributed caching. **HybridCache** is particularly useful in scenarios where quick, in-memory access is desirable but data consistency across multiple application instances is also a requirement.
In this article, we’ll explore **HybridCache** in .NET 9 and how it integrates with ABP Framework using `AbpHybridCache`. This new feature offers a robust solution for applications that need to scale while maintaining efficient caching strategies.
## What is HybridCache?
**HybridCache** is designed to merge different caching layers, commonly including an in-memory cache (for high-speed access) and a distributed cache (for scalability across multiple instances). This hybrid approach allows for:
* **Improved Performance**: Frequently accessed data is stored in-memory, reducing latency.
* **Increased Scalability**: Cached data can still be shared across distributed environments, essential for load-balanced applications.
* **Automatic Synchronization**: Changes in distributed cache automatically update the in-memory cache, ensuring data consistency.
## Using HybridCache with ABP
> For more information about the implementation in the ABP side, you can refer to the pull request [here](https://github.com/abpframework/abp/pull/20859).
ABP's support for **HybridCache** is available starting from version 9.0 through the [`AbpHybridCache`](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCache.cs) implementation. By leveraging this feature, developers using ABP can implement hybrid caching in a way that aligns with ABP’s modular and extensible architecture.
To demonstrate how to use **HybridCache** in ABP, let's start with a simple example.
> You can create an ABP-based application with v9.0+, and then follow the next steps for using hybrid caching in your application.
### Configuring the `AbpHybridCacheOptions` (Optional)
First, you can configure the hybrid cache options in your module class as below (it's optional):
```csharp
using Microsoft.Extensions.Caching.Hybrid;
using Volo.Abp.Caching.Hybrid;
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//...
Configure<AbpHybridCacheOptions>(options =>
{
//configuring the global hybrid cache options
options.GlobalHybridCacheEntryOptions = new HybridCacheEntryOptions()
{
Expiration = TimeSpan.FromMinutes(20),
LocalCacheExpiration = TimeSpan.FromMinutes(10)
};
});
}
}
```
* You can configure the `AbpHybridCacheOptions` to set *keyPrefix* for your cache keys, throw or hide exceptions for the distributed cache (by default *it hides errors*), or configure cache for specific cache item keys and more...
* By setting the `GlobalHybridCacheEntryOptions`, you specify the caching options globally in your application. Thanks to that, you don't need to manually pass the related options whenever you use the `IHybridCache` service.
### Using the `IHybridCache` Service
After the configuration, now you can inject the `IHybridCache` and use it to set and retrieve cache values:
```csharp
using Volo.Abp.Caching.Hybrid;
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IHybridCache<BookCacheItem> _hybridCache;
public BookAppService(IHybridCache<BookCacheItem> hybridCache)
{
_hybridCache = hybridCache;
}
public async Task<BookCacheItem> GetBookWithPageCountAsync(string name)
{
var cacheKey = "cacheKey:book-" + name;
// Retrieve data from hybrid cache
return await _hybridCache.GetOrCreateAsync(cacheKey, async () =>
{
// Simulating getting and returning the data if not exist in the cache
return new BookCacheItem
{
Name = name,
PageCount = 100
};
});
}
}
public class BookCacheItem
{
public string Name { get; set; }
public int PageCount { get; set; }
}
```
* You can use the `IHybridCache<TCacheItem>` or `IHybridCache<TCacheItem, TCacheKey>` service to leverage the hybrid caching. If you use `IHybridCache<TCacheItem>`as the service, then you should pass the cache key as *string* like in the example above.
* In this example, you used the `GetOrCreateAsync` method, which first tries to get the cache item with the provided cache key, if there is no cache with the specified key, then it runs the factory method and add the returned data to the cache.
* Alternatively, you can use the `SetAsync` method to set the cache item.
### Debugging the `IHybridCache` Service (deep-dive)
When you debug the `IHybridCache` service, you'll notice the L1 and L2 cache stores. (L1 is in-memory cache store and L2 is the distributed cache store):
![](debug-hybrid-cache.png)
As you can see from the figure, it only set the cache item to the **LocalCache** (`MemoryCache`) and did not set the **BackendCache** (`DistributedCache`) because I did not configure the distributed cache and not running my application in multiple instances. But as you can notice, even without an `IDistributedCache` configuration, the `HybridCache` service will still provide in-process caching.
**Note:** If you configure distributed caching options, `HybridCache` service uses the distributed cache and sets the **BackendCache**.
## Conclusion
The **HybridCache** library in .NET 9 provides a powerful tool for applications needing both high-speed caching and consistency in distributed environments.
With ABP Framework’s `AbpHybridCache` support, integrating this feature into an ABP-based application becomes straightforward. This setup helps ensure that cached data remains synchronized across instances, bringing a new level of flexibility to caching in .NET 9 applications.
> For more information, you can refer to the [Microsoft's official document](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0#new-hybridcache-library).
## References
- https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0#new-hybridcache-library
- https://www.youtube.com/watch?v=TDyZc11cJfA
- https://github.com/abpframework/abp/pull/20803
- https://github.com/abpframework/abp/pull/20859

BIN
docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

BIN
docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/debug-hybrid-cache.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

86
docs/en/Community-Articles/2024-11-04-EF Core 9 Read-only-Primitive-Collections/POST.md

@ -0,0 +1,86 @@
# EF Core 9 Read-only Primitive Collections
In this article, we will explore the new features introduced in EF Core 9, specifically focusing on Read-only Primitive Collections. EF Core 8 introduced support for mapping arrays and mutable lists of primitive types, and you can read more about it [here](https://abp.io/community/articles/ef-core-8-primitive-collections-ttn5b6xp). This has been expanded in EF Core 9 to include read-only collections/lists. Specifically, EF Core 9 supports collections typed as `IReadOnlyList`, `IReadOnlyCollection`, or `ReadOnlyCollection`.
## Introduction to EF Core 9 Read-only Primitive Collections
Entity Framework Core 9 introduces several enhancements, one of which is the support for Read-only Primitive Collections. This feature aims to provide better support for scenarios where collections of primitive types, such as `int`, `string`, or `bool`, need to be used in a read-only manner in your entity classes. Previously, developers had to use complex workarounds to ensure collections couldn't be modified, but EF Core 9 now provides a simpler, built-in solution to handle this more effectively.
### Why Read-only Primitive Collections Matter
Read-only Primitive Collections are particularly useful when you need to guarantee the integrity of certain data within your entities. For example, imagine you have a `Car` entity that has a collection of `Colors`, represented as a set of enums. You might not want these colors to be modified after they're initially set, ensuring that any business logic reliant on these values remains consistent.
EF Core 9 introduces a convenient way to define these collections as read-only, helping developers maintain stricter control over their data.
### How It Works
Defining a read-only primitive collection is quite straightforward in EF Core 9. You can use the `IReadOnlyList<T>`, `IReadOnlyCollection<T>`, or `ReadOnlyCollection<T>` types to declare your properties, ensuring a consistent read-only behavior. This helps maintain data integrity by preventing modifications after the collection is set. Below is an example that includes a `Car` class and a `Color` enum. The `Car` class has a `Colors` property that holds a read-only list of available colors, ensuring that these values cannot be modified after being initially set:
```csharp
public enum Color
{
Black,
White,
Red,
Blue
}
public class Car
{
public int Id { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public IReadOnlyList<Color> Colors { get; private set; } = new List<Color> { Color.Black, Color.White }.AsReadOnly();
protected Car()
{
/* This constructor is for deserialization / ORM purpose */
}
public Car(string brand, string model, IEnumerable<Color> colors)
{
Brand = brand;
Model = model;
Colors = colors.ToList().AsReadOnly();
}
}
```
In the example above, `Colors` is defined as a read-only list, preventing any accidental modifications once it is set. This ensures that data integrity is maintained without the need for manual validation.
To query cars with specific colors, you can use the following example:
```csharp
var colors = new List<Color> { Color.Black, Color.White };
var cars = await context.Cars
.Where(c => c.Colors.Intersect(colors).Any())
.ToListAsync();
```
The query selects all cars that have any of the specified colors in their `Colors` collection.
The SQL result looks like this; as you can see, it sends colors as parameters instead of adding them inline. It also uses the `json_each` function to deserialize on the database side:
```sql
SELECT "c"."id",
"c"."brand",
"c"."colors",
"c"."model"
FROM "cars" AS "c"
WHERE EXISTS (SELECT 1
FROM (SELECT "c0"."value"
FROM Json_each("c"."colors") AS "c0"
INTERSECT
SELECT "c1"."value"
FROM Json_each(@__colors_0) AS "c1") AS "i")
```
### Conclusion
Read-only primitive collections make it easier to enforce data integrity by preventing changes to your collection data. This feature helps simplify your code while ensuring that critical parts of your data remain consistent.
## References
- https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#read-only-primitive-collections
- https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#primitive-collections
- https://abp.io/community/articles/ef-core-8-primitive-collections-ttn5b6xp

54
docs/en/Community-Articles/2024-11-05-.NET Aspire 9.0 Features/Post.md

@ -0,0 +1,54 @@
# .NET Aspire 9.0 Features
.NET Aspire 9.0 is the next major release, supporting both .NET 8 and .NET 9. This version includes new features and improvements.
## Upgrade to .NET Aspire
Now, you don't need workloads to develop .NET Aspire applications. In your project, you can add an SDK reference to `Aspire.AppHost.Sdk`.
For more information, you can check out [https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/dotnet-aspire-9?tabs=windows#upgrade-to-net-aspire-9](https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/dotnet-aspire-9?tabs=windows#upgrade-to-net-aspire-9) which explains upgrading an existing project in details.
## Dashboard
.NET Aspire offers a nice dashboard for developers to observe the performance and behavior of their applications. In this version, there are some enhancements;
* **Manage resource lifecycle**: You can stop, start, and restart resources.
* **Mobile and responsive support**: The .NET Aspire dashboard is now mobile-friendly.
* **Sensitive properties**: Properties can be marked as sensitive, automatically masking them in the dashboard UI.
* **Volumes**: Configured container volumes are listed in resource details.
* **Health checks**: .NET Aspire 9 adds support for health checks.
![Resource Lifecycle](./aspire_resource_lifecycle.jpg)
## Telemetry
.NET Aspire 9 comes with many new features to the Telemetry service.
* **Improve telemetry filtering**: Telemetry data can now be filtered by attribute values.
* **Combine telemetry from multiple resources**: If a resource has multiple replicas, you can now filter telemetry data to view from all instances.
* **Browser telemetry support**: The dashboard now supports OpenTelemetry Protocol (OTLP) over HTTP and cross-origin resource sharing (CORS).
![Telemetry Filtering](./aspire_trace_filter.jpg)
## Orchestration
The .NET App Host is a core component of the .NET runtime that helps launch and execute .NET applications.
.NET Aspire 9 introduces many new features to the app host. Let's take a look;
* **Waiting for dependencies**: You can configure a resource to wait for another resource to start before starting.
* **Resource health checks**: The `Waiting for dependencies` feature uses health checks to determine if a resource is ready.
## Integrations
.NET Aspire has integrations with some services and tools that make it easy to get started. New integrations are coming with .NET Aspire 9.
* Redis Insight
* OpenAI (Preview)
* MongoDB
* Azure
For Azure part, it is better to check the official documentation here [https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/dotnet-aspire-9-release-candidate-1?tabs=windows&pivots=visual-studio#azure](https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/dotnet-aspire-9-release-candidate-1?tabs=windows&pivots=visual-studio#azure) because it has a very detailed explanation.
## ABP Studio
.NET Aspire and [ABP Studio](https://abp.io/studio) are tools for different purposes with different scopes, and they have different approaches to solving problems; many developers may still be confused since they also have some similar functionalities and solve some common problems. You can check the comparison of .NET Aspire and ABP Studio in this [article](https://abp.io/community/articles/.net-aspire-vs-abp-studio-side-by-side-t1c73d1l).

BIN
docs/en/Community-Articles/2024-11-05-.NET Aspire 9.0 Features/aspire_resource_lifecycle.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/Community-Articles/2024-11-05-.NET Aspire 9.0 Features/aspire_trace_filter.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

113
docs/en/Community-Articles/2024-11-05-SignalR-supports-trimming-and-Native-AOT/POST.md

@ -0,0 +1,113 @@
# SignalR supports trimming and Native AOT
## What is SignalR?
SignalR is a library that allows you to add real-time web functionality to your applications. It provides a simple API for creating server-to-client remote procedure calls (RPC) that can be called from the server and client. Now SignalR supports trimming and Native AOT in .NET 8.0 and .NET 9.0. You can learn more about [SignalR new features](https://abp.io/community/articles/asp.net-core-signalr-new-features-summary-kcydtdgq) in this article.
## What is trimming and Native AOT?
AOT (Ahead-of-Time) compilation is a feature that allows you to compile your application into native code before running it. This can help improve performance and reduce startup times. Trimming is a feature that allows you to remove unused code from your application, reducing its size and improving performance. You can learn more about [Native AOT Compilation](https://abp.io/community/articles/native-aot-compilation-in-.net-8-oq7qtwov) in this article.
## How to use SignalR with trimming and Native AOT?
You can create ASP.NET Core AOT application with using the following command:
```bash
dotnet new webapiaot -n Acme.Sample
```
The created application uses `CreateSlimBuilder` method to create minimal builder for the application. You can use `CreateBuilder` method to create a builder with all the services registered. However, deploying an application with `CreateSlimBuilder` method is more convenient because it reduces the size of the application. You can learn more about [CreateSlimBuilder vs CreateBuilder](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot#createslimbuilder-vs-createbuilder).
Replace the `Program.cs` file with the following code:
```csharp
using Microsoft.AspNetCore.SignalR;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddSignalR();
builder.Services.Configure<JsonHubProtocolOptions>(o =>
{
o.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
var app = builder.Build();
app.MapHub<ChatHub>("/chatHub");
app.MapGet("/", () => Results.Content("""
<!DOCTYPE html>
<html>
<head>
<title>SignalR Chat</title>
</head>
<body>
<input id="userInput" placeholder="Enter your name" />
<input id="messageInput" placeholder="Type a message" />
<button onclick="sendMessage()">Send</button>
<ul id="messages"></ul>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
connection.on("ReceiveMessage", (user, message) => {
const li = document.createElement("li");
li.textContent = `${user}: ${message}`;
document.getElementById("messages").appendChild(li);
});
async function sendMessage() {
const user = document.getElementById("userInput").value;
const message = document.getElementById("messageInput").value;
await connection.invoke("SendMessage", user, message);
}
connection.start().catch(err => console.error(err));
</script>
</body>
</html>
""", "text/html"));
app.Run();
[JsonSerializable(typeof(string))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
```
It is a simple chat application that uses SignalR to send and receive messages.
![chat](chat.png)
Before deploying the application, ensure that **Desktop development with C++** is installed on your machine if you're using Windows OS. For more details, you can check the [pre-requisites](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot#prerequisites).
You can deploy the application with the following command:
```bash
dotnet publish -c Release
```
### Limitations
Since we are using Native AOT, there are some limitations that you should be aware of:
- **Only the JSON protocol is supported**: For the payload serialization in SignalR, only the JSON protocol is supported. You need to configure the `JsonHubProtocolOptions` to use the `AppJsonSerializerContext` for serialization/deserialization.
- **Reflection**: Native AOT does not support reflection. You need to use the `JsonSerializable` attribute to specify the types that should be serialized/deserialized. In this example, we have used the `JsonSerializable` attribute for the `string` type in the `AppJsonSerializerContext` class.
For more details, you can check the [limitations](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot#limitations-of-native-aot-deployment) of Native AOT.
## Conclusion
In this article, we learned how to use SignalR with trimming and Native AOT in .NET 8.0 and .NET 9.0. We created a simple chat application that uses SignalR to send and receive messages. We also discussed the limitations of using Native AOT and how to overcome them.
For more information, you can refer to the [Microsoft's official document](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0#signalr-supports-trimming-and-native-aot).

BIN
docs/en/Community-Articles/2024-11-05-SignalR-supports-trimming-and-Native-AOT/chat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

58
docs/en/Community-Articles/2024-11-06-Keyed-DI-in-Middlewares-Net-9/post.md

@ -0,0 +1,58 @@
# Middleware Now Supports Keyed Dependency Injection in .NET 9
This article explores a new feature in .NET 9 that enables keyed dependency injection in middleware. Previously, .NET 8 introduced keyed services, which allowed developers to register multiple instances of the same service type with distinct keys. Now, .NET 9 extends this feature to middleware, making it easier to inject specific services within the middleware based on defined keys. For more details, see this [overview on the .NET blog](https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/rc1/aspnetcore.md#keyed-di-in-middleware).
## What is Keyed Dependency Injection?
Keyed dependency injection is a technique for registering multiple service versions with unique identifiers, or “keys.” This approach is especially helpful when multiple implementations of the same service are required in different contexts. For example, you may have various logging services but want to inject a specific logger based on the application’s current needs. By using keys, developers can ensure that the appropriate service version is injected precisely where it’s needed.
## Using Keyed Dependency Injection in Middleware
In .NET 9, developers can now use keyed dependency injection directly in middleware. Keyed services can be injected through the middleware constructor or via the `Invoke`/`InvokeAsync` methods, allowing for straightforward and flexible control of service instances in middleware components. Here’s an example of how to configure and use keyed dependency injection in middleware:
```csharp
var builder = WebApplication.CreateBuilder(args);
// Register services with unique keys
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");
var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();
internal class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly MySingletonClass _singletonService;
// Constructor injection with key
public MyMiddleware(RequestDelegate next, [FromKeyedServices("test")] MySingletonClass singletonService)
{
_next = next;
_singletonService = singletonService;
}
// Invoke method with additional scoped service injection using key
public Task Invoke(HttpContext context, [FromKeyedServices("test2")] MyScopedClass scopedService)
{
// Middleware logic here
return _next(context);
}
}
```
In this example:
- `MySingletonClass` and `MyScopedClass` are registered with unique keys (`"test"` and `"test2"`).
- These services are injected into the middleware through both the constructor and `Invoke` method, based on their respective keys.
This approach allows developers to manage which service instances are available within middleware precisely.
## Conclusion
Keyed dependency injection in middleware is a significant addition in .NET 9. It provides developers with more control over which services are injected based on specific keys. This enhancement enables selective service injection in middleware scenarios, allowing for more modular and maintainable applications.
## References
- [.NET 9 Release Notes](https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/rc1/aspnetcore.md#keyed-di-in-middleware)
- [Dependency Injection and Keyed Services](https://learn.microsoft.com/aspnet/core/fundamentals/dependency-injection#keyed-services)

108
docs/en/Community-Articles/2024-11-06-Optimize-static-web-asset-delivery/POST.md

@ -0,0 +1,108 @@
# Optimizing Static Asset Delivery feature in ASP.NET Core 9.0
Delivering static assets efficiently is a key factor in building performant web applications. By optimizing how assets like CSS, JavaScript, and images are served to the browser, you can reduce load times, decrease network traffic, and improve the overall user experience.
One powerful tool to help achieve this is **MapStaticAssets**, a feature in ASP.NET Core that significantly optimizes the delivery of static resources. Whether you're working with Blazor, Razor Pages, MVC, or other UI frameworks, **MapStaticAssets** streamlines asset management and ensures that your web app delivers resources in the most efficient way possible.
## Why Optimizing Static Assets Matters
Serving static assets without optimization can lead to several performance bottlenecks:
- **Excessive network requests**: The browser may need to request the same resources multiple times, even if they haven’t changed.
- **Unnecessary data transfer**: Larger files are sent over the network, consuming bandwidth and slowing down page loads.
- **Outdated assets**: Without proper cache management, users may receive stale versions of files after an app update.
Optimizing static assets involves compressing files, managing caching headers, and ensuring that only the necessary resources are sent to the client. **MapStaticAssets** takes care of all these issues in a seamless, automated way.
## What is MapStaticAssets?
**MapStaticAssets** is designed to enhance the default static asset serving mechanism in ASP.NET Core. It can replace `UseStaticFiles` in most scenarios and comes with several built-in optimizations. These optimizations are executed at both build and publish time, ensuring that static resources are served in the most efficient way possible when your app is running.
Here's how you can implement **MapStaticAssets** in your app:
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
// Replacing UseStaticFiles with MapStaticAssets
app.MapStaticAssets();
app.MapRazorPages();
app.Run();
```
## Key Features of MapStaticAssets
1. **Build-time Compression**:
**MapStaticAssets** automatically compresses all static assets during the build process. It uses **gzip** compression during development and **gzip + brotli** compression when publishing. This reduces the file size significantly, ensuring faster download times.
For example, in a default Razor Pages template, assets like `bootstrap.min.css` and `jquery.js` are compressed by over 80%, resulting in significantly reduced file sizes:
| File | Original Size | Compressed Size | Compression Reduction |
|----------------------|---------------|-----------------|-----------------------|
| `bootstrap.min.css` | 163 KB | 17.5 KB | 89.26% |
| `jquery.js` | 89.6 KB | 28 KB | 68.75% |
| `bootstrap.min.js` | 78.5 KB | 20 KB | 74.52% |
| **Total** | 331.1 KB | 65.5 KB | 80.20% |
2. **Content-based ETags**:
**MapStaticAssets** generates **ETags** based on the SHA-256 hash of the file content, encoded in Base64. This ensures that the browser only re-downloads a resource if its content has changed. This eliminates unnecessary network requests, improving page load speeds.
3. **Smaller File Sizes for Libraries**:
Popular component libraries, such as **Fluent UI Blazor** and **MudBlazor**, benefit from similar compression optimizations. For example, the size of the **MudBlazor** library is reduced by over 90%, from 588 KB to just 46.7 KB after compression.
| File | Original Size | Compressed Size | Compression Reduction |
|----------------------|---------------|-----------------|-----------------------|
| `MudBlazor.min.css` | 541 KB | 37.5 KB | 93.07% |
| `MudBlazor.min.js` | 47.4 KB | 9.2 KB | 80.59% |
| **Total** | 588.4 KB | 46.7 KB | 92.07% |
4. **Automatic Optimization**:
As libraries or components are added or updated, **MapStaticAssets** automatically optimizes the assets as part of the build process. This includes minimizing the size of JavaScript and CSS files, reducing the impact of mobile or low-bandwidth environments.
5. **Serving Assets with a CDN**:
Although **MapStaticAssets** is focused on server-side optimizations, integrating a **CDN (Content Delivery Network)** can further boost performance by serving static assets from servers geographically closer to the user, reducing latency.
## Comparing MapStaticAssets to IIS Dynamic Compression
**MapStaticAssets** provides several advantages over traditional dynamic compression techniques, such as IIS **gzip** compression:
- **Simplicity**: There is no need for server-specific configuration, making **MapStaticAssets** easy to implement.
- **Performance**: By compressing assets at build time, the app doesn't need to perform compression during every request, which improves server performance.
- **Optimization**: Developers can focus on ensuring that assets are compressed to the smallest possible size during the build process.
For example, using **MapStaticAssets**, a file like `MudBlazor.min.css` is compressed down to 37.5 KB, whereas IIS dynamic compression might result in a size of 90 KB. This represents a **59%** reduction in size.
## About MapAbpStaticAssets
The ABP framework is 100% compatible with this new feature.
However, some JavaScript, CSS, and image files exist in the [Virtual File System](https://abp.io/docs/latest/framework/infrastructure/virtual-file-system), which ASP.NET Core's **MapStaticAssets** can't handle. For these files, additional **StaticFileMiddleware** is needed to serve them, which is where **MapAbpStaticAssets** comes in.
**MapAbpStaticAssets** adds the necessary **StaticFileMiddleware** to ensure that virtual files are correctly served. This middleware setup ensures seamless delivery of virtual resources alongside static assets.
You can view the source code of **MapAbpStaticAssets** on [GitHub](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs#L129-L198).
## Conclusion
Optimizing static asset delivery is essential for building fast, efficient web applications. **MapStaticAssets** simplifies and automates the optimization of static files by providing build-time compression, caching headers, and content-based ETags. This ensures that your app's static assets are always delivered in the most efficient way, whether users are on fast broadband or slower mobile connections. By using **MapStaticAssets**, you can deliver a faster, more reliable experience for your users with minimal effort.
## References
* [Static files in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-9.0)
* [What's new in ASP.NET Core 9.0](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-8.0#optimize-static-web-asset-delivery)

BIN
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

BIN
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

BIN
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

164
docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/post.md

@ -0,0 +1,164 @@
Built-in OpenAPI Document Generation with .NET 9 — No more SwaggerUI! 👋
========================================================================
![Cover](cover.png)
What’s Swagger UI?
------------------
[Swagger UI](https://swagger.io/) is an open-source tool that automatically generates an interactive, web-based documentation interface for WebAPIs.
It supports OpenAPI standards. It was very popular tool among the ASP.NET Core developers from 2020 to 2024.
Because it was a built-in tool comes with ASP.NET Core default templates.
We liked this tool because it was the first tool that allows us to make WebAPI calls for testing.
Now it provides paid services as well as free ones.
> Previously, Swagger was included by default from **.NET 5** to **.NET 8** in .NET web templates.
---
What’s OpenAPI?
---------------
OpenAPI is a standard specification for defining REST APIs.
The official website is [https://www.openapis.org/](https://www.openapis.org/).
Microsoft is now using OpenAPI and here is the official documentation 👉 [https://aka.ms/aspnet/openapi](https://aka.ms/aspnet/openapi)
---
Replacement of Swagger UI with OpenAPI
----------------------------------------------------------------------------
Swagger UI is no longer integrated into NET 9, as Microsoft wants a solution with first-class support, better control, and enhanced security. As you see in the below screenshot, Microsoft declares that it's already removed.
![Docs](img2.png)
---
## Why is Swagger Removed from .NET 9?
In March 2024, the ASP.NET Core team announced that they are removing the `Swashbuckle.AspNetCore` dependency from web templates from .NET 9 release.
> This decision was influenced by the project's lack of active maintenance and the absence of an official release for .NET 8.
Microsoft team created a new package `Microsoft.AspNetCore.OpenApi`. It provides built-in OpenAPI document generation just like Swagger. So Microsoft doesn't depend on external tools. Because in every .NET release, they need to ask the owners of the external tool libraries to align with their new version. And sometimes these library owners cannot update their code-base according to the recent .NET changes. And it is becoming harder for Microsoft to support the 3rd party libraries under these circumstances. Basically reducing 3rd party dependencies will help Microsoft fast release cycles.
I read Reddit, GitHub discussions and YouTube reviews about this topic. As I see community members expressed concerns about the inactivity of Swashbuckle and they are discussing alternatives like contributing to or forking the project. The Microsoft team also contacted the owners of Swashbuckle and NSwag to explore potential collaborations and ensure a smooth transition for developers.
In the below GitHub issue, you can see the details of this decision:
* [github.com/dotnet/aspnetcore/issues/54599](https://github.com/dotnet/aspnetcore/issues/54599)
**Jeremy** -Product Manager- at Microsoft, answers why they took this decision in [this post](https://github.com/dotnet/aspnetcore/issues/54599#issuecomment-2004975574).
![Jeremy Comments](img3.png)
As a summary;
**The change is due to a lack of maintenance of the Swagger library**, although it has seen some recent updates. This aims to reduce dependency on external tools and provide a streamlined, out-of-the-box experience for generating OpenAPI documentation for ASP.NET Core Web APIs.
---
What are the Benefits of the New OpenAI Package?
---------------------------------------------------------------
### Native Support and Reduced Dependency
The new `Microsoft.AspNetCore.OpenApi` package provides first-class citizen support for OpenAPI. It reduces reliance on external tools like Swashbuckle or NSwag for basic documentation needs. The native implementation leverages source generators to reduce runtime overhead.
### Simplified Configuration
No need extra setup or 3rd party integrations. Just by defining controllers and endpoints, ASP.NET Core automatically generates OpenAPI specifications.
### Well Integration with Minimal APIs
[Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis) introduced in .NET 6. There's an optimized built-in support for Minimal APIs. It automatically adds metadata for routes, request parameters, and responses.
### Compatibility with Existing Tools
You can still use the output of OpenAPI with Swagger or NSwag... So it doesn't mean that in this case you have only one option when you use OpenAPI.
---
How to Use the New OpenAPI in .NET9?
------------------------------------
When you create a new ASP.NET Core project, you can see the below checkbox to add OpenAPI.
![New .NET 9 Project Screen](img5.png)
I created a new .NET 9 web project, I saw that OpenAPI had already been added.
![Package Reference](img4.png)
## Add OpenAPI Support For Your Existing Project
Upgrade your project to .NET 9 and add the required NuGet package [Microsoft.AspNetCore.OpenApi](https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi)
```
dotnet add package Microsoft.AspNetCore.OpenApi
```
###
Add the following services and middleware in `Program.cs`
```
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi(); //<<-----
var app = builder.Build();
app.MapOpenApi(); //<<-----
app.MapGet("/", () => "Test");
app.Run();
```
Your OpenAPI document URL is [_https://localhost:7077/openapi/v1.json_](https://localhost:7077/openapi/v1.json)
Change the port to your active port. This is how it looks like:
![Web UI of the Documentation](img1.png)
---
Alternative 3rd Party Tool: Scalar
==================================
**Scalar** is an open-source API platform for RestAPI documentation. Also, it provides an interface for interacting with RESTful API. Generates interactive and user-friendly API documentation. Supports OpenAPI and Swagger specifications. It’s open-source with **7K stars** on GitHub.
See the repo 👉 [https://github.com/scalar/scalar](https://github.com/scalar/scalar).
That's all from the replacement of Swagger in .NET 9.
Happy coding 👨‍💻
**References**
* [https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-8.0#openapi](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-8.0#openapi)

202
docs/en/Community-Articles/2024-11-14-Csharp-13-Features/Post.md

@ -0,0 +1,202 @@
# C# 13 Features
C# 13 is the latest version of C# and it comes with a lot of new features. In this article, we will discuss some of the new features of C# 13.
## `params` collections
With the C# 13, method parameter with `params` keyword isn't limited to be an array. You can now use any collection type that implements `IEnumerable<T>` interface.
Let's see how it can help us in our code.
```csharp
public IEnumerable<int> GetOdds(params IEnumerable<int> numbers)
{
foreach (var number in numbers)
{
if (number % 2 != 0)
{
Console.WriteLine(number);
}
}
}
```
## New lock object
I'm sure you have used `lock` statement in your code to synchronize access to a shared resource. With C# 13, you can now use a new lock object that is more efficient than the traditional lock object.
The new `Lock` type provides better thread synchronization through its API. When `Lock.EnterScope()` method is called, it returns a struct named `Scope` that contains a `Dispose` method. The `Dispose` method is called when the `Scope` object goes out of scope, which releases the lock. C# `using` statement recognizes the `Dispose` method and calls it automatically like it does with other `IDisposable` objects.
It was something similar before:
```csharp
private object _lock = new();
public void DoSomething()
{
lock (_lock)
{
// Do something
}
}
```
Now, you can use the new lock object like this:
```csharp
System.Threading.Lock x = new System.Threading.Lock();
public void DoSomething()
{
using (x.EnterScope())
{
// Do something
}
}
```
## New escape sequence
In C# 13, a new escape sequence `\e` has been introduced to represent the `ESCAPE` character, Unicode `U+001B`. Previously, you had to use `\u001b` or `\x1b` to represent this character. The new `\e` escape sequence simplifies this process and avoids potential issues with hexadecimal digits following `\x1b`.
> You can check [here](https://en.wikipedia.org/wiki/ANSI_escape_code#C0_control_codes) for ANSI escape codes.
## Implicit index access
The implicit "from the end" index operator, `^`, is now allowed in an object initializer expression.
It was not possible before, but now you can do this:
```csharp
var countdown = new TimerRemaining()
{
buffer =
{
[^1] = 0,
[^2] = 1,
[^3] = 2,
[^4] = 3,
[^5] = 4,
[^6] = 5,
[^7] = 6,
[^8] = 7,
[^9] = 8,
[^10] = 9
}
};
```
It's a great feature that makes the code more readable and maintainable. Still not a big deal, but it's nice to have it.
## `ref` and `unsafe` in iterators and async methods
In C# 13, the restrictions on using `ref` and `unsafe` constructs in iterators and async methods have been relaxed. Previously, you couldn't declare local `ref` variables or use unsafe contexts in these methods. Now, you can declare ref local variables and use unsafe contexts in async methods and iterators, provided they are not accessed across `await` or `yield` boundaries
This change allows for more expressive and efficient code, especially when working with types like `System.Span<T>` and `System.ReadOnlySpan<T>`. The compiler ensures that these constructs are used safely, and it will notify you if any safety rules are violated.
You can read more about this feature on the [Microsoft Learn page](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-13.0/ref-unsafe-in-iterators-async).
## More partial members
In C# 13, the concept of partial members has been expanded to include partial properties and partial indexers. Previously, only methods could be defined as partial members. This means you can now split the definition of properties and indexers across multiple files, just like you could with methods.
For example, you can declare a partial property in one part of your class and implement it in another part. Here's a simple illustration:
```csharp
public partial class MyClass
{
// Declaring declaration
public partial string MyProperty { get; set; }
}
public partial class MyClass
{
// Implementing declaration
private string _myProperty;
public partial string MyProperty
{
get => _myProperty;
set => _myProperty = value;
}
}
```
This feature allows for better organization and modularization of your code, especially in large projects where different parts of a class might be implemented by different team members.
## Overload resolution priority
What does "Overload resolution priority" section mean in this page?
In C# 13, the OverloadResolutionPriority attribute allows library authors to specify which method overload should be preferred by the compiler when multiple overloads are available. This attribute helps avoid ambiguity and ensures that the most appropriate overload is chosen, even if it might not be the most obvious choice based on traditional overload resolution rules.
This may be useful in scenarios where you have multiple overloads that are equally valid, but you want to prioritize one over the others. The attribute can be applied to a method or constructor to indicate its priority in the overload resolution process. It can prevent unexpected behavior and make your code more predictable and maintainable.
Let me show with an example:
```csharp
public class Example
{
// Existing method
public void Display(string message = "Hello!")
{
Console.WriteLine("Message: " + message);
}
// New, more efficient method with higher priority
[OverloadResolutionPriority(1)]
public void Display(string message = "Hello!", int repeatCount = 3)
{
for (int i = 0; i < repeatCount; i++)
{
Console.WriteLine("Message: " + message);
}
}
}
class Program
{
static void Main()
{
Example example = new Example();
// Normally, you can't compile this code because of ambiguity:
example.Display();
}
}
```
Output:
```
Message: Hello!
Message: Hello!
Message: Hello!
```
## The `field` keyword
n C# 13, the `field` keyword is introduced as a preview feature to simplify property accessors. This keyword allows you to reference the compiler-generated backing `field` directly within a property accessor, eliminating the need to declare an explicit backing `field` in your type declaration.
For example, instead of writing:
```csharp
private int _value;
public int Value
{
get => _value;
set => _value = value;
}
```
You can now write:
```csharp
public int Value
{
get => field;
set => field = value;
}
```
This makes your code cleaner and more concise. However, be cautious if you have a `field` named `field` in your class, as it could cause confusion. You can disambiguate by using `@field` or `this.field`.
Make sure you're using the latest `LangVersion` in your `.csproj` project file to enable this feature.
```xml
<LangVersion>preview</LangVersion>
```

165
docs/en/Community-Articles/2024-11-14-EF-Core-9-Linq-SQL-Translation/POST.md

@ -0,0 +1,165 @@
# EF Core 9 LINQ & SQL translation
EF Core improves the translation of LINQ queries to SQL with every release. EF Core 9 is no exception. This article will show you some of the improvements in EF Core 9.
EF Core 9 includes a lot of improvements in LINQ to SQL translation. we don't cover all of them in this article. You can find more information in the [official release notes](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#linq-and-sql-translation).
## Support for complex types
### GroupBy
EF Core now supports grouping by complex type instance. For example:
```csharp
var groupedAddress = await context.Customers
.GroupBy(c => new { c.Address })
.Select(g => new { g.Key, Count = g.Count() })
.ToListAsync();
```
Address is a complex type as a value object here.
### ExecuteUpdate
EF Core now supports updating a complex type. For example:
```csharp
var newAddress = new Address("New Street", "New City", "New Country");
await context.Customers
.Where(e => e.Region == "Turkey")
.ExecuteUpdateAsync(s => s.SetProperty(b => b.Address, newAddress));
```
EF Core updates each column of the complex type.
## Prune unneeded elements from SQL
Ef Core now translates LINQ queries to SQL more efficiently. It will remove unneeded elements from the SQL query and bring better performance.
### Table pruning
When you use table-per-hierarchy (TPH) inheritance, previously EF Core generated SQL queries that included JIONs to tables that were not needed.
For example:
```csharp
public class Order
{
public int Id { get; set; }
...
public Customer Customer { get; set; }
}
public class DiscountedOrder : Order
{
public double Discount { get; set; }
}
public class Customer
{
public int Id { get; set; }
...
public List<Order> Orders { get; set; }
}
public class AppContext : DbContext
{
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().UseTptMappingStrategy();
}
}
```
Consider the following query to get all customers with at least one order:
```csharp
var customers = await context.Customers.Where(o => o.Orders.Any()).ToListAsync();
```
Previously, EF Core generated the following SQL query:
```sql
SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE EXISTS (
SELECT 1
FROM [Orders] AS [o]
LEFT JOIN [DiscountedOrders] AS [d] ON [o].[Id] = [d].[Id]
WHERE [c].[Id] = [o].[CustomerId])
```
It included a JOIN to the `DiscountedOrders` table, which was not needed. In EF Core 9, the generated SQL query is:
```sql
SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE EXISTS (
SELECT 1
FROM [Orders] AS [o]
WHERE [c].[Id] = [o].[CustomerId])
```
## EF Core in ABP
ABP Framework is built on top of the latest technologies. It will support EF Core 9 as soon as it is released. You can use the latest features of EF Core in your ABP applications.
For example, you can use the `ExecuteUpdateAsync` method in your ABP application:
```csharp
public class Book : FullAuditedAggregateRoot<Guid>
{
public string Name { get; set; }
public float Price { get; set; }
public string Author { get; set; }
}
public class AppContext : AbpDbContext<AppContext>
{
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable("Books");
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.Property(x => x.Author).IsRequired().HasMaxLength(64);
});
}
}
public class BookRepository : EfCoreRepository<AppContext, Book, Guid>, IBookRepository
{
public BookRepository(IDbContextProvider<AppContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task UpdatePriceByAuthorAsync(string author, float price)
{
await (await GetDbSetAsync())
.Where(b => b.Author == author)
.ExecuteUpdateAsync(b => b.SetProperty(x => x.Price, price));
}
}
```
* `FullAuditedAggregateRoot` is an aggregate root base class with auditing properties provided by ABP Framework.
* `IRepository` is a generic repository interface provided by ABP Framework that provides CRUD operations and you can use EF Core's API in your entity repository implementation.
## References
* [LINQ and SQL translation](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#linq-and-sql-translation)
* [ABP Entity Framework Core Integration](https://abp.io/docs/latest/framework/data/entity-framework-core)
* [ABP Entities](https://abp.io/docs/latest/framework/architecture/domain-driven-design/entities)

309
docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md

@ -0,0 +1,309 @@
# ABP Global Assets - New way to bundle JavaScript/CSS files in Blazor WebAssembly app
We have introduced a new feature in the ABP framework to bundle the `JavaScript/CSS` files in the Blazor wasm app. This feature is called `Global Assets`.
With this feature, you don't need to run the `abp bundle` command to manually create/maintain the `global.js` and `global.css` files in your Blazor wasm app.
## How Global Assets works?
The new `Blazor wasm app` has two projects:
1. `MyProjectName` (ASP.NET Core app)
2. `MyProjectName.Client` (Blazor wasm app)
The `MyProjectName` reference the `MyProjectName.Client` project, and will be the entry point of the application, which means the `MyProjectName` project will be the `host` project of the `MyProjectName.Client` project.
The static/virtual files of `MyProjectName` can be accessed by the `MyProjectName.Client` project, so we can create dynamic global assets in the `MyProjectName` project and use them in the `MyProjectName.Client` project.
## How it works in ABP?
We have created a new package `WebAssembly.Theme.Bundling` for the theme `WebAssembly` module and used the `Volo.Abp.AspNetCore.Mvc.UI.Bundling.BundleContributor` to add `JavaScript/CSS` files to the bundling system.
* LeptonXLiteTheme: `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule`
* LeptonXTheme: `AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule`
* LeptonTheme: `AbpAspNetCoreComponentsWebAssemblyLeptonThemeBundlingModule`
* BasicTheme: `AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule`
The new `ThemeBundlingModule` only depends on `AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule(new package)`. It's an `abstractions module`, which only depends on `AbpAspNetCoreMvcUiBundlingAbstractionsModule`.
We will get all `JavaScript/CSS` files on `OnApplicationInitializationAsync` method of `AbpAspNetCoreMvcUiBundlingModule` from bundling system and add them to `IDynamicFileProvider` service. After that, we can access the `JavaScript/CSS` files in the Blazor wasm app.
## Add the Global Assets in the module
If your module has `JavaScript/CSS` files that need to the bundling system, You have to create a new project(`YourModuleName.Blazor.WebAssembly.Bundling`) to your module solution, and reference the new project in the `MyProjectName` project and module dependencies.
The new project should **only** depend on the `AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule` and define `BundleContributor` classes to contribute the `JavaScript/CSS` files.
> Q: The new project(`YourModuleName.Blazor.WebAssembly.Bundling`) doesn't have the `libs/myscript.js` and `libs/myscript.css` files why the files can be added to the bundling system?
> A: Because the `MyProjectName.Client` will depend on the `MyBlazorModule(YourModuleName.Blazor)` that contains the `JavaScript/CSS` files, The `MyProjectName` is referencing the `MyProjectName.Client` project, so the `MyProjectName` project can access the `JavaScript/CSS` files in the `MyProjectName.Client` project and add them to the bundling system.
```csharp
[DependsOn(
typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule)
)]
public class MyBlazorWebAssemblyBundlingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBundlingOptions>(options =>
{
// Script Bundles
options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global).AddContributors(typeof(MyModuleBundleScriptContributor));
// Style Bundles
options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global).AddContributors(typeof(MyModuleBundleStyleBundleContributor));
});
}
}
```
```csharp
public class MyModuleBundleScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/myscript.js");
}
}
public class MyModuleBundleStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/myscript.css");
}
}
```
## Use the Global Assets in the Blazor WASM
### MyCompanyName.MyProjectName.Blazor
Convert your `MyCompanyName.MyProjectName.Blazor` project to integrate the `ABP module` system and depend on the `AbpAspNetCoreMvcUiBundlingModule` and `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule`:
* The `AbpAspNetCoreMvcUiBundlingModule` uses to create the `JavaScript/CSS` files to virtual files.
* The `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` uses to add theme `JavaScript/CSS` to the bundling system.
Here is how your project files look like:
**`Program.cs`:**
```csharp
public class Program
{
public async static Task<int> Main(string[] args)
{
//...
var builder = WebApplication.CreateBuilder(args);
builder.Host.AddAppSettingsSecretsJson()
.UseAutofac()
.UseSerilog();
await builder.AddApplicationAsync<MyProjectNameBlazorModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
return 0;
//...
}
}
```
**`MyProjectNameBlazorModule.cs`:**
```csharp
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcUiBundlingModule),
typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule) //Should be added!
)]
public class MyProjectNameBlazorModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//https://github.com/dotnet/aspnetcore/issues/52530
Configure<RouteOptions>(options =>
{
options.SuppressCheckForUnhandledSecurityMetadata = true;
});
// Add services to the container.
context.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var env = context.GetEnvironment();
var app = context.GetApplicationBuilder();
// Configure the HTTP request pipeline.
if (env.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.MapAbpStaticAssets();
app.UseRouting();
app.UseAntiforgery();
app.UseConfiguredEndpoints(builder =>
{
builder.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(WebAppAdditionalAssembliesHelper.GetAssemblies<MyProjectNameBlazorClientModule>());
});
}
}
```
**`MyCompanyName.MyProjectName.Blazor.csproj`:**
```xml
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0.0" />
<PackageReference Include="Volo.Abp.Autofac" Version="9.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Bundling" Version="9.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme.Bundling" Version="9.0.0" />
<!-- <PackageReference Include="Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme.Bundling" Version="9.0.0" /> --> if you're using LeptonXTheme
<ProjectReference Include="..\MyProjectName.Blazor.Client\MyProjectName.Blazor.Client.csproj" />
</ItemGroup>
```
### BlazorWebAssemblyBundlingModule in the ABP commercial
Here is the list of `Bundling Modules` in the ABP commercial. If you're using the pro template, you should add them to the `MyCompanyName.MyProjectName.Blazor` project.
| BundlingModules | Nuget Package |
|---------------------------------------------|-----------------------------------------------------|
| AbpAuditLoggingBlazorWebAssemblyBundlingModule | Volo.Abp.AuditLogging.Blazor.WebAssembly.Bundling |
| FileManagementBlazorWebAssemblyBundlingModule | Volo.FileManagement.Blazor.WebAssembly.Bundling |
| SaasHostBlazorWebAssemblyBundlingModule | Volo.Saas.Host.Blazor.WebAssembly.Bundling |
| ChatBlazorWebAssemblyBundlingModule | Volo.Chat.Blazor.WebAssembly.Bundling |
| CmsKitProAdminBlazorWebAssemblyBundlingModule | Volo.CmsKit.Pro.Admin.Blazor.WebAssembly.Bundling |
### MyCompanyName.MyProjectName.Blazor.Client
1. Remove the `global.JavaScript/CSS` files from the `MyCompanyName.MyProjectName.Blazor`'s `wwwroot` folder.
2. Remove the `AbpCli:Bundle` section from the `appsettings.json` file.
3. Remove all BundleContributor classes that inherit from IBundleContributor. Then, create `MyProjectNameStyleBundleContributor` and `MyProjectNameScriptBundleContributor` classes to add your style and JavaScript files. Finally, add them to `AbpBundlingOptions`.
```cs
public class MyProjectNameStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add(new BundleFile("main.css", true));
}
}
public class MyProjectNameScriptBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add(new BundleFile("main.js", true));
}
}
```
```cs
Configure<AbpBundlingOptions>(options =>
{
var globalStyles = options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global);
globalStyles.AddContributors(typeof(MyProjectNameStyleBundleContributor));
var globalScripts = options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global);
globalScripts.AddContributors(typeof(MyProjectNameScriptBundleContributor));
});
```
## Use the Global Assets in the Blazor WebApp
### MyCompanyName.MyProjectName.Blazor.WebApp
Depending on the `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` in your `MyCompanyName.MyProjectName.Blazor.WebApp` project.
* The `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` uses to add theme `JavaScript/CSS` to the bundling system.
### BlazorWebAssemblyBundlingModule in the ABP commercial
Here is the list of `Bundling Modules` in the ABP commercial. If you're using the pro template, you should add them to the `MyCompanyName.MyProjectName.Blazor.WebApp` project.
| BundlingModules | Nuget Package |
|---------------------------------------------|-----------------------------------------------------|
| AbpAuditLoggingBlazorWebAssemblyBundlingModule | Volo.Abp.AuditLogging.Blazor.WebAssembly.Bundling |
| FileManagementBlazorWebAssemblyBundlingModule | Volo.FileManagement.Blazor.WebAssembly.Bundling |
| SaasHostBlazorWebAssemblyBundlingModule | Volo.Saas.Host.Blazor.WebAssembly.Bundling |
| ChatBlazorWebAssemblyBundlingModule | Volo.Chat.Blazor.WebAssembly.Bundling |
| CmsKitProAdminBlazorWebAssemblyBundlingModule | Volo.CmsKit.Pro.Admin.Blazor.WebAssembly.Bundling |
### MyCompanyName.MyProjectName.Blazor.WebApp.Client
1. Remove the `global.JavaScript/CSS` files from the `MyCompanyName.MyProjectName.Blazor.WebApp.Client`'s `wwwroot` folder.
2. Remove the `AbpCli:Bundle` section from the `appsettings.json` file.
3. Remove all BundleContributor classes that inherit from IBundleContributor. Then, create `MyProjectNameStyleBundleContributor` and `MyProjectNameScriptBundleContributor` classes to add your style and JavaScript files. Finally, add them to `AbpBundlingOptions`.
```cs
public class MyProjectNameStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add(new BundleFile("main.css", true));
}
}
public class MyProjectNameScriptBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add(new BundleFile("main.js", true));
}
}
```
```cs
Configure<AbpBundlingOptions>(options =>
{
var globalStyles = options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global);
globalStyles.AddContributors(typeof(MyProjectNameStyleBundleContributor));
var globalScripts = options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global);
globalScripts.AddContributors(typeof(MyProjectNameScriptBundleContributor));
});
```
### Check the Global Assets
Run the `MyProject` project and check the `https://localhost/global.js` and `https://localhost/global.css` files. You should be able to see the `JavaScript/CSS` files content from the Bundling system:
![global](image.png)
## GlobalAssets(AbpBundlingGlobalAssetsOptions)
You can configure the JavaScript and CSS file names in the `GlobalAssets` property of the `AbpBundlingOptions` class.
The default values are `global.js` and `global.css`.
## Conclusion
With the new `Global Assets` feature, you can easily bundle the `JavaScript/CSS` files in the Blazor wasm app. This feature is very useful for the Blazor wasm app, and it will save you a lot of time and effort. We hope you will enjoy this feature and use it in your projects.
## References
* [Virtual Files](https://docs.abp.io/en/abp/latest/Virtual-Files)
* [Bundle Contributors](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/bundling-minification#bundle-contributors)
* [Global Assets Pull Request](https://github.com/abpframework/abp/pull/19968)

BIN
docs/en/Community-Articles/2024-11-25-Global-Assets/image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

385
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md

@ -0,0 +1,385 @@
# How to Use OpenAI API with ABP Framework
In this article, I will show you how to integrate and use the [OpenAI API](https://github.com/openai/openai-dotnet?tab=readme-ov-file#getting-started) with the [ABP Framework](https://abp.io/). We will explore step-by-step how these technologies can work together to enhance your application with powerful AI capabilities, such as natural language processing, image generation, and more.
![cover-image](cover-image.png)
## Creating an ABP Project
To begin integrating OpenAI API with ABP Framework, you first need to create an ABP project. Follow these steps to create and set up your ABP project:
### Step 1: Install ABP CLI
The ABP CLI is a command-line interface tool that helps you create and manage ABP projects easily. To install the ABP CLI, run the following command in your terminal:
```bash
dotnet tool install -g Volo.Abp.Studio.Cli
```
### Step 2: Create a New ABP Project
Once you have installed the ABP CLI, you can create a new ABP project using the following command:
```bash
abp new Acme.OpenAIIntegration -t app --ui-framework mvc --database-provider ef -dbms PostgreSQL --csf
```
> This command will generate a complete ABP project with an [MVC UI](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/overall). The examples provided in this article make use of UI controllers for demonstration purposes. However, the same approach can easily be applied to other UI types supported by ABP, such as Blazor or Angular. You can find other options [here](https://abp.io/docs/latest/cli).
## OpenAI Integration Setup
To begin integrating OpenAI API with ABP Framework, follow these steps:
### Step 1: Create an API Key
To use the OpenAI services, you first need an API key. To obtain one, first [create a new OpenAI account](https://platform.openai.com/signup) or [log in](https://platform.openai.com/login). Next, navigate to the [API key page](https://platform.openai.com/account/api-keys) and select "Create new secret key", optionally naming the key. Make sure to save your API key somewhere safe and do not share it with anyone.
This key will be used to authenticate your application when making requests to the OpenAI endpoints.
### Step 2: Adding *Microsoft.Extensions.AI* Package
To integrate OpenAI API with ABP, we use [Microsoft.Extensions.AI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI/). This package offers a unified API for integrating AI services, making it easy for developers to work with different AI providers. You can find more details in [this blog post](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/).
To begin integrating OpenAI API with ABP Framework, follow these steps:
1. Add the **Microsoft.Extensions.AI** and **Microsoft.Extensions.AI.OpenAI** (used to interact specifically with OpenAI services. Additionally, this package has alternatives like [Azure OpenAI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI/), [Azure AI Inference](https://www.nuget.org/packages/Microsoft.Extensions.AI.AzureAIInference/), and [Ollama](https://www.nuget.org/packages/Microsoft.Extensions.AI.Ollama/), offering flexibility for developers to choose the AI provider that best fits their needs) packages:
```bash
dotnet add package Microsoft.Extensions.AI --prerelease
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease
```
2. Add the required configuration to the `appsettings.json` file located inside the `Acme.OpenAIIntegration.Web` project and dependencies to your `ConfigureServices` method:
```json
"AI": {
"OpenAI": {
"Key": "YOUR-API-KEY",
"Chat": {
"ModelId": "gpt-4o-mini"
}
}
}
```
> Replace the value of the `Key` with your OpenAI API key.
Next, add the following code to the `ConfigureServices` method in `OpenAIIntegrationBlazorModule`:
```csharp
context.Services.AddSingleton(new OpenAIClient(configuration["AI:OpenAI:Key"]));
context.Services.AddChatClient(services =>
services.GetRequiredService<OpenAIClient>().AsChatClient(configuration["AI:OpenAI:Chat:ModelId"] ?? "gpt-4o-mini"));
```
## Creating a Sample Page
To demonstrate the use of OpenAI API, let's create a page named `Sample` in the `Acme.OpenAIIntegration.Web` project:
Create a `Sample` folder under the `Pages` folder of the `Acme.OpenAIIntegration.Web` project. Add a new Razor Page by right-clicking the `Sample` folder then selecting `Add > Razor Page`. Name it `Index`.
Open the `Index.cshtml` and change the whole content as shown below:
> Note: This example demonstrates a simple implementation of a sample page that interacts with the OpenAI API, covering chat, [retrieval-augmented generation (RAG)](https://github.com/openai/openai-dotnet?tab=readme-ov-file#how-to-use-assistants-with-retrieval-augmented-generation-rag), and image generation features. Each example is explained in detail in the next section, so feel free to continue for a better understanding of the steps and logic involved.
```html
@page
@model Acme.OpenAIIntegration.Web.Pages.Sample
@{
ViewData["Title"] = "OpenAI API Demonstration";
}
<h1>@ViewData["Title"]</h1>
<br/><br/>
<div class="row">
<div class="col-md-4">
<h2>Chat Example</h2>
<form method="post" asp-page-handler="Chat">
<div class="form-group">
<label asp-for="ChatInput">Enter your message:</label>
<textarea asp-for="ChatInput" class="form-control" rows="4"></textarea>
</div>
<button type="submit" class="btn btn-primary mt-2">Send</button>
</form>
@if (!string.IsNullOrEmpty(Model.ChatResponse))
{
<h3 class="mt-3">Response:</h3>
<p>@Model.ChatResponse</p>
}
</div>
<div class="col-md-4">
<h2>RAG Example</h2>
<form method="post" asp-page-handler="RAG">
<div class="form-group mt-2">
<label asp-for="RAGQuery">Query:</label>
<input asp-for="RAGQuery" class="form-control" />
</div>
<button type="submit" class="btn btn-primary mt-2">Ask</button>
</form>
@if (!string.IsNullOrEmpty(Model.RAGResponse))
{
<h3 class="mt-3">Result:</h3>
<p>@Model.RAGResponse</p>
}
</div>
<div class="col-md-4">
<h2>Image Generation Example</h2>
<form method="post" asp-page-handler="ImageGeneration">
<div class="form-group">
<label asp-for="ImagePrompt">Image Description:</label>
<input asp-for="ImagePrompt" class="form-control" />
</div>
<button type="submit" class="btn btn-primary mt-2">Generate Image</button>
</form>
@if (Model.GeneratedImageBytes != null)
{
<h3 class="mt-3">Generated Image:</h3>
<img src="data:image/png;base64,@Convert.ToBase64String(Model.GeneratedImageBytes)" alt="Generated image" class="img-fluid mt-2" />
}
</div>
</div>
```
`Index.cshtml.cs` content should be like that:
```csharp
using System;
using System.ClientModel;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Assistants;
using OpenAI.Files;
using OpenAI.Images;
namespace Acme.OpenAIIntegration.Web.Pages;
public class Sample : PageModel
{
[BindProperty]
public string ChatInput { get; set; }
public string ChatResponse { get; set; }
[BindProperty]
public string RAGQuery { get; set; }
public string RAGResponse { get; set; }
[BindProperty]
public string ImagePrompt { get; set; }
public byte[] GeneratedImageBytes { get; set; }
private readonly IChatClient _chatClient;
private readonly OpenAIClient _openAiClient;
public Sample(
IChatClient chatClient,
OpenAIClient openAiClient)
{
_chatClient = chatClient;
_openAiClient = openAiClient;
}
public async Task<IActionResult> OnPostChatAsync()
{
ChatResponse = $"Chat response: {(await _chatClient.CompleteAsync(ChatInput)).Message}";
return Page();
}
public async Task<IActionResult> OnPostRAGAsync()
{
#pragma warning disable OPENAI001
var fileClient = _openAiClient.GetOpenAIFileClient();
var assistantClient = _openAiClient.GetAssistantClient();
using var document = BinaryData.FromBytes(GetExceptionHandlingDocumentContent().ToArray()).ToStream();
var exceptionHandlingDoc = await fileClient.UploadFileAsync(
document,
"ExceptionHandling.md",
FileUploadPurpose.Assistants);
AssistantCreationOptions assistantOptions = new()
{
Name = "Exception Handling Assistant",
Instructions =
"""
This assistant helps you with exception handling in ABP Framework. You can ask questions about exception handling and get answers.
- Do not make any assumptions when asked for information that is not in the document
- Give the most accurate information possible
- Give short(max 1-2 sentence) and concise answers
- Do not provide file citations
""",
Tools =
{
new FileSearchToolDefinition(),
},
ToolResources = new()
{
FileSearch = new()
{
NewVectorStores =
{
new VectorStoreCreationHelper([exceptionHandlingDoc.Value.Id]),
}
}
},
};
var assistant = await assistantClient.CreateAssistantAsync("gpt-4o", assistantOptions);
ThreadCreationOptions threadOptions = new()
{
InitialMessages = { RAGQuery }
};
ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Value.Id, threadOptions);
do
{
Thread.Sleep(TimeSpan.FromSeconds(1));
threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id);
} while (!threadRun.Status.IsTerminal);
CollectionResult<ThreadMessage> messages
= assistantClient.GetMessages(threadRun.ThreadId,
new MessageCollectionOptions() { Order = MessageCollectionOrder.Ascending });
var response = new StringBuilder();
foreach (var message in messages)
{
response.AppendLine($"[{message.Role.ToString().ToUpper()}]: ");
foreach (var contentItem in message.Content)
{
if (!string.IsNullOrEmpty(contentItem.Text))
{
response.AppendLine(contentItem.Text);
if (contentItem.TextAnnotations.Count > 0)
{
response.AppendLine("");
}
}
}
response.AppendLine("");
#pragma warning restore OPENAI001
}
RAGResponse = response.ToString();
return Page();
}
public async Task<IActionResult> OnPostImageGenerationAsync()
{
var client = _openAiClient.GetImageClient("dall-e-3");
var image = await client.GenerateImageAsync(ImagePrompt, new ImageGenerationOptions
{
ResponseFormat = GeneratedImageFormat.Bytes
});
var imageBytes = image.Value.ImageBytes;
using var memoryStream = new MemoryStream();
await imageBytes.ToStream().CopyToAsync(memoryStream);
GeneratedImageBytes = memoryStream.ToArray();
return Page();
}
public ReadOnlySpan<byte> GetExceptionHandlingDocumentContent()
{
return """
# Exception Handling
ABP provides a built-in infrastructure and offers a standard model for handling exceptions.
* Automatically **handles all exceptions** and sends a standard **formatted error message** to the client for an API/AJAX request.
* Automatically hides **internal infrastructure errors** and returns a standard error message.
* Provides an easy and configurable way to **localize** exception messages.
* Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map custom exceptions.
## Automatic Exception Handling
`AbpExceptionFilter` handles an exception if **any of the following conditions** are met:
* Exception is thrown by a **controller action** which returns an **object result** (not a view result).
* The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`).
* Client explicitly accepts the `application/json` content type (via `accept` HTTP header).
If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client.
## Business Exceptions
Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception.
`BusinessException` implements the `IBusinessException` interface in addition to the `IHasErrorCode`, `IHasErrorDetails` and `IHasLogLevel` interfaces. The default log level is `Warning`.
Usually you have an error code related to a particular business exception. For example:
````C#
throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);
````
### User Friendly Exception
If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client.
`UserFriendlyException` class is the built-in implementation of the `IUserFriendlyException` interface. Example usage:
````C#
throw new UserFriendlyException(
"Username should be unique!"
);
````
* The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class.
"""u8;
}
}
```
## Running the Application
After completing the setup, you can run the application using the following command:
```bash
dotnet run --project ./src/Acme.OpenAIIntegration.Web
```
Once the application is running, open your browser and navigate to `/Sample`. You should see the `Sample` page we created, which contains sections for Chat, RAG (Retrieval-Augmented Generation), and Image Generation. You can find the screenshot of the page below:
![sample page](sample-page.png)
## Examples Overview
To showcase the integration of the OpenAI API with the ABP Framework, we implemented three different examples:
1. **Chat Example**: This example demonstrates how to use OpenAI's chat capabilities by allowing users to enter a message and receive an AI-generated response. The implementation involves setting up a simple form on the `Sample` page where users can input their message. The form submission triggers the `OnPostChatAsync` method, which uses the `IChatClient` to generate a response.
![chat-example](chat-example.gif)
2. **Retrieval-Augmented Generation (RAG) Example**: In this example, we use OpenAI to answer user queries by referencing custom documents uploaded to the OpenAI API. The implementation involves uploading a document using the `OpenAIFileClient` and creating an assistant with specific instructions to handle the uploaded content. In this case, the document is a section from ABP's Exception Handling documentation, which includes examples on how ABP handles exceptions, user-friendly error messages, and business exceptions. Users can input their query on the `Sample` page, and the `OnPostRAGAsync` method processes the query to generate precise answers based on the document content. If users ask questions that are not covered in the document, the assistant clearly indicates that the information is not available, as per the instructions provided. For example, when asked about `Object Extensions`, the response begins with: "The uploaded document does not contain information about `Object Extensions`...". This demonstrates how the assistant adheres to the provided instructions. You can also find this example illustrated in the GIF below.
![rag-example-1](rag-example-1.gif)
![rag-example-2](rag-example-2.gif)
3. **Image Generation Example**: This example leverages the [DALL-E](https://openai.com/index/dall-e-3/) model to generate images based on user-provided prompts. On the `Sample` page, users can provide a description of the image they want to generate, and the `OnPostImageGenerationAsync` method uses the `OpenAIClient` to generate the image.
![image-generation-example](image-generation-example.gif)
## Conclusion
In this article, we covered how to integrate the OpenAI API with the ABP Framework by creating a sample project, setting up the OpenAI services, and implementing examples for conversational AI, knowledge-based assistance, and image generation. By following these steps, you can add powerful AI-driven capabilities to your application, making it more interactive, intelligent, and capable of meeting user needs effectively.

BIN
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/chat-example.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

BIN
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/image-generation-example.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

BIN
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-1.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-2.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
docs/en/Community-Articles/2024-12-01-OpenAI-Integration/sample-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

234
docs/en/Community-Articles/2024-12-09-Unit-Test/POST.md

@ -0,0 +1,234 @@
# The new Unit Test structure in ABP application
A typical ABP modular project usually consists of three main projects: `Application`, `Domain`, and `EntityFrameworkCore/MongoDB`. In these projects, we may provide many services that require unit testing.
Using abstract unit test classes involves first writing tests in the `Application` and `Domain` layers that are independent of the storage technology, ensuring the correctness of core business logic. These abstract tests are then implemented in `EntityFrameworkCore` or `MongoDB`. The benefits of this approach include:
1. **Reduced Coupling**: Core logic tests do not depend on specific storage technologies, so switching databases does not require rewriting test code.
2. **Better Isolation**: Focuses on verifying business logic correctness, avoiding interference from database operations.
3. **Increased Reusability**: The same abstract tests can be reused with different storage implementations.
4. **Easier Maintenance and Extensibility**: Different storage implementations can be extended independently without breaking existing tests.
5. **Faster and More Reliable Tests**: Reduces dependency on databases, making tests faster and more stable.
## How to migrate old unit tests to the new unit test structure
Assume our project name is `MyCompanyName.MyProjectName`.
### Changes to the `MyCompanyName.MyProjectName.Application.Tests` project:
1. Remove the `MyCompanyName.MyProjectName.Application.Tests` project's `MyProjectNameApplicationCollection` class.
2. Modify the `MyCompanyName.MyProjectName.Application.Tests` project's `MyProjectNameApplicationTestBase` class.
```csharp
public abstract class MyProjectNameApplicationTestBase<TStartupModule> : MyProjectNameTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
//...
}
```
3. Modify the `MyCompanyName.MyProjectName.Application.Tests` project's unit test classes to become abstract unit test classes, such as: `SampleAppServiceTests`.
```csharp
public abstract class SampleAppServiceTests<TStartupModule> : MyProjectNameApplicationTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
[Fact]
public async Task Initial_Data_Should_Contain_Admin_User()
{
//...
}
}
```
### Changes to the `MyCompanyName.MyProjectName.Domain.Tests` project:
1. Remove the `MyCompanyName.MyProjectName.Domain.Tests` project's `MyProjectNameDomainCollection` class.
2. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's `MyProjectNameDomainTestBase` class.
```csharp
public abstract class MyProjectNameDomainTestBase<TStartupModule> : MyProjectNameTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
//...
}
```
3. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's unit test classes to become abstract unit test classes, such as: `SampleDomainTests`.
```csharp
public abstract class SampleDomainTests<TStartupModule> : MyProjectNameDomainTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
[Fact]
public async Task Should_Set_Email_Of_A_User()
{
//...
}
}
```
4. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's `csproj` and module class. Remove references to `EntityFrameworkCore/MongoDB`.
`MyCompanyName.MyProjectName.Domain.Tests.csproj`:
```xml
<Project Sdk="Microsoft.NET.Sdk">
//...
<ItemGroup>
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.Domain\MyCompanyName.MyProjectName.Domain.csproj" />
<ProjectReference Include="..\MyCompanyName.MyProjectName.TestBase\MyCompanyName.MyProjectName.TestBase.csproj" />
</ItemGroup>
</Project>
```
`MyProjectNameDomainTestModule.cs`:
```csharp
[DependsOn(
typeof(MyProjectNameDomainModule),
typeof(MyProjectNameTestBaseModule)
)]
public class MyProjectNameDomainTestModule : AbpModule
{
//...
}
```
### Changes to the `MyCompanyName.MyProjectName.EntityFrameworkCore.Tests` project:
Here, we need to create implementation classes for all abstract unit tests.
```csharp
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]
public class EfCoreSampleAppServiceTests : SampleAppServiceTests<MyProjectNameEntityFrameworkCoreTestModule>
{
//...
}
```
```csharp
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]
public class EfCoreSampleDomainTests : SampleDomainTests<MyProjectNameEntityFrameworkCoreTestModule>
{
//...
}
```
We also need to modify the project's dependencies and module class, which should directly or indirectly reference the `Application` and `Domain` test projects.
`MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj`:
```xml
<Project Sdk="Microsoft.NET.Sdk">
//...
<ItemGroup>
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.EntityFrameworkCore\MyCompanyName.MyProjectName.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\MyCompanyName.MyProjectName.Application.Tests\MyCompanyName.MyProjectName.Application.Tests.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj" />
</ItemGroup>
</Project>
```
`MyProjectNameEntityFrameworkCoreTestModule.cs`:
```csharp
[DependsOn(
typeof(MyProjectNameApplicationTestModule),
typeof(MyProjectNameEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class MyProjectNameEntityFrameworkCoreTestModule : AbpModule
{
//...
}
```
### Changes to the `MyCompanyName.MyProjectName.MongoDB.Tests` project (skip this step if not using MongoDB):
Like the `EntityFrameworkCore` project, we need to create implementation classes for all abstract unit tests and modify the project's dependencies and module class.
```csharp
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]
public class MongoDBSampleAppServiceTests : SampleAppServiceTests<MyProjectNameMongoDbTestModule>
{
//...
}
```
```csharp
[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]
public class MongoDBSampleDomainTests : SampleDomainTests<MyProjectNameMongoDbTestModule>
{
//...
}
```
```xml
<Project Sdk="Microsoft.NET.Sdk">
//...
<ItemGroup>
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.MongoDB\MyCompanyName.MyProjectName.MongoDB.csproj" />
<ProjectReference Include="..\MyCompanyName.MyProjectName.Application.Tests\MyCompanyName.MyProjectName.Application.Tests.csproj" />
</ItemGroup>
</Project>
```
```csharp
[DependsOn(
typeof(MyProjectNameApplicationTestModule),
typeof(MyProjectNameMongoDbModule)
)]
public class MyProjectNameMongoDbTestModule : AbpModule
{
//...
}
```
### Changes to the `MyCompanyName.MyProjectName.Web.Tests` project:
We need to reference the `EntityFrameworkCore/MongoDB` test projects in this test project.
```xml
<Project Sdk="Microsoft.NET.Sdk">
//...
<ItemGroup>
<ProjectReference Include="..\MyCompanyName.MyProjectName.Application.Tests\MyCompanyName.MyProjectName.Application.Tests.csproj" />
<ProjectReference Include="..\..\src\MyCompanyName.MyProjectName.Web\MyCompanyName.MyProjectName.Web.csproj" />
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.AspNetCore.TestBase\Volo.Abp.AspNetCore.TestBase.csproj" />
<ProjectReference Include="..\MyCompanyName.MyProjectName.EntityFrameworkCore.Tests\MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj" />
</ItemGroup>
</Project>
```
```csharp
[DependsOn(
typeof(AbpAspNetCoreTestBaseModule),
typeof(MyProjectNameWebModule),
typeof(MyProjectNameApplicationTestModule),
typeof(MyProjectNameEntityFrameworkCoreTestModule)
)]
public class MyProjectNameWebTestModule : AbpModule
{
//...
}
```
We no longer need the `MyProjectNameWebCollection` class in this project. Please delete it and use `[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]` instead.
## Conclusion
This is our new unit test structure. Decoupling unit tests from storage technologies ensures the independence of business logic and allows easy switching between storage implementations. Abstract unit test classes improve test reusability, maintainability, and efficiency, reducing refactoring costs and providing flexibility for future tech updates.
## References
- [Unit Test](https://abp.io/docs/latest/testing/unit-tests)
- [Abstract all db-related unit tests](https://github.com/abpframework/abp/pull/17880)

12
docs/en/cli/index.md

@ -112,7 +112,7 @@ abp cli clear-cache
### new
Generates a new solution based on the ABP [startup templates](../solution-templates).
Generates a new solution based on the ABP [startup templates](../solution-templates). See [new solution create sample commands](new-command-samples.md)
Usage:
@ -211,6 +211,7 @@ For more samples, go to [ABP CLI Create Solution Samples](new-command-samples.md
* `leptonx`: LeptonX Theme.
* `basic`: Basic Theme.
* `--public-website`: Public Website is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. This option is only included in PRO templates.
* `--no-grafana-dashboard` or `-ngd`: Does not add example Grafana Dashboard to the solution.
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--local-framework-ref` or `-lfr`: Uses local projects references to the ABP framework instead of using the NuGet packages. It tries to find the paths from `ide-state.json`. The file is located at `%UserProfile%\.abp\studio\ui\ide-state.json` (for Windows) and `~/.abp/studio/ui/ide-state.json` (for MAC).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
@ -225,15 +226,16 @@ For more samples, go to [ABP CLI Create Solution Samples](new-command-samples.md
* `--dont-run-bundling`: Skip bundling for Blazor packages.
* `--no-kubernetes-configuration` or `-nkc`: Skips the Kubernetes configuration files.
* `--no-social-logins` or `-nsl`: Skipts the social login configuration.
* *Module Options*: You can skip some modules if you don't want to add them to your solution (*Available for* ***Team*** *or higher licenses*). Available commands:
* `--no-tests` or `-ntp`: Does not add test projects.
* *Module Options*: You can skip some modules if you don't want to add them to your solution, or include if you want them (*Available for* ***Team*** *or higher licenses*). Available commands:
* `-no-saas`: Skips the Saas module.
* `-no-gdpr`: Skips the GDPR module.
* `-no-openiddict-admin-ui`: Skips the OpenIddict Admin UI module.
* `-no-audit-logging`: Skips the Audit Logging module.
* `-no-file-management`: Skips the File Management module.
* `-no-language-management`: Skips the Language Management module.
* `-no-text-template-management`: Skips the Text Template Management module.
* `-no-chat`: Skips the Chat module.
* `-file-management`: Includes the File Management module.
* `-chat`: Includes the Chat module.
* `--legacy`: Generates a legacy solution.
* `trust-version`: Trusts the user's version and does not check if the version exists or not. If the template with the given version is found in the cache, it will be used, otherwise throws an exception.
@ -913,7 +915,7 @@ abp logout
### bundle
This command generates script and style references for ABP Blazor WebAssembly and MAUI Blazor project and updates the **index.html** file. It helps developers to manage dependencies required by ABP modules easily. In order ```bundle``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain a Blazor or MAUI Blazor project file(*.csproj).
This command generates script and style references for ABP Blazor WebAssembly and MAUI Blazor project and updates the **index.html** file. It helps developers to manage dependencies required by ABP modules easily. In order for ```bundle``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain a Blazor or MAUI Blazor project file(*.csproj).
Usage:

75
docs/en/cli/new-command-samples.md

@ -21,7 +21,7 @@ The following commands are for creating Angular UI projects:
* **Entity Framework Core**, **custom connection string**, creates the project in a new folder:
```bash
abp new Acme.BookStore -u angular -csf --connection-string Server=localhost;Database=MyDatabase;Trusted_Connection=True
abp new Acme.BookStore -u angular -csf --connection-string "Server=localhost;Database=MyDatabase;Trusted_Connection=True"
```
* **MongoDB**, default app template, mobile project included, creates solution in `C:\MyProjects\Acme.BookStore`
@ -221,6 +221,79 @@ As seen below, ABP libraries are local project references.
</ItemGroup>
```
## Using Existing Configuration
If you want to programmaticaly create solutions, you can use an existing configuration instead of passing parameters to CLI one by one. ABP Studio keeps the solution creation history locally in `(UserProfile)\.abp\studio\solution-creation-history.json` file, there you can find the configurations of the solutions created in your machine.
### Using a Solution Id
In `*.abpsln` file of the solutions, there is an ID field that you can use to recreate a solution. To do this, pass the id to cli using `-shi or --solution-history-id` parameters.
```bash
abp new -shi dbb1afa9-190e-419a-842d-2780bb1bad1f
abp new -shi dbb1afa9-190e-419a-842d-2780bb1bad1f -o D:\test\Acme.BookStore
```
### Using a JSON Configuration File
You can also use a configuration file to create solutions. You need to use `-rcp or ready-config-path` parameters to do that.
```bash
abp new -rcp MyTests\config.json
abp new -rcp D:\MyTests\config.json
abp new -rcp D:\MyTests\config.json -o D:\test\Acme.BookStore
```
To prepare a config file, you can check the records in `solution-creation-history.json` file mentioned above. An example config file would look like that:
```json
{
"solutionName": {
"fullName": "Acme.BookStore",
"companyName": "Acme",
"projectName": "BookStore"
},
"pro": true,
"useOpenSourceTemplate": false,
"booksSample": false,
"databaseProvider": "ef",
"createInitialMigration": true,
"runDbMigrator": true,
"uiFramework": "angular",
"theme": "leptonx",
"themeStyle": "system",
"mobileFramework": "none",
"databaseManagementSystem": "sqlserver",
"databaseManagementSystemBuilderExtensionMethod": "UseSqlServer",
"connectionString": "Server=(LocalDb)\\\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True;TrustServerCertificate=true",
"mauiBlazorApplicationIdGuid": "d3499a09-f3d4-4bb7-9d58-4c7b1caee331",
"tiered": false,
"publicWebsite": false,
"cmskit": false,
"openIddictAdmin": true,
"languageManagement": true,
"textTemplateManagement": true,
"multiTenancy": true,
"auditLogging": false,
"gdpr": true,
"chat": false,
"fileManagement": false,
"socialLogins": true,
"includeTests": true,
"distributedEventBus": "none",
"publicRedis": false,
"separateTenantSchema": false,
"progressiveWebApp": false,
"runProgressiveWebAppSupport": false,
"runInstallLibs": false,
"runBundling": false,
"kubernetesConfiguration": true,
"templateName": "app"
}
```
## See Also
* [ABP CLI documentation](../cli/index.md)

6
docs/en/contribution/index.md

@ -50,6 +50,12 @@ This is the recommended approach, since it automatically finds all missing texts
If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub.
### Commercial Modules
The commercial modules are not open source, and their localization files are not available in the public repository. The open-source module, `Account`, and the commercial module, `Account.Pro`, may have different translations.
If you would like to translate a commercial module, please [create an issue](https://github.com/abpframework/abp/issues/new) on Github, and we will provide the necessary files (`abp-translation.json` for one or all modules).
## Bug Report
If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new).

6
docs/en/deployment/configuring-openIddict.md

@ -4,6 +4,8 @@ This document introduces how to configure `OpenIddict` in the `AuthServer` proje
There are different configurations in the `AuthServer` project for the `Development` and `Production` environments.
> If your solution does not include a project named `.AuthServer`, it means that you might have another project that depends on `AbpAccountPublicWebOpenIddictModule`. The project name can be `MyProject`, `MyProject.Web`, or `MyProject.HttpApi.Host`. They are both `Authentication Server` projects in that context.
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
@ -36,7 +38,9 @@ To avoid that, consider creating self-signed certificates and storing them in th
`AddDevelopmentEncryptionAndSigningCertificate` is disabled in production environment. Signing and encryption of certificates is done using `openiddict.pfx` file in production environment.
You can use the `dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000` command to generate the `authserver.pfx` certificate.
You can use the `dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000` command to generate the `openiddict.pfx` certificate.
> `openiddict.pfx` is just an example of a filename. You can use any filename for the pfx file.
> `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want.

84
docs/en/deployment/forwarded-headers.md

@ -0,0 +1,84 @@
# Forwarded Headers
Reverse proxies and load balancers play a crucial role in modern web application architectures. When an application is deployed behind these proxies and load balancers, several specific issues can arise. This document will discuss these issues in detail, explain how ASP.NET Core's forwarded headers middleware can address them, and provide a code example for configuring forwarded headers in an ABP application.
## Possible problem in a Reverse Proxy Environment
When requests pass through a reverse proxy or load balancer, the following common issues can occur:
### 1. Loss of Original Request Information
A reverse proxy or load balancer typically modifies the original HTTP request headers. For example, the proxy may replace the client's `X-Forwarded-For` header, or the `Host` header might be set to the proxy's address. This can result in the backend application being unable to directly access the client's IP address, the true hostname, and the protocol used.
### 2. HTTPS vs HTTP Protocol Confusion
When a request is forwarded by a proxy server, it is often upgraded to HTTPS to ensure secure transmission. The proxy server will send a header like `X-Forwarded-Proto` to indicate whether the original request was HTTP or HTTPS. If the backend application does not correctly handle this header, it may generate URLs with the wrong protocol.
### 3. Path Handling Issues
Since load balancers and proxies might modify or map the request URL paths differently, the backend application could encounter path inconsistencies. For example, a reverse proxy might forward a request from `/api` to `/myapp/api`. If the backend application is not correctly configured, path parsing errors may occur.
### 4. IP Address and Security
Reverse proxies might replace the original client IP address with their own, which can affect logging, authentication, and access control mechanisms. To retrieve the actual client IP address, the `X-Forwarded-For` header must be correctly parsed and trusted.
### 5. Load Balancer Impact
Load balancers might distribute requests to different backend servers using different algorithms. This can create session affinity problems. If session data is stored on a single server and the load balancer directs subsequent requests to different servers, session loss or inconsistency may occur.
## Forwarded Headers Middleware in ABP web application
To resolve the above issues, ASP.NET Core provides a built-in middleware, `ForwardedHeadersMiddleware`, which processes the headers forwarded by reverse proxies. This middleware helps the application recover the correct original request information, such as the client’s IP address, protocol, and host.
### Configuring `ForwardedHeadersMiddleware`
ASP.NET Core’s `ForwardedHeadersMiddleware` supports several HTTP headers:
- **X-Forwarded-For**: Contains the original client’s IP address.
- **X-Forwarded-Proto**: Indicates whether the original request was HTTP or HTTPS.
- **X-Forwarded-Host**: Contains the original host requested by the client.
- **X-Forwarded-Port**: Indicates the original port of the request.
To configure this middleware:
1. In the `ConfigureServices` method of your module, configure the `ForwardedHeadersOptions`:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
}
```
2. In the `OnApplicationInitialization` method of your module, add the middleware:
> Forwarded Headers Middleware should run before other middleware. This ordering ensures that the middleware relying on forwarded headers information can consume the header values for processing. Forwarded Headers Middleware can run after diagnostics and error handling, but it must be run before calling UseHsts:
```csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseForwardedHeaders();
}
else
{
app.UseErrorPage();
app.UseForwardedHeaders();
app.UseHsts();
}
// Other middleware configurations...
}
```
## References
- [ASP.NET Core Proxy and Load Balancer Configuration](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-9.0)

1
docs/en/deployment/index.md

@ -12,3 +12,4 @@ However, there are some topics that you should care about when you are deploying
* [Optimization for Production](./optimizing-production.md): Tips and suggestions for optimizing your application in production environments.
* [Deploying to a Clustered Environment](./clustered-environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently.
* [Deploying Distributed / Microservice Solutions](./distributed-microservice.md): Deployment notes for solutions consisting of multiple applications and/or services.
* [Forwarded Headers](./forwarded-headers): Explains how to configure the application to trust headers forwarded by reverse proxies and recover the original request information from the `X-Forwarded-For` and `X-Forwarded-Proto` headers.

534
docs/en/docs-nav.json

@ -5,7 +5,8 @@
"items": [
{
"text": "Overview",
"path": "get-started"
"path": "get-started",
"isIndex": true
},
{
"text": "Single Layer Web Application",
@ -39,6 +40,10 @@
"path": "get-started/console.md"
}
]
},
{
"text": "Pre-Requirements",
"path": "get-started/pre-requirements.md"
}
]
},
@ -47,14 +52,18 @@
"items": [
{
"text": "Overview",
"path": "tutorials"
"path": "tutorials",
"isIndex": true
},
{
"text": "TODO Application",
"isLazyExpandable": true,
"path": "tutorials/todo",
"items": [
{
"text": "Overview",
"path": "tutorials/todo"
"path": "tutorials/todo",
"isIndex": true
},
{
"text": "Single-Layer Solution",
@ -68,10 +77,13 @@
},
{
"text": "Book Store Application",
"isLazyExpandable": true,
"path": "tutorials/book-store",
"items": [
{
"text": "Overview",
"path": "tutorials/book-store"
"path": "tutorials/book-store",
"isIndex": true
},
{
"text": "1: Creating the Server Side",
@ -115,12 +127,47 @@
}
]
},
{
"text": "Book Store Application (with ABP Suite)",
"isLazyExpandable": true,
"path": "tutorials/book-store-with-abp-suite/index.md",
"items": [
{
"text": "Overview",
"path": "tutorials/book-store-with-abp-suite",
"isIndex": true
},
{
"text": "1: Creating the Solution",
"path": "tutorials/book-store-with-abp-suite/part-01.md"
},
{
"text": "2: Creating the Books",
"path": "tutorials/book-store-with-abp-suite/part-02.md"
},
{
"text": "3: Creating the Authors",
"path": "tutorials/book-store-with-abp-suite/part-03.md"
},
{
"text": "4: Book to Author Relation",
"path": "tutorials/book-store-with-abp-suite/part-04.md"
},
{
"text": "5: Customizing the Generated Code",
"path": "tutorials/book-store-with-abp-suite/part-05.md"
}
]
},
{
"text": "Modular Monolith Application",
"isLazyExpandable": true,
"path": "tutorials/modular-crm/index.md",
"items": [
{
"text": "Overview",
"path": "tutorials/modular-crm/index.md"
"path": "tutorials/modular-crm/index.md",
"isIndex": true
},
{
"text": "1: Creating the Initial Solution",
@ -156,6 +203,65 @@
}
]
},
{
"text": "Microservice Solution",
"isLazyExpandable": true,
"path": "tutorials/microservice/index.md",
"items": [
{
"text": "Overview",
"path": "tutorials/microservice/index.md",
"isIndex": true
},
{
"text": "1: Creating the initial solution",
"path": "tutorials/microservice/part-01.md"
},
{
"text": "2: Creating the initial Catalog service",
"path": "tutorials/microservice/part-02.md"
},
{
"text": "3: Building the Catalog service",
"path": "tutorials/microservice/part-03.md"
},
{
"text": "4: Creating the initial Ordering service",
"path": "tutorials/microservice/part-04.md"
},
{
"text": "5: Building the Ordering service",
"path": "tutorials/microservice/part-05.md"
},
{
"text": "6: Integrating the services: HTTP API Calls",
"path": "tutorials/microservice/part-06.md"
},
{
"text": "7: Integrating the services: Using Distributed Events",
"path": "tutorials/microservice/part-07.md"
}
]
},
{
"text": "Mobile Application Development",
"isLazyExpandable": true,
"path": "tutorials/mobile/index.md",
"items": [
{
"text": "Overview",
"path": "tutorials/mobile/index.md"
},
{
"text": "MAUI",
"path": "tutorials/mobile/maui/index.md"
},
{
"text": "React Native",
"path": "tutorials/mobile/react-native/index.md"
}
]
},
{
"text": "Community Articles",
"path": "https://abp.io/community"
@ -167,14 +273,16 @@
"items": [
{
"text": "Overview",
"path": "tools.md"
"path": "tools.md",
"isIndex": true
},
{
"text": "ABP CLI",
"items": [
{
"text": "Overview",
"path": "cli"
"path": "cli",
"isIndex": true
},
{
"text": "New Solution Sample Commands",
@ -187,7 +295,8 @@
"items": [
{
"text": "Overview",
"path": "studio"
"path": "studio",
"isIndex": true
},
{
"text": "Installation",
@ -198,7 +307,8 @@
"items": [
{
"text": "Overview",
"path": "studio/overview.md"
"path": "studio/overview.md",
"isIndex": true
},
{
"text": "Solution Explorer",
@ -227,8 +337,8 @@
"path": "studio/concepts.md"
},
{
"text": "Version Compatibility",
"path": "studio/version-compatibility.md"
"text": "Version Mapping",
"path": "studio/version-mapping.md"
},
{
"text": "Release Notes",
@ -241,7 +351,8 @@
"items": [
{
"text": "Overview",
"path": "suite"
"path": "suite",
"isIndex": true
},
{
"text": "How to Install",
@ -264,7 +375,8 @@
"items": [
{
"text": "Overview",
"path": "suite/generating-crud-page.md"
"path": "suite/generating-crud-page.md",
"isIndex": true
},
{
"text": "Creating Many-To-Many Relationship",
@ -316,7 +428,8 @@
"items": [
{
"text": "Overview",
"path": "framework/fundamentals"
"path": "framework/fundamentals",
"isIndex": true
},
{
"text": "Application Startup",
@ -327,7 +440,8 @@
"items": [
{
"text": "Overview",
"path": "framework/fundamentals/authorization.md"
"path": "framework/fundamentals/authorization.md",
"isIndex": true
},
{
"text": "Dynamic Claims",
@ -340,7 +454,8 @@
"items": [
{
"text": "Overview",
"path": "framework/fundamentals/caching.md"
"path": "framework/fundamentals/caching.md",
"isIndex": true
},
{
"text": "Redis Cache",
@ -361,7 +476,8 @@
"items": [
{
"text": "Overview",
"path": "framework/fundamentals/dependency-injection.md"
"path": "framework/fundamentals/dependency-injection.md",
"isIndex": true
},
{
"text": "AutoFac Integration",
@ -394,7 +510,8 @@
"items": [
{
"text": "Overview",
"path": "framework/fundamentals/validation.md"
"path": "framework/fundamentals/validation.md",
"isIndex": true
},
{
"text": "FluentValidation Integration",
@ -409,7 +526,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure"
"path": "framework/infrastructure",
"isIndex": true
},
{
"text": "Audit Logging",
@ -420,7 +538,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/background-jobs"
"path": "framework/infrastructure/background-jobs",
"isIndex": true
},
{
"text": "Hangfire Integration",
@ -441,7 +560,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/background-workers"
"path": "framework/infrastructure/background-workers",
"isIndex": true
},
{
"text": "Quartz Integration",
@ -458,7 +578,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/blob-storing"
"path": "framework/infrastructure/blob-storing",
"isIndex": true
},
{
"text": "Storage Providers",
@ -532,7 +653,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/emailing.md"
"path": "framework/infrastructure/emailing.md",
"isIndex": true
},
{
"text": "MailKit Integration",
@ -549,7 +671,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/event-bus"
"path": "framework/infrastructure/event-bus",
"isIndex": true
},
{
"text": "Local Event Bus",
@ -560,7 +683,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/event-bus/distributed"
"path": "framework/infrastructure/event-bus/distributed",
"isIndex": true
},
{
"text": "Azure Service Bus Integration",
@ -627,7 +751,8 @@
"items": [
{
"text": "Overview",
"path": "framework/infrastructure/text-templating"
"path": "framework/infrastructure/text-templating",
"isIndex": true
},
{
"text": "Razor Integration",
@ -654,14 +779,16 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture"
"path": "framework/architecture",
"isIndex": true
},
{
"text": "Module Development Best Practices",
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices"
"path": "framework/architecture/best-practices",
"isIndex": true
},
{
"text": "Module Architecture",
@ -672,7 +799,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices/domain-layer-overview.md"
"path": "framework/architecture/best-practices/domain-layer-overview.md",
"isIndex": true
},
{
"text": "Entities",
@ -693,7 +821,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices/application-layer-overview.md"
"path": "framework/architecture/best-practices/application-layer-overview.md",
"isIndex": true
},
{
"text": "Application Services",
@ -710,7 +839,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices/data-access-overview.md"
"path": "framework/architecture/best-practices/data-access-overview.md",
"isIndex": true
},
{
"text": "Entity Framework Core Integration",
@ -729,7 +859,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/modularity/basics.md"
"path": "framework/architecture/modularity/basics.md",
"isIndex": true
},
{
"text": "Plug-In Modules",
@ -740,7 +871,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/modularity/extending/customizing-application-modules-guide.md"
"path": "framework/architecture/modularity/extending/customizing-application-modules-guide.md",
"isIndex": true
},
{
"text": "Module Entity Extension System",
@ -763,14 +895,16 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/domain-driven-design"
"path": "framework/architecture/domain-driven-design",
"isIndex": true
},
{
"text": "Domain Layer",
"items": [
{
"text": "Overview",
"path": "framework/architecture/domain-driven-design/domain-layer.md"
"path": "framework/architecture/domain-driven-design/domain-layer.md",
"isIndex": true
},
{
"text": "Entities & Aggregate Roots",
@ -799,7 +933,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/domain-driven-design/application-layer.md"
"path": "framework/architecture/domain-driven-design/application-layer.md",
"isIndex": true
},
{
"text": "Application Services",
@ -828,6 +963,74 @@
{
"text": "Microservices",
"path": "framework/architecture/microservices"
},
{
"text": "Module Development Best Practices",
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices"
},
{
"text": "Module Architecture",
"path": "framework/architecture/best-practices/module-architecture.md"
},
{
"text": "Domain Layer",
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices/domain-layer-overview.md"
},
{
"text": "Entities",
"path": "framework/architecture/best-practices/entities.md"
},
{
"text": "Repositories",
"path": "framework/architecture/best-practices/repositories.md"
},
{
"text": "Domain Services",
"path": "framework/architecture/best-practices/domain-services.md"
}
]
},
{
"text": "Application Layer",
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices/application-layer-overview.md"
},
{
"text": "Application Services",
"path": "framework/architecture/best-practices/application-services.md"
},
{
"text": "Data Transfer Objects",
"path": "framework/architecture/best-practices/data-transfer-objects.md"
}
]
},
{
"text": "Data Access",
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices/data-access-overview.md"
},
{
"text": "Entity Framework Core Integration",
"path": "framework/architecture/best-practices/entity-framework-core-integration.md"
},
{
"text": "MongoDB Integration",
"path": "framework/architecture/best-practices/mongodb-integration.md"
}
]
}
]
}
]
},
@ -836,14 +1039,16 @@
"items": [
{
"text": "Overview",
"path": "framework/api-development"
"path": "framework/api-development",
"isIndex": true
},
{
"text": "ABP Endpoints",
"items": [
{
"text": "Overview",
"path": "framework/api-development/standard-apis"
"path": "framework/api-development/standard-apis",
"isIndex": true
},
{
"text": "Application Configuration",
@ -886,14 +1091,16 @@
"items": [
{
"text": "Overview",
"path": "framework/ui"
"path": "framework/ui",
"isIndex": true
},
{
"text": "MVC / Razor Pages",
"items": [
{
"text": "Overview",
"path": "framework/ui/mvc-razor-pages/overall"
"path": "framework/ui/mvc-razor-pages/overall.md",
"isIndex": true
},
{
"text": "Navigation / Menus",
@ -940,7 +1147,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/mvc-razor-pages/tag-helpers"
"path": "framework/ui/mvc-razor-pages/tag-helpers",
"isIndex": true
},
{
"text": "Form Elements",
@ -981,7 +1189,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/mvc-razor-pages/theming.md"
"path": "framework/ui/mvc-razor-pages/theming.md",
"isIndex": true
},
{
"text": "The Basic Theme",
@ -998,7 +1207,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/mvc-razor-pages/javascript-api"
"path": "framework/ui/mvc-razor-pages/javascript-api",
"isIndex": true
},
{
"text": "Localization",
@ -1059,7 +1269,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/mvc-razor-pages/customization-user-interface.md"
"path": "framework/ui/mvc-razor-pages/customization-user-interface.md",
"isIndex": true
},
{
"text": "Entity Action Extensions",
@ -1091,7 +1302,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/blazor/overall.md"
"path": "framework/ui/blazor/overall.md",
"isIndex": true
},
{
"text": "Navigation / Menu",
@ -1110,7 +1322,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/blazor/theming.md"
"path": "framework/ui/blazor/theming.md",
"isIndex": true
},
{
"text": "The Basic Theme",
@ -1232,7 +1445,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/angular/overview.md"
"path": "framework/ui/angular/overview.md",
"isIndex": true
},
{
"text": "Quick Start",
@ -1426,7 +1640,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/angular/theming.md"
"path": "framework/ui/angular/theming.md",
"isIndex": true
},
{
"text": "Configuration",
@ -1463,7 +1678,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/angular/extensions-overall.md"
"path": "framework/ui/angular/extensions-overall.md",
"isIndex": true
},
{
"text": "Entity Action Extensions",
@ -1525,7 +1741,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/react-native"
"path": "framework/ui/react-native",
"isIndex": true
}
]
},
@ -1534,7 +1751,8 @@
"items": [
{
"text": "Overview",
"path": "framework/ui/maui"
"path": "framework/ui/maui",
"isIndex": true
}
]
},
@ -1563,14 +1781,16 @@
"items": [
{
"text": "Overview",
"path": "framework/data"
"path": "framework/data",
"isIndex": true
},
{
"text": "Entity Framework Core",
"items": [
{
"text": "Overview",
"path": "framework/data/entity-framework-core"
"path": "framework/data/entity-framework-core",
"isIndex": true
},
{
"text": "Database Migrations",
@ -1640,7 +1860,12 @@
"items": [
{
"text": "Overview",
"path": "solution-templates"
"path": "solution-templates",
"isIndex": true
},
{
"text": "Template Guide",
"path": "solution-templates/guide.md"
},
{
"text": "Single-Layer Solution",
@ -1697,7 +1922,170 @@
},
{
"text": "Microservice Solution",
"path": "solution-templates/microservice"
"isLazyExpandable": true,
"path": "solution-templates/microservice",
"items":[
{
"text": "Overview",
"path": "solution-templates/microservice"
},
{
"text": "Solution Structure",
"path": "solution-templates/microservice/solution-structure.md"
},
{
"text": "Main Components",
"items": [
{
"text": "Overview",
"path": "solution-templates/microservice/main-components"
},
{
"text": "Microservices",
"path": "solution-templates/microservice/microservices.md"
},
{
"text": "API Gateways",
"path": "solution-templates/microservice/api-gateways.md"
},
{
"text": "Web Applications",
"path": "solution-templates/microservice/web-applications.md"
},
{
"text": "Mobile Applications",
"path": "solution-templates/microservice/mobile-applications.md"
}
]
},
{
"text": "Built-In Features",
"items": [
{
"text": "Overview",
"path": "solution-templates/microservice/built-in-features.md"
},
{
"text": "Authentication",
"path": "solution-templates/microservice/authentication.md"
},
{
"text": "Database configurations",
"path": "solution-templates/microservice/database-configurations.md"
},
{
"text": "Logging (with Serilog and Elasticsearch)",
"path": "solution-templates/microservice/logging.md"
},
{
"text": "Monitoring (with Prometheus and Grafana)",
"path": "solution-templates/microservice/monitoring.md"
},
{
"text": "Swagger integration",
"path": "solution-templates/microservice/swagger.md"
},
{
"text": "Permission management",
"path": "solution-templates/microservice/permission-management.md"
},
{
"text": "Feature management",
"path": "solution-templates/microservice/feature-management.md"
},
{
"text": "Localization system",
"path": "solution-templates/microservice/localization-system.md"
},
{
"text": "Background Jobs",
"path": "solution-templates/microservice/background-jobs.md"
},
{
"text": "Background Workers",
"path": "solution-templates/microservice/background-workers.md"
},
{
"text": "Distributed Locking",
"path": "solution-templates/microservice/distributed-locking.md"
},
{
"text": "Distributed Cache",
"path": "solution-templates/microservice/distributed-cache.md"
},
{
"text": "Multi-Tenancy",
"path": "solution-templates/microservice/multi-tenancy.md"
},
{
"text": "BLOB Storing",
"path": "solution-templates/microservice/blob-storing.md"
},
{
"text": "CORS configuration",
"path": "solution-templates/microservice/cors-configuration.md"
}
]
},
{
"text": "Communication",
"items":[
{
"text": "Overview",
"path": "solution-templates/microservice/communication.md"
},
{
"text": "HTTP API Calls",
"path": "solution-templates/microservice/http-api-calls.md"
},
{
"text": "gRPC Calls",
"path": "solution-templates/microservice/grpc-calls.md"
},
{
"text": "Distributed Events",
"path": "solution-templates/microservice/distributed-events.md"
}
]
},
{
"text": "Helm Charts and Kubernetes",
"path": "solution-templates/microservice/helm-charts-and-kubernetes.md"
},
{
"text": "Guides",
"items": [
{
"text": "Overview",
"path": "solution-templates/microservice/guides.md"
},
{
"text": "Adding new microservices",
"path": "solution-templates/microservice/adding-new-microservices.md"
},
{
"text": "Adding new applications",
"path": "solution-templates/microservice/adding-new-applications.md"
},
{
"text": "Adding new API gateways",
"path": "solution-templates/microservice/adding-new-api-gateways.md"
},
{
"text": "Mono-repo vs multiple repository approaches",
"path": "solution-templates/microservice/mono-repo-vs-multiple-repository-approaches.md"
},
{
"text": "Authoring unit and integration tests",
"path": "solution-templates/microservice/authoring-unit-and-integration-tests.md"
},
{
"text": "How to use with ABP Suite",
"path": "solution-templates/microservice/how-to-use-with-abp-suite.md"
}
]
}
]
},
{
"text": "Application Module",
@ -1710,7 +2098,8 @@
"items": [
{
"text": "Overview",
"path": "modules"
"path": "modules",
"isIndex": true
},
{
"text": "Account",
@ -1721,7 +2110,8 @@
"items": [
{
"text": "Overview",
"path": "modules/account-pro.md"
"path": "modules/account-pro.md",
"isIndex": true
},
{
"text": "Tenant impersonation & User impersonation",
@ -1783,10 +2173,13 @@
},
{
"text": "IdentityServer",
"isLazyExpandable": true,
"path": "modules/identity-server.md",
"items": [
{
"text": "Overview",
"path": "modules/identity-server.md"
"path": "modules/identity-server.md",
"isIndex": true
},
{
"text": "IdentityServer Migration Guide",
@ -1804,10 +2197,13 @@
},
{
"text": "OpenIddict",
"isLazyExpandable": true,
"path": "modules/openiddict.md",
"items": [
{
"text": "Overview",
"path": "modules/openiddict.md"
"path": "modules/openiddict.md",
"isIndex": true
},
{
"text": "OpenIddict Migration Guide",
@ -1867,11 +2263,12 @@
"items": [
{
"text": "Overview",
"path": "ui-themes"
"path": "ui-themes",
"isIndex": true
},
{
"text": "The Basic Theme",
"path": "framework/ui/mvc-razor-pages/basic-theme.md"
"path": "ui-themes/basic-theme"
},
{
"text": "LeptonX Theme",
@ -1884,7 +2281,8 @@
"items": [
{
"text": "Overview",
"path": "testing/overall.md"
"path": "testing/overall.md",
"isIndex": true
},
{
"text": "Unit tests",
@ -1905,7 +2303,8 @@
"items": [
{
"text": "Overview",
"path": "deployment"
"path": "deployment",
"isIndex": true
},
{
"text": "Configuring SSL certificate(HTTPS)",
@ -1938,7 +2337,8 @@
"items": [
{
"text": "Overview",
"path": "samples"
"path": "samples",
"isIndex": true
},
{
"text": "EventHub",
@ -1963,7 +2363,8 @@
"items": [
{
"text": "Overview",
"path": "https://abp.io/books"
"path": "https://abp.io/books",
"isIndex": true
},
{
"text": "Mastering ABP Framework",
@ -1980,7 +2381,8 @@
"items": [
{
"text": "Overview",
"path": "release-info"
"path": "release-info",
"isIndex": true
},
{
"text": "Release Notes",

2
docs/en/docs-params.json

@ -7,6 +7,8 @@
"MVC": "MVC / Razor Pages",
"Blazor": "Blazor WebAssembly",
"BlazorServer": "Blazor Server",
"BlazorWebApp": "Blazor WebApp",
"MAUIBlazor": "MAUI Blazor (Hybrid)",
"NG": "Angular"
}
},

44
docs/en/framework/architecture/best-practices/application-services.md

@ -1,8 +1,14 @@
# Application Services Best Practices & Conventions
> This document offers best practices for implementing Application Services classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Application Services*](../domain-driven-design/application-services.md) document first.**
## General
* **Do** create an application service for each **aggregate root**.
### Application Service Interface
## Application Service Interface
* **Do** define an `interface` for each application service in the **application contracts** package.
* **Do** inherit from the `IApplicationService` interface.
@ -11,11 +17,11 @@
* **Do not** get/return entities for the service methods.
* **Do** define DTOs based on the [DTO best practices](data-transfer-objects.md).
#### Outputs
### Outputs
* **Avoid** to define too many output DTOs for same or related entities. Instead, define a **basic** and a **detailed** DTO for an entity.
##### Basic DTO
#### Basic DTO
**Do** define a **basic** DTO for an aggregate root.
@ -44,7 +50,7 @@ public class IssueLabelDto
}
```
##### Detailed DTO
#### Detailed DTO
**Do** define a **detailed** DTO for an entity if it has reference(s) to other aggregate roots.
@ -81,20 +87,20 @@ public class LabelDto : ExtensibleEntityDto<Guid>
}
````
#### Inputs
### Inputs
* **Do not** define any property in an input DTO that is not used in the service class.
* **Do not** share input DTOs between application service methods.
* **Do not** inherit an input DTO class from another one.
* **May** inherit from an abstract base DTO class and share some properties between different DTOs in that way. However, should be very careful in that case because manipulating the base DTO would effect all related DTOs and service methods. Avoid from that as a good practice.
#### Methods
### Methods
* **Do** define service methods as asynchronous with **Async** postfix.
* **Do not** repeat the entity name in the method names.
* Example: Define `GetAsync(...)` instead of `GetProductAsync(...)` in the `IProductAppService`.
##### Getting A Single Entity
#### Getting A Single Entity
* **Do** use the `GetAsync` **method name**.
* **Do** get Id with a **primitive** method parameter.
@ -104,7 +110,7 @@ public class LabelDto : ExtensibleEntityDto<Guid>
Task<QuestionWithDetailsDto> GetAsync(Guid id);
````
##### Getting A List Of Entities
#### Getting A List Of Entities
* **Do** use the `GetListAsync` **method name**.
* **Do** get a single DTO argument for **filtering**, **sorting** and **paging** if necessary.
@ -117,7 +123,7 @@ Task<QuestionWithDetailsDto> GetAsync(Guid id);
Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
````
##### Creating A New Entity
#### Creating A New Entity
* **Do** use the `CreateAsync` **method name**.
* **Do** get a **specialized input** DTO to create the entity.
@ -151,7 +157,7 @@ public class CreateQuestionDto : ExtensibleObject
}
````
##### Updating An Existing Entity
#### Updating An Existing Entity
- **Do** use the `UpdateAsync` **method name**.
- **Do** get a **specialized input** DTO to update the entity.
@ -167,7 +173,7 @@ Example:
Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto);
````
##### Deleting An Existing Entity
#### Deleting An Existing Entity
- **Do** use the `DeleteAsync` **method name**.
- **Do** get Id with a **primitive** method parameter. Example:
@ -176,7 +182,7 @@ Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuesti
Task DeleteAsync(Guid id);
````
##### Other Methods
#### Other Methods
* **Can** define additional methods to perform operations on the entity. Example:
@ -186,7 +192,7 @@ Task<int> VoteAsync(Guid id, VoteType type);
This method votes a question and returns the current score of the question.
### Application Service Implementation
## Application Service Implementation
* **Do** develop the application layer **completely independent from the web layer**.
* **Do** implement application service interfaces in the **application layer**.
@ -195,30 +201,30 @@ This method votes a question and returns the current score of the question.
* **Do** make all public methods **virtual**, so developers may inherit and override them.
* **Do not** make **private** methods. Instead make them **protected virtual**, so developers may inherit and override them.
#### Using Repositories
### Using Repositories
* **Do** use the specifically designed repositories (like `IProductRepository`).
* **Do not** use generic repositories (like `IRepository<Product>`).
#### Querying Data
### Querying Data
* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source.
#### Extra Properties
### Extra Properties
* **Do** use either `MapExtraPropertiesTo` extension method ([see](../../fundamentals/object-extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services.
#### Manipulating / Deleting Entities
### Manipulating / Deleting Entities
* **Do** always get all the related entities from repositories to perform the operations on them.
* **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update.
#### Handle files
### Handle files
* **Do not** use any web components like `IFormFile` or `Stream` in the application services. If you want to serve a file you can use `byte[]`.
* **Do** use a `Controller` to handle file uploading then pass the `byte[]` of the file to the application service method.
#### Using Other Application Services
### Using Other Application Services
* **Do not** use other application services of the same module/application. Instead;
* Use domain layer to perform the required task.

6
docs/en/framework/architecture/best-practices/data-transfer-objects.md

@ -1,5 +1,11 @@
# Data Transfer Objects Best Practices & Conventions
> This document offers best practices for implementing Data Transfer Object classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Data Transfer Objects*](../domain-driven-design/data-transfer-objects.md) document first.**
## General
* **Do** define DTOs in the **application contracts** package.
* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on).
* **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto<TKey>`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way.

10
docs/en/framework/architecture/best-practices/domain-services.md

@ -1,6 +1,10 @@
# Domain Services Best Practices & Conventions
### Domain Service
> This document offers best practices for implementing Domain Service classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Domain Services*](../domain-driven-design/domain-services.md) document first.**
## Domain Services
- **Do** define domain services in the **domain layer**.
- **Do not** create interfaces for the domain services **unless** you have a good reason to (like mock and test different implementations).
@ -14,7 +18,7 @@ public class IssueManager : DomainService
}
```
### Domain Service Methods
## Domain Service Methods
- **Do not** define `GET` methods. `GET` methods do not change the state of an entity. Hence, use the repository directly in the Application Service instead of Domain Service method.
@ -57,8 +61,6 @@ public async Task AssignToAsync(Issue issue, IdentityUser user)
- **Do not** return `DTO`. Return only domain objects when you need.
- **Do not** involve authenticated user logic. Instead, define extra parameter and send the related data of ` CurrentUser` from the Application Service layer.
## See Also
* [Video tutorial](https://abp.io/video-courses/essentials/domain-services)

33
docs/en/framework/architecture/best-practices/entities.md

@ -1,12 +1,16 @@
# Entity Best Practices & Conventions
### Entities
> This document offers best practices for implementing Aggregate Root and Entity classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Entities*](../domain-driven-design/entities.md) document first.**
## Entities
Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them.
- **Do** define entities in the **domain layer**.
#### Primary Constructor
### Primary Constructor
* **Do** define a **primary constructor** that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code.
@ -14,15 +18,15 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate
- **Do** always initialize sub collections in the primary constructor.
- **Do not** generate `Guid` keys inside the constructor. Get it as a parameter, so the calling code will use `IGuidGenerator` to generate a new `Guid` value.
#### Parameterless Constructor
### Parameterless Constructor
- **Do** always define a `protected` parameterless constructor to be compatible with ORMs.
#### References
### References
- **Do** always **reference** to other aggregate roots **by Id**. Never add navigation properties to other aggregate roots.
#### Other Class Members
### Other Class Members
- **Do** always define properties and methods as `virtual` (except `private` methods, obviously). Because some ORMs and dynamic proxy tools require it.
- **Do** keep the entity as always **valid** and **consistent** within its own boundary.
@ -30,27 +34,27 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate
- **Do** define `public `, `internal` or `protected internal` (virtual) **methods** to change the properties (with non-public setters) if necessary.
- **Do** return the entity object (`this`) from the setter methods.
### Aggregate Roots
## Aggregate Roots
#### Primary Keys
### Primary Keys
* **Do** always use a **Id** property for the aggregate root key.
* **Do not** use **composite keys** for aggregate roots.
* **Do** use **Guid** as the **primary key** of all aggregate roots.
#### Base Class
### Base Class
* **Do** inherit from the `AggregateRoot<TKey>` or one of the audited classes (`CreationAuditedAggregateRoot<TKey>`, `AuditedAggregateRoot<TKey>` or `FullAuditedAggregateRoot<TKey>`) based on requirements.
#### Aggregate Boundary
### Aggregate Boundary
* **Do** keep aggregates **as small as possible**. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions:
* **Performance** & **memory** cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory.
* **Consistency** & **validity** boundary.
### Example
## Example
#### Aggregate Root
### Aggregate Root
````C#
public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/identifier
@ -130,7 +134,7 @@ public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/iden
}
````
#### The Entity
### Entity
````C#
public class IssueLabel : Entity
@ -151,11 +155,12 @@ public class IssueLabel : Entity
}
````
### References
## References
* Effective Aggregate Design by Vaughn Vernon
http://dddcommunity.org/library/vernon_2011
## See Also
## See Also
* [Video tutorial](https://abp.io/video-courses/essentials/entities)

18
docs/en/framework/architecture/best-practices/entity-framework-core-integration.md

@ -1,12 +1,16 @@
# Entity Framework Core Integration Best Practices
> See [Entity Framework Core Integration document](../../data/entity-framework-core) for the basics of the EF Core integration.
> This document offers best practices for implementing Entity Framework Core integration in your modules and applications.
>
> **Ensure you've read the [*Entity Framework Core Integration*](../../data/entity-framework-core/index.md) document first.**
## General
- **Do** define a separated `DbContext` interface and class for each module.
- **Do not** rely on lazy loading on the application development.
- **Do not** enable lazy loading for the `DbContext`.
### DbContext Interface
## DbContext Interface
- **Do** define an **interface** for the `DbContext` that inherits from `IEfCoreDbContext`.
- **Do** add a `ConnectionStringName` **attribute** to the `DbContext` interface.
@ -23,7 +27,7 @@ public interface IIdentityDbContext : IEfCoreDbContext
* **Do not** define `set;` for the properties in this interface.
### DbContext class
## DbContext class
* **Do** inherit the `DbContext` from the `AbpDbContext<TDbContext>` class.
* **Do** add a `ConnectionStringName` attribute to the `DbContext` class.
@ -46,7 +50,7 @@ public class IdentityDbContext : AbpDbContext<IdentityDbContext>, IIdentityDbCon
}
````
### Table Prefix and Schema
## Table Prefix and Schema
- **Do** add static `TablePrefix` and `Schema` **properties** to the `DbContext` class. Set default value from a constant. Example:
@ -58,7 +62,7 @@ public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
- **Do** always use a short `TablePrefix` value for a module to create **unique table names** in a shared database. `Abp` table prefix is reserved for ABP core modules.
- **Do** set `Schema` to `null` as default.
### Model Mapping
## Model Mapping
- **Do** explicitly **configure all entities** by overriding the `OnModelCreating` method of the `DbContext`. Example:
@ -100,7 +104,7 @@ public static class IdentityDbContextModelBuilderExtensions
* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above).
### Repository Implementation
## Repository Implementation
- **Do** **inherit** the repository from the `EfCoreRepository<TDbContext, TEntity, TKey>` class and implement the corresponding repository interface. Example:
@ -168,7 +172,7 @@ public override async Task<IQueryable<IdentityUser>> WithDetailsAsync()
}
````
### Module Class
## Module Class
- **Do** define a module class for the Entity Framework Core integration package.
- **Do** add `DbContext` to the `IServiceCollection` using the `AddAbpDbContext<TDbContext>` method.

4
docs/en/framework/architecture/best-practices/module-architecture.md

@ -1,13 +1,13 @@
# Module Architecture Best Practices & Conventions
### Solution Structure
## Solution Structure
* **Do** create a separated Visual Studio solution for every module.
* **Do** name the solution as *CompanyName.ModuleName* (for core ABP modules, it's *Volo.Abp.ModuleName*).
* **Do** develop the module as layered, so it has several packages (projects) those are related to each other.
* Every package has its own module definition file and explicitly declares the dependencies for the depended packages/modules.
### Layers & Packages
## Layers & Packages
The following diagram shows the packages of a well-layered module and dependencies of those packages between them:

18
docs/en/framework/architecture/best-practices/mongodb-integration.md

@ -1,8 +1,14 @@
# MongoDB Integration
> This document offers best practices for implementing MongoDB integration in your modules and applications.
>
> **Ensure you've read the [*MongoDB Integration*](../../data/entity-framework-core/index.md) document first.**
## General
* Do define a separated `MongoDbContext` interface and class for each module.
### MongoDbContext Interface
## MongoDbContext Interface
- **Do** define an **interface** for the `MongoDbContext` that inherits from `IAbpMongoDbContext`.
- **Do** add a `ConnectionStringName` **attribute** to the `MongoDbContext` interface.
@ -17,7 +23,7 @@ public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext
}
````
### MongoDbContext class
## MongoDbContext class
- **Do** inherit the `MongoDbContext` from the `AbpMongoDbContext` class.
- **Do** add a `ConnectionStringName` attribute to the `MongoDbContext` class.
@ -34,7 +40,7 @@ public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbC
}
```
### Collection Prefix
## Collection Prefix
- **Do** add static `CollectionPrefix` **property** to the `DbContext` class. Set default value from a constant. Example:
@ -46,7 +52,7 @@ Used the same constant defined for the EF Core integration table prefix in this
- **Do** always use a short `CollectionPrefix` value for a module to create **unique collection names** in a shared database. `Abp` collection prefix is reserved for ABP core modules.
### Collection Mapping
## Collection Mapping
- **Do** explicitly **configure all aggregate roots** by overriding the `CreateModel` method of the `MongoDbContext`. Example:
@ -83,7 +89,7 @@ public static class AbpIdentityMongoDbContextExtensions
}
```
### Repository Implementation
## Repository Implementation
- **Do** **inherit** the repository from the `MongoDbRepository<TMongoDbContext, TEntity, TKey>` class and implement the corresponding repository interface. Example:
@ -124,7 +130,7 @@ public async Task<IdentityUser> FindByNormalizedUserNameAsync(
* Using `IQueryable<TEntity>` makes the code as much as similar to the EF Core repository implementation and easy to write and read.
* **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method.
### Module Class
## Module Class
- **Do** define a module class for the MongoDB integration package.
- **Do** add `MongoDbContext` to the `IServiceCollection` using the `AddMongoDbContext<TMongoDbContext>` method.

10
docs/en/framework/architecture/best-practices/repositories.md

@ -1,6 +1,10 @@
# Repository Best Practices & Conventions
### Repository Interfaces
> This document offers best practices for implementing Repository classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Repositories*](../domain-driven-design/repositories.md) document first.**
## Repository Interfaces
* **Do** define repository interfaces in the **domain layer**.
* **Do** define a repository interface (like `IIdentityUserRepository`) and create its corresponding implementations for **each aggregate root**.
@ -30,7 +34,7 @@ public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid>
* **Do** inherit the repository interface from `IBasicRepository<TEntity, TKey>` (as normally) or a lower-featured interface, like `IReadOnlyRepository<TEntity, TKey>` (if it's needed).
* **Do not** define repositories for entities those are **not aggregate roots**.
### Repository Methods
## Repository Methods
* **Do** define all repository methods as **asynchronous**.
* **Do** add an **optional** `cancellationToken` parameter to every method of the repository. Example:
@ -68,7 +72,7 @@ Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
* **Avoid** to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where:
* Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance.
### See Also
## See Also
* [Entity Framework Core Integration](./entity-framework-core-integration.md)
* [MongoDB Integration](./mongodb-integration.md)

2
docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md

@ -1,7 +1,5 @@
# Data Transfer Objects
## Introduction
**Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients.
Typically, an [application service](./application-services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.

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

@ -1,7 +1,5 @@
# Domain Services
## Introduction
In a [Domain Driven Design](../domain-driven-design) (DDD) solution, the core business logic is generally implemented in aggregates ([entities](./entities.md)) and the Domain Services. Creating a Domain Service is especially needed when;
* You implement a core domain logic that depends on some services (like repositories or other external services).

44
docs/en/framework/fundamentals/caching.md

@ -1,14 +1,14 @@
# Distributed Caching
ABP extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
ABP extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to provide a more comfortable and easy-to-use cache service.
> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](./redis-cache.md) document if you want to use Redis as the distributed cache server.
> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** Memory cache is only useful if you are building a monolith application and you run a single instance of your application. For other cases, consider using a distributed cache server. See the ***[When to Use a Distributed Cache Server](../../kb/when-to-use-a-distributed-cache-server.md)*** document for more details.
## Installation
> This package is already installed by default with the [application startup template](../../solution-templates/layered-web-application). So, most of the time, you don't need to install it manually.
> This package is already installed by default in [startup templates](../../solution-templates/index.md). So, most of the time, you don't need to install it manually.
[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it a project using the add-package command of the [ABP CLI](../../cli):
[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it as a project using the add-package command of the [ABP CLI](../../cli):
```bash
abp add-package Volo.Abp.Caching
@ -24,10 +24,10 @@ ASP.NET Core defines the `IDistributedCache` interface to get/set the cache valu
* It works with **byte arrays** rather than .NET objects. So, you need to **serialize/deserialize** the objects you need to cache.
* It provides a **single key pool** for all cache items, so;
* You need to care about the keys to distinguish **different type of objects**.
* You need to care about the keys to distinguish **different types of objects**.
* You need to care about the cache items of **different tenants** in a [multi-tenant](../architecture/multi-tenancy) system.
> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**.
> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications but also available to **any type of applications**.
See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information.
@ -37,12 +37,12 @@ ABP defines the generic `IDistributedCache<TCacheItem>` interface in the [Volo.A
`IDistributedCache<TCacheItem>` solves the difficulties explained above;
* It internally **serializes/deserializes** the cached objects. Uses **JSON** serialization by default, but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](./dependency-injection.md) system.
* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name.
* It internally **serializes/deserializes** the cached objects. It uses **JSON** serialization by default but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](./dependency-injection.md) system.
* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. The default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name.
* It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (if your application is [multi-tenant](../architecture/multi-tenancy)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application.
* Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache server.
* Allows defining a **global cache key prefix** per application so different applications can use their isolated key pools in a shared distributed cache server.
* It **can tolerate errors** wherever possible and bypasses the cache. This is useful when you have temporary problems on the cache server.
* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance on **batch operations**.
* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance of **batch operations**.
**Example: Store Book names and prices in the cache**
@ -167,7 +167,7 @@ namespace MyProject
````
* This sample service uses the `GetOrAddAsync()` method to get a book item from the cache.
* Since cache explicitly implemented as using `Guid` as cache key, `Guid` value passed to `_cache_GetOrAddAsync()` method.
* Since the cache is explicitly implemented as using `Guid` as the cache key, the `Guid` value is passed to the `_cache_GetOrAddAsync()` method.
#### Complex Types as the Cache Key
@ -228,29 +228,29 @@ Configure<AbpDistributedCacheOptions>(options =>
* `HideErrors` (`bool`, default: `true`): Enables/disables hiding the errors on writing/reading values from the cache server.
* `KeyPrefix` (`string`, default: `null`): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items.
* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. Default value uses the `SlidingExpiration` as 20 minutes.
* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. The default value uses the `SlidingExpiration` as 20 minutes.
## Error Handling
When you design a cache for your objects, you typically try to get the value from cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require to perform an HTTP call to a remote server.
When you design a cache for your objects, you typically try to get the value from the cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require an HTTP call to a remote server to be performed.
In most cases, you want to **tolerate the cache errors**; If you get error from the cache server you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP does by default.
In most cases, you want to **tolerate the cache errors**; If you get an error from the cache server, you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP does by default.
ABP's Distributed Cache [handle](./exception-handling.md), log and hide errors by default. There is an option to change this globally (see the options below).
In addition, all of the `IDistributedCache<TCacheItem>` (and `IDistributedCache<TCacheItem, TCacheKey>`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter left as `null`, otherwise you can decide to hide or throw the exceptions for individual method calls.
In addition, all of the `IDistributedCache<TCacheItem>` (and `IDistributedCache<TCacheItem, TCacheKey>`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter is left as `null`; otherwise, you can decide to hide or throw the exceptions for individual method calls.
## Batch Operations
ABP's distributed cache interfaces provide methods to perform batch methods those improves the performance when you want to batch operation multiple cache items in a single method call.
ABP's distributed cache interfaces provide methods to perform batch operations that improve performance when you want to batch operation multiple cache items in a single method call.
* `SetManyAsync` and `SetMany` methods can be used to set multiple values to the cache.
* `GetManyAsync` and `GetMany` methods can be used to retrieve multiple values from the cache.
* `GetOrAddManyAsync` and `GetOrAddMany` methods can be used to retrieve multiple values and set missing values from the cache
* `RefreshManyAsync` and `RefreshMany` methods can be used to resets the sliding expiration timeout of multiple values from the cache
* `RefreshManyAsync` and `RefreshMany` methods can be used to reset the sliding expiration timeout of multiple values from the cache
* `RemoveManyAsync` and `RemoveMany` methods can be used to remove multiple values from the cache
> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](./redis-cache.md). If the provider doesn't support, it fallbacks to `SetAsync` and `GetAsync` ... methods (called once for each item).
> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](./redis-cache.md). If the provider doesn't support it, it falls back to `SetAsync` and `GetAsync` ... methods (called once for each item).
## Caching Entities
@ -266,17 +266,17 @@ It's designed as read-only and automatically invalidates a cached entity if the
Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and you **rollback the transaction** that updates the price of the book? In this case, cache value will be incorrect.
`IDistributedCache<..>` methods gets an optional parameter, named `considerUow`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](../architecture/domain-driven-design/unit-of-work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**.
`IDistributedCache<..>` methods gets an optional parameter, named `considerUow`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](../architecture/domain-driven-design/unit-of-work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeeds**.
### IDistributedCacheSerializer
`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. Default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](../../json-serialization.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache.
`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. The default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](../../json-serialization.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache.
You can [replace](./dependency-injection.md) this service by your own implementation if you want to implement your own serialization logic.
You can [replace](./dependency-injection.md) this service with your own implementation if you want to implement your own serialization logic.
### IDistributedCacheKeyNormalizer
`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds cache name, application cache prefix and current tenant id to the cache key. If you need a more advanced key normalization, you can [replace](./dependency-injection.md) this service by your own implementation.
`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds the cache name, application cache prefix and current tenant ID to the cache key. If you need a more advanced key normalization, you can [replace](./dependency-injection.md) this service with your own implementation.
## See Also

2
docs/en/framework/fundamentals/dependency-injection.md

@ -498,6 +498,8 @@ Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unle
> ABP also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions.
> Another advantage of using `ICachedServiceProvider` is that, during an HTTP request, if a service's constructor requires injecting many dependencies, it can negatively impact performance, as the injected services may not all be used by the current request. By resolving services on-demand, performance degradation can be effectively avoided.
## Advanced Features
### IServiceCollection.OnRegistered Event

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

@ -106,6 +106,24 @@ Configure<AbpAspNetCoreAuditingOptions>(options =>
`IgnoredUrls` is the only option. It is a list of ignored URLs prefixes. In the preceding example, all URLs starting with `/products` will be ignored for audit logging.
## AbpAspNetCoreAuditingUrlOptions
`AbpAspNetCoreAuditingUrlOptions` is the [options object](../fundamentals/options.md) to configure audit logging in the ASP.NET Core layer. You can configure it in the `ConfigureServices` method of your [module](../architecture/modularity/basics.md):
````csharp
Configure<AbpAspNetCoreAuditingUrlOptions>(options =>
{
options.IncludeQuery = true;
});
````
Here, a list of the options you can configure:
* `IncludeSchema` (default: `false`): If you set to true, it will include the schema in the URL.
* `IncludeHost` (default: `false`): If you set to true, it will include the host in the URL.
* `IncludeQuery` (default: `false`): If you set to true, it will include the query string in the URL.
## Enabling/Disabling Audit Logging for Services
### Enable/Disable for Controllers & Actions

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

@ -221,6 +221,13 @@ public class MyModule : AbpModule
}
````
* `JobPollPeriod` is used to determine the interval between two job polling operations. Default is 5000 ms (5 seconds).
* `MaxJobFetchCount` is used to determine the maximum job count to fetch in a single polling operation. Default is 1000.
* `DefaultFirstWaitDuration` is used to determine the duration to wait before the first retry. Default is 60 seconds.
* `DefaultTimeout` is used to determine the timeout duration for a job. Default is 172800 seconds (2 days).
* `DefaultWaitFactor` is used to determine the factor to increase the wait duration between retries. Default is 2.0.
* `DistributedLockName` is used to determine the distributed lock name to use. Default is `AbpBackgroundJobWorker`.
### Data Store
The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction to store the jobs.

25
docs/en/framework/infrastructure/event-bus/index.md

@ -6,5 +6,26 @@ An event bus is a mediator that transfers a message from a sender to a receiver.
ABP provides two type of event buses;
* **[Local Event Bus](local)** is suitable for in-process messaging.
* **[Distributed Event Bus](distributed)** is suitable for inter-process messaging, like microservices publishing and subscribing to distributed events.
* **[Local Event Bus](local/index.md)** is suitable for in-process messaging.
* **[Distributed Event Bus](distributed/index.md)** is suitable for inter-process messaging, like publishing and subscribing to distributed events in a distributed/microservice system.
## Event Bus Guiding
You may confuse which event bus to use in your application. Here, a few example scenarios:
* If you are building a microservice system, use the distributed event bus for **inter-microservice communication**. If you are publishing an event and always handling it in the same microservice, you can use the local event bus for that event.
* If you are building a modular monolith application, use the distributed event bus for **inter-module communication**. Since the distributed event bus works in-process by default (unless you configure a real distributed event bus provider). In that way, if you migrate to a microservice system later, these inter-module communication can become a inter-microservice communication without any code change (with just installing a distributed event bus provide package). If you are publishing an event and always handling it in the same module, you can use the local event bus for that event.
* If you are building a monolith (non-modular) application, you can always use the local event bus.
The distributed event bus works in-process by default. Unless you don't configure a real distributed event bus provider, it works just like the local event bus. Once you configure a real distributed event bus provider, all your events become distributed.
So, you use the local event bus only if you an event should always remain in-process in any scenario. For other type of events, use the distributed event bus.
### Technical Differences
There are some technical differences between the local and distributed event bus:
* You can send entities or other non-serializable objects using the local event bus since these objects are not serialized/deserialized on event publishing. On the other hand, distributed event bus serializes/deserializes objects once you use a real distributed event bus provider.
* The local event bus is always transactional in the database level. For distributed event bus, you should enable inbox/outbox pattern to ensure data consistency once you use a real distributed event bus provider.
These differences are because of the distributed event bus supports distributed scenarios while the local event bus is only used for in-process messaging. When you don't set up a real distributed event bus provider, these differences are not valid.

12
docs/en/framework/infrastructure/features.md

@ -161,8 +161,16 @@ namespace FeaturesDemo
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false");
myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10");
myGroup.AddFeature(
"MyApp.PdfReporting",
defaultValue: "false"
);
myGroup.AddFeature(
"MyApp.MaxProductCount",
defaultValue: "10",
valueType: new FreeTextStringValueType(new NumericValueValidator())
);
}
}
}

2
docs/en/framework/ui/angular/http-error-handling.md

@ -27,7 +27,7 @@ export class AppModule {}
```
- `ErrorScreenErrorCodes` the error codes that you can pass to `skipHandledErrorCodes` and `forWhichErrors`.
- `skipHandledErrorCodes` the error codes those you don't want to handle it.
- `skipHandledErrorCodes` the error codes those you don't want to handle.
- `errorScreen` the screen that you want to show when a route error occurs.
- `component` component that you want to show.
- `forWhichErrors` same as `ErrorScreenErrorCodes`

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

@ -6,7 +6,7 @@
Please follow the steps below to prepare your development environment for Angular.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js `v18.19+` installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js `v20.11+` installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
2. **[Optional] Install Yarn:** You may install Yarn v1.22+ (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v10 and below. You may skip this step and work with npm, which is built-in in Node.js, instead.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to Angular projects. ABP project templates even contain plugin recommendations for VS Code users, which VS Code will ask you to install when you open the Angular project folder. Here is a list of recommended extensions:
- [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=angular.ng-template)

109
docs/en/framework/ui/blazor/global-scripts-styles.md

@ -1,83 +1,72 @@
# Blazor UI: Managing Global Scripts & Styles
Some modules may require additional styles or scripts that need to be referenced in **index.html** file. It's not easy to find and update these types of references in Blazor apps. ABP offers a simple, powerful, and modular way to manage global style and scripts in Blazor apps.
You can add your JavaScript and CSS files from your modules or applications to the Blazor global assets system. All the JavaScript and CSS files will be added to the `global.js` and `global.css` files. You can access these files via the following URL in a Blazor WASM project:
To update script & style references without worrying about dependencies, ordering, etc in a project, you can use the [bundle command](../../../cli#bundle).
- https://localhost/global.js
- https://localhost/global.css
You can also add custom styles and scripts and let ABP manage them for you. In your Blazor project, you can create a class implementing `IBundleContributor` interface.
## Add JavaScript and CSS to the global assets system in the module
`IBundleContributor` interface contains two methods.
Your module project solution will have two related Blazor projects:
* `AddScripts(...)`
* `AddStyles(...)`
* `MyModule.Blazor`:This project includes the JavaScript/CSS files required for your Blazor components. The `MyApp.Blazor.Client (Blazor WASM)` project will reference this project.
* `MyModule.Blazor.WebAssembly.Bundling`:This project is used to add your JavaScript/CSS files to the Blazor global resources. The `MyModule.Blazor (ASP.NET Core)` project will reference this project.
Both methods get `BundleContext` as a parameter. You can add scripts and styles to the `BundleContext` and run [bundle command](../../../cli#bundle). Bundle command detects custom styles and scripts with module dependencies and updates `index.html` file.
You need to define JavaScript and CSS contributor classes in the `MyModule.Blazor.WebAssembly.Bundling` project to add the files to the global assets system.
## Example Usage
```csharp
namespace MyProject.Blazor
> Please use `BlazorWebAssemblyStandardBundles.Scripts.Global` and `BlazorWebAssemblyStandardBundles.Styles.Global` for the bundle name.
```cs
public class MyModuleBundleScriptContributor : BundleContributor
{
public class MyProjectBundleContributor : IBundleContributor
public override void ConfigureBundle(BundleConfigurationContext context)
{
public void AddScripts(BundleContext context)
{
context.Add("site.js");
}
public void AddStyles(BundleContext context)
{
context.Add("main.css");
context.Add("custom-styles.css");
}
context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/myscript.js");
}
}
```
> There is a BundleContributor class implementing `IBundleContributor` interface coming by default with the startup templates. So, most of the time, you don't need to add it manually.
## Bundling And Minification
`abp bundle` command offers bundling and minification support for client-side resources(JavaScript and CSS files). `abp bundle` command reads the `appsettings.json` file inside the Blazor project and bundles the resources according to the configuration. You can find the bundle configurations inside `AbpCli.Bundle` element.
Here are the options that you can control inside the `appsettings.json` file.
`Mode`: Bundling and minification mode. Possible values are
* `BundleAndMinify`: Bundle all the files into a single file and minify the content.
* `Bundle`: Bundle all files into a single file, but not minify.
* `None`: Add files individually, do not bundle.
`Name`: Bundle file name. Default value is `global`.
`Parameters`: You can define additional key/value pair parameters inside this section. `abp bundle` command automatically sends these parameters to the bundle contributors, and you can check these parameters inside the bundle contributor, take some actions according to these values.
Let's say that you want to exclude some resources from the bundle and control this action using the bundle parameters. You can add a parameter to the bundle section like below.
```json
"AbpCli": {
"Bundle": {
"Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */
"Name": "global",
"Parameters": {
"ExcludeThemeFromBundle":"true"
}
}
}
```
You can check this parameter and take action like below.
```csharp
public class MyProjectNameBundleContributor : IBundleContributor
```cs
public class MyModuleBundleStyleContributor : BundleContributor
{
public void AddScripts(BundleContext context)
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/mystyle.css");
}
}
```
public void AddStyles(BundleContext context)
```cs
[DependsOn(
typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule)
)]
public class MyBlazorWebAssemblyBundlingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var excludeThemeFromBundle = bool.Parse(context.Parameters.GetValueOrDefault("ExcludeThemeFromBundle"));
context.Add("mytheme.css", excludeFromBundle: excludeThemeFromBundle);
context.Add("main.css");
Configure<AbpBundlingOptions>(options =>
{
// Add script bundle
options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global)
.AddContributors(typeof(MyModuleBundleScriptContributor));
// Add style bundle
options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global)
.AddContributors(typeof(MyModuleBundleStyleContributor));
});
}
}
```
## Add JavaScript and CSS to the global assets system in the application
This is similar to the module. You need to define JavaScript and CSS contributor classes in the `MyApp.Blazor.Client` project to add the files to the global assets system.
## AbpBundlingGlobalAssetsOptions
You can configure the JavaScript and CSS file names in the `GlobalAssets` property of the `AbpBundlingOptions` class. The default values are `global.js` and `global.css`.
## Reference
- [ASP.NET Core MVC Bundling & Minification](../mvc-razor-pages/bundling-minification#bundle-contributorsg)
- [ABP Global Assets - New way to bundle JavaScript/CSS files in Blazor WebAssembly app](https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md)

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

@ -40,6 +40,9 @@ Open a command line terminal and run the `adb reverse` command to expose a port
> You should replace "44305" with the real port.
> You should run the command after starting the emulator.
> If you don't have a separate installation of Android Debug Bridge, you can open it from **Visual Studio** by following toolbar menu `Tools` > `Android` > `Android Adb Command Prompt`. Android emulator has to be running for this operation.
### iOS
The iOS simulator uses the host machine network. Therefore, applications running in the simulator can connect to web services running on your local machine via the machines IP address or via the localhost hostname. For example, given a local secure web service that exposes a GET operation via the /api/todoitems/ relative URI, an application running on the iOS simulator can consume the operation by sending a GET request to https://localhost:<port>/api/todoitems/.

2
docs/en/framework/ui/mvc-razor-pages/client-side-package-management.md

@ -41,7 +41,7 @@ After depending on a NPM package, all you should do is to run the **yarn** comma
yarn
```
Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before.
Alternatively, you can use `npm install` but [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) is suggested as mentioned before.
#### Package Contribution

6
docs/en/framework/ui/mvc-razor-pages/tag-helpers/dynamic-forms.md

@ -23,7 +23,7 @@ public class DynamicFormsModel : PageModel
new SelectListItem { Value = "CA", Text = "Canada"},
new SelectListItem { Value = "US", Text = "USA"},
new SelectListItem { Value = "UK", Text = "United Kingdom"},
new SelectListItem { Value = "RU", Text = "Russia"}
new SelectListItem { Value = "RU", Text = "Turkey"}
};
public void OnGet()
@ -217,7 +217,7 @@ public class DynamicFormsModel : PageModel
new SelectListItem { Value = "CA", Text = "Canada"},
new SelectListItem { Value = "US", Text = "USA"},
new SelectListItem { Value = "UK", Text = "United Kingdom"},
new SelectListItem { Value = "RU", Text = "Russia"}
new SelectListItem { Value = "RU", Text = "Turkey"}
};
public void OnGet()
@ -278,4 +278,4 @@ public string Name { get; set; }
## See Also
* [Form Elements](form-elements.md)
* [Form Elements](form-elements.md)

2
docs/en/framework/ui/react-native/index.md

@ -17,7 +17,7 @@ ABP platform provide basic [React Native](https://reactnative.dev/) startup temp
Please follow the steps below to prepare your development environment for React Native.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v16 or v18 installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v20.11+ installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system.
2. **[Optional] Install Yarn:** You may install Yarn v1 (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v6 and below. You may skip this step and work with npm, which is built-in in Node.js, instead.
3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to React Native projects.
4. **Install an Emulator:** React Native applications need an Android emulator or an iOS simulator to run on your OS. See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/) or [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) on expo.io documentation to learn how to set up an emulator.

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-docker-dependencies.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/en/get-started/images/abp-studio-no-layers-new-solution-additional-options-0.9.13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-0.9.13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore-0.9.13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo-0.9.13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore-0.9.13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo-0.9.13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

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

Loading…
Cancel
Save