Browse Source

Merge branch 'dev' into docs-pdf

pull/22430/head
liangshiwei 2 years ago
parent
commit
a02df55bca
  1. 127
      Directory.Packages.props
  2. 7
      README.md
  3. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
  4. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  5. 35
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  6. 5
      build/common.ps1
  7. 82
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md
  8. BIN
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/community-talks.png
  9. BIN
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/cover-image.png
  10. BIN
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/upgrade-abp-packages.png
  11. BIN
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/1.png
  12. BIN
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/2.png
  13. BIN
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/3.png
  14. 183
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md
  15. BIN
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman1.png
  16. BIN
      docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman2.png
  17. 177
      docs/en/Community-Articles/2023-10-03-basta-mainz-2023-what-a-blast-in-germany/post.md
  18. 134
      docs/en/Community-Articles/2024-03-05-URL-Based-Localization/Post.md
  19. BIN
      docs/en/Community-Articles/2024-03-05-URL-Based-Localization/UrlLocalizationSampleProject.zip
  20. BIN
      docs/en/Community-Articles/2024-03-05-URL-Based-Localization/cover.png
  21. BIN
      docs/en/Community-Articles/2024-03-05-URL-Based-Localization/scrshot1.jpg
  22. BIN
      docs/en/Community-Articles/2025-02-07-event-recap-ndc-london-2025/3a180ca3de1eb496455c4c3341721ab3.jpg
  23. BIN
      docs/en/Community-Articles/2025-02-07-event-recap-ndc-london-2025/3a180ca5122043881595c75c6ea661de.jpg
  24. BIN
      docs/en/Community-Articles/2025-02-07-event-recap-ndc-london-2025/3a180ca776481fe9cb89b439988e99e1.jpg
  25. 234
      docs/en/Community-Articles/2025-02-20-Using-OutboxInbox-Pattern-for-Reliable-Event-Handling-in-a-Multi-Module-Monolithic-Application/POST.md
  26. BIN
      docs/en/Community-Articles/2025-02-20-Using-OutboxInbox-Pattern-for-Reliable-Event-Handling-in-a-Multi-Module-Monolithic-Application/index.png
  27. BIN
      docs/en/Community-Articles/2025-02-20-Using-OutboxInbox-Pattern-for-Reliable-Event-Handling-in-a-Multi-Module-Monolithic-Application/order.png
  28. BIN
      docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/3a183abfe065ba8bacdd56ef05d9a4cd.jpg
  29. BIN
      docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/3a183ac7b768a9a7523278ab07847b3c.jpg
  30. BIN
      docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/3a183ac7c4767cabf12d657ac90f4c17.jpg
  31. 28
      docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/post.md
  32. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_122158.webp
  33. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_153010.webp
  34. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_153413.webp
  35. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250305_151043.webp
  36. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4732-2000px.webp
  37. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4770-2000px.webp
  38. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4915-2000px.webp
  39. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/Screenshot_2025-03-04_at_13.47.28.webp
  40. 39
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/post.md
  41. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/abp-js-proxy.png
  42. 324
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md
  43. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-component-result.gif
  44. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission-vue.png
  45. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission.png
  46. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-counter-result.gif
  47. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-message.png
  48. BIN
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/1.png
  49. BIN
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/2.png
  50. BIN
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/3.png
  51. 248
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/post.md
  52. 2
      docs/en/cli/index.md
  53. 86
      docs/en/docs-nav.json
  54. 26
      docs/en/framework/api-development/dynamic-csharp-clients.md
  55. 24
      docs/en/framework/api-development/static-csharp-clients.md
  56. 39
      docs/en/framework/data/mongodb/index.md
  57. 73
      docs/en/framework/infrastructure/background-jobs/index.md
  58. 74
      docs/en/framework/infrastructure/background-workers/index.md
  59. 34
      docs/en/framework/infrastructure/cancellation-token-provider.md
  60. 64
      docs/en/framework/ui/blazor/basic-theme.md
  61. 19
      docs/en/framework/ui/blazor/navigation-menu.md
  62. 29
      docs/en/framework/ui/blazor/theming.md
  63. BIN
      docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png
  64. BIN
      docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png
  65. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png
  66. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png
  67. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png
  68. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png
  69. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png
  70. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png
  71. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png
  72. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png
  73. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png
  74. BIN
      docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png
  75. 16
      docs/en/get-started/microservice.md
  76. 18
      docs/en/guides/microservice-mongodb.md
  77. BIN
      docs/en/images/account-pro-external-login-settings.png
  78. BIN
      docs/en/images/account-pro-select-account-parameter.png
  79. BIN
      docs/en/images/account-pro-select-account.png
  80. BIN
      docs/en/images/books.png
  81. 17
      docs/en/index.md
  82. 39
      docs/en/kb/can-not-login-with-admin-user.md
  83. 58
      docs/en/modules/account-pro.md
  84. 16
      docs/en/modules/gdpr.md
  85. 2
      docs/en/release-info/migration-guides/abp-9-0.md
  86. 6
      docs/en/release-info/migration-guides/abp-9-1.md
  87. 2
      docs/en/release-info/migration-guides/identityserver4-step-by-step.md
  88. 4
      docs/en/solution-templates/layered-web-application/cors-configuration.md
  89. 29
      docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md
  90. 121
      docs/en/solution-templates/layered-web-application/health-check-configuration.md
  91. 2
      docs/en/solution-templates/microservice/adding-new-microservices.md
  92. 4
      docs/en/solution-templates/microservice/cors-configuration.md
  93. 110
      docs/en/solution-templates/microservice/health-check-configuration.md
  94. 4
      docs/en/solution-templates/single-layer-web-application/cors-configuration.md
  95. 115
      docs/en/solution-templates/single-layer-web-application/health-check-configuration.md
  96. BIN
      docs/en/studio/images/monitoring-applications/overall.png
  97. BIN
      docs/en/studio/images/solution-runner/csharp-application-context-menu-build.png
  98. BIN
      docs/en/studio/images/solution-runner/csharp-application-context-menu-monitor.png
  99. BIN
      docs/en/studio/images/solution-runner/csharp-application-context-menu.png
  100. BIN
      docs/en/studio/images/solution-runner/solutioın-runner-properties.png

127
Directory.Packages.props

@ -59,71 +59,74 @@
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="13.4.0" />
<PackageVersion Include="MailKit" Version="4.8.0" />
<PackageVersion Include="Markdig.Signed" Version="0.40.0" />
<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" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.30" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="9.0.30" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.2" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.2" />
<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" />
<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.Data.Sqlite" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.7.24406.2" />
<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.Extensions.Caching.Memory" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Composite" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.5.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.5.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.5.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.5.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.6.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.6.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.6.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.6.0" />
<PackageVersion Include="Minio" Version="6.0.3" />
<PackageVersion Include="MongoDB.Driver" Version="3.1.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.3" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="NuGet.Versioning" Version="6.11.1" />
<PackageVersion Include="NUglify" Version="1.21.9" />
@ -164,18 +167,18 @@
<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" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.2" />
<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.6.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.1" />
<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" />
<PackageVersion Include="System.Security.Permissions" Version="9.0.2" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<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.Text.Encoding.CodePages" Version="9.0.2" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.2" />
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.0" />
<PackageVersion Include="TencentCloudSDK.Sms" Version="3.0.1142" />
<PackageVersion Include="TimeZoneConverter" Version="6.1.0" />
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />

7
README.md

@ -1,11 +1,12 @@
# 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://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.
[![ABP Platform](https://github.com/user-attachments/assets/200653c0-0e69-4b47-b76a-3a83460aaab6)](https://abp.io)
[![ABP Platform](https://github.com/user-attachments/assets/c4356ec7-4d0f-4e00-a1d2-fc74ad985fb8)](https://abp.io)
## Getting Started

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

@ -17,6 +17,6 @@
"SelectYourOrganization": "Select your organization",
"PleaseSelectOrganization": "Please select an organization to continue",
"Continue": "Continue",
"CaptchaExplanation": "Calculate the given mathematical expression and enter the correct answer."
"CaptchaExplanation": "Calculate the below mathematical expression and enter the answer."
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -264,6 +264,7 @@
"EditProfile": "Edit Profile",
"ConfirmEmailForPost": "To be able to post, you need to confirm your email. Go to account.abp.io/Account/Manage and verify your email in the Personal Info tab.",
"DailyPostCreateLimitation": "You have reached the daily post creation limit. You can create a new post in {0}.",
"YourAccountDisabled": "Your user account is disabled!"
"YourAccountDisabled": "Your user account is disabled!",
"PostCreationFailed": "An error occurred while creating the post. Please try again later."
}
}

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

@ -672,20 +672,20 @@
"ExternalPostUrl": "External Post Url",
"ThankYouForContribution": "Thank you for contributing to the ABP Community. We accept articles and video tutorials on ABP Framework, .NET, ASP.NET Core and general software development topics.",
"GithubPost": "GitHub Post",
"GithubPostSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> Write a post on any public GitHub repository with the Markdown format. <a target=\"_blank\" class=\"fw-6 text-white text-decoration-underline\" href=\"https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md\">example</a>",
"GithubPostSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> Submit your post URL using the form.",
"GithubPostSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> Your post will be rendered in this website.",
"GithubPostSubmitStepOne": "1. Write a post on any public GitHub repository with the Markdown format. <a target=\"_blank\" class=\"text-sub-title text-decoration-underline\" href=\"https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2020-12-04-Event-Organizer/Post.md\">example</a>",
"GithubPostSubmitStepTwo": "2. Submit your post URL using the form.",
"GithubPostSubmitStepThree": "3. Your post will be rendered in this website.",
"YoutubeVideo": "Youtube Video",
"YoutubeVideoSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> Publish your video on YouTube.",
"YoutubeVideoSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> Submit the video URL using the form.",
"YoutubeVideoSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> Visitors will be able to watch your video content directly on this website.",
"YoutubeVideoSubmitStepOne": "1. Publish your video on YouTube.",
"YoutubeVideoSubmitStepTwo": "2. Submit the video URL using the form.",
"YoutubeVideoSubmitStepThree": "3. Visitors will be able to watch your video content directly on this website.",
"ExternalContent": "External Content",
"ExternalContentSubmitStepOne": "<span class=\"font-weight-bold\">1.</span> Create a content on any public platform (Medium, your own blog or anywhere you like).",
"ExternalContentSubmitStepTwo": "<span class=\"font-weight-bold\">2.</span> Submit your content URL using the form.",
"ExternalContentSubmitStepThree": "<span class=\"font-weight-bold\">3.</span> Visitors are redirected to the content on the original website.",
"ExternalContentSubmitStepOne": "1. Create a content on any public platform (Medium, your own blog or anywhere you like).",
"ExternalContentSubmitStepTwo": "2. Submit your content URL using the form.",
"ExternalContentSubmitStepThree": "3. Visitors are redirected to the content on the original website.",
"ChooseYourContentType": "Please choose the way you want to add your content.",
"PostContentViaGithub": "I want to add my post with <span class=\"icon-with-text-github\"> GitHub</span> in accordance with the markdown rules.",
"PostContentViaYoutube": "I want to share my videos available on <span class=\"icon-with-text-youtube\"> Youtube</span> here.",
"PostContentViaGithub": "I want to add my post with <span class=\"icon-with-text-github text-nowrap\"> GitHub</span> in accordance with the markdown rules.",
"PostContentViaYoutube": "I want to share my videos available on <span class=\"icon-with-text-youtube text-nowrap\"> Youtube</span> here.",
"PostContentViaExternalSource": "I want to add the content I published on <b>another platform</b> here.",
"GitHubUserNameValidationMessage": "Your Github username can not include whitespace, please make sure your Github username is correct.",
"PersonalSiteUrlValidationMessage": "Your personal site URL can not include whitespace, please make sure your personal site URL is correct.",
@ -1875,6 +1875,17 @@
"RcStableDifferenceExplanation3": "Use the RC version for testing and early adoption but use the Stable version for production deployment.",
"AddToCalendar": "Add To Calendar",
"AddToGoogleCalendar": "Add To Google Calendar",
"DownloadICSFile": "Download ICS File"
"DownloadICSFile": "Download ICS File",
"AddToAppleCalendar": "Add To Apple Calendar",
"AddToOutlookCalendar": "Add To Outlook Calendar",
"FaqIyzicoPaymentIssuesTitle" : "What should I do if I encounter issues while purchasing the Iyzico payment gateway?",
"FaqIyzicoPaymentIssuesExplanation1": "This website uses two payment gateways to receive payments: Iyzico and 2Checkout. The default gateway is Iyzico. When you check the 'Automatic Renewal' option, it will automatically use the Iyzico payment gateway. If you’re experiencing issues while trying to purchase through the Iyzico payment gateway, it could be due to the following reasons:",
"FaqIyzicoPaymentIssuesExplanation2": "<strong>Card Type</strong>: Iyzico only supports VISA and MasterCard. If you’re using a different card type (e.g., American Express), please switch to the 2Checkout payment gateway.",
"FaqIyzicoPaymentIssuesExplanation3": "<strong>3D Secure</strong>: If your card has 3D Secure enabled, you’ll be redirected to your card provider’s 3D Secure page to complete the verification process. Once done, you’ll return to Iyzico to finalize the payment. If you encounter any issues with 3D Secure, please contact your card provider for assistance. Sometimes there might be problems with the redirection to the 3D Secure provider, in that case you can disable 3D Secure or contact us.",
"FaqIyzicoPaymentIssuesExplanation4": "If you’re unable to complete the payment through Iyzico, you can use our alternative payment gateway, 2Checkout which supports a wider range of payment options, including:",
"FaqIyzicoPaymentIssuesExplanation5": "<strong>Credit/Debit Cards</strong>: Visa, MasterCard, American Express, Discover, Diners Club, JCB",
"FaqIyzicoPaymentIssuesExplanation6": "<strong>Digital Wallets</strong>: PayPal, AliPay, WebMoney",
"FaqIyzicoPaymentIssuesExplanation7": "Alternatively, you can send the license amount directly via bank wire transfer. For our bank account details, please visit: <a href='https://volosoft.com/bank-account'>Bank Account Information</a> (use USD currency).",
"FaqIyzicoPaymentIssuesExplanation8": "ABP website doesn't save or process your credit card. We use payment gateways for this and the entire transaction is handled by payment gateways. We have no authority to interfere with the payment process or fix the payment steps. If you have further questions or need additional support, feel free to contact us at <a href='https://abp.io/contact'>abp.io/contact</a>."
}
}

5
build/common.ps1

@ -23,6 +23,11 @@ $solutionPaths = @(
"../modules/blob-storing-database"
)
# Remove MAUI related projects if not on Windows
if ($env:OS -ne "Windows_NT") {
dotnet sln ../framework/Volo.Abp.sln remove ../framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj
}
if ($full -eq "-f")
{
# List of additional solutions required for full build

82
docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md

@ -0,0 +1,82 @@
# ABP.IO Platform 9.1 Final Has Been Released!
We are glad to announce that [ABP](https://abp.io/) 9.1 stable version has been released today.
## What's New With Version 9.1?
All the new features were explained in detail in the [9.1 RC Announcement Post](https://abp.io/community/articles/abp-platform-9.1-rc-has-been-released-wws5l00k), so there is no need to review them again. You can check it out for more details.
## Getting Started with 9.1
### 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.1 for now, so you don't need to specify the version.
### 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.1. 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 **Upgrade ABP Packages** action button to instantly upgrade your solution:
![](upgrade-abp-packages.png)
### 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.
## 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 v9.0: [ABP Version 9.1 Migration Guide](https://abp.io/docs/latest/release-info/migration-guides/abp-9-1)
## Community News
### New ABP Community Articles
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here:
* [URL-Based Localization](https://abp.io/community/articles/urlbased-localization-3ivzinbb) by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu)
* [Building a CRUD API with ABP Framework, ASP.NET Core, and PostgreSQL](https://abp.io/community/articles/building-a-crud-api-with-abp-framework-asp.net-core-and-postgresql-elrj0old) by [Berkan Şaşmaz](https://github.com/berkansasmaz)
* [Encryption and Decryption in ABP Framework](https://abp.io/community/articles/encryption-and-decryption-in-abp-framework-37uqhdwz) by [Liming Ma](https://github.com/maliming)
* [Migrate Your DB from the Web Application - Adding a DB Migration Controller](https://abp.io/community/articles/migrate-your-db-from-the-web-application-adding-a-db-migration-controller-in-abp-framework-x3u3uvk3) by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu)
* [Containerization: Blazor WASM + JWT Web API => Docker](https://abp.io/community/articles/containerization-blazor-wasm-jwt-web-api-docker-i3eirlsf) by [Bart Van Hoey](https://abp.io/community/members/bartvanhoey)
* [Configuring Post-Logout Redirect URI in ABP Based Blazor Applications with OpenIddict](https://abp.io/community/articles/configuring-postlogout-redirect-uri-in-abp-based-blazor-applications-with-openiddict-1t84suxg) by [Engincan Veske](https://github.com/EngincanV)
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 2025.2: Real World Problems and Solutions with AI
![](community-talks.png)
In this episode of ABP Community Talks (2025.2), Decision Tree joined us to explore how AI is being leveraged to solve real-world problems, showcasing a practical use case of AI applications.
> You can re-watch the talk from [here](https://www.youtube.com/watch?v=CXpWjxCIY_E).
## About the Next Version
The next feature version will be 9.2. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version.

BIN
docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/community-talks.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

BIN
docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/upgrade-abp-packages.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

BIN
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

BIN
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

183
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/POST.md

@ -2,17 +2,72 @@
## ITokenExtensionGrant
Create a class that inherits `ITokenExtensionGrant`, and then register it with the framework.
Create a `MyTokenExtensionGrant` class that inherits `ITokenExtensionGrant`, and then register it with the framework.
In the `MyTokenExtensionGrant` class below we try to get the token details, The `ForbidResult` handles the failure case and `SignInResult` returns a new token response, You can pass more parameters to implement business checks.
```cs
public override void PreConfigureServices(ServiceConfigurationContext context)
{
//...
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.Configure(openIddictServerOptions =>
{
openIddictServerOptions.GrantTypes.Add(MyTokenExtensionGrant.ExtensionGrantName);
});
});
//...
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
//...
Configure<AbpOpenIddictExtensionGrantsOptions>(options =>
{
options.Grants.Add(MyTokenExtensionGrant.ExtensionGrantName, new MyTokenExtensionGrant());
});
//...
}
```
## Generate a new token response
In the `MyTokenExtensionGrant` class below we have two methods to get a new token using a user token or user API key. You can choose one of them based on your business.
These methods are just examples. Please add more logic to validate input data.
```cs
using System.Collections.Immutable;
using System.Security.Principal;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using OpenIddict.Server.AspNetCore;
using Volo.Abp.Identity;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
namespace OpenIddict.Demo.Server.ExtensionGrants;
public class MyTokenExtensionGrant : ITokenExtensionGrant
{
public const string ExtensionGrantName = "MyTokenExtensionGrant";
public string Name => ExtensionGrantName;
public async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
{
// You can get a new token using any of the following methods based on your business.
// They are just examples. You can implement your own logic here.
return await HandleUserAccessTokenAsync(context);
return await HandleUserApiKeyAsync(context);
}
public async Task<IActionResult> HandleUserAccessTokenAsync(ExtensionGrantContext context)
{
var userToken = context.Request.GetParameter("token").ToString();
@ -26,6 +81,9 @@ public class MyTokenExtensionGrant : ITokenExtensionGrant
}!));
}
// We will validate the user token
// The Token is issued by the OpenIddict server, So we can validate it using the introspection endpoint
var transaction = await context.HttpContext.RequestServices.GetRequiredService<IOpenIddictServerFactory>().CreateTransactionAsync();
transaction.EndpointType = OpenIddictServerEndpointType.Introspection;
transaction.Request = new OpenIddictRequest
@ -64,23 +122,92 @@ public class MyTokenExtensionGrant : ITokenExtensionGrant
}));
}
// We have validated the user token and got the user id
var userId = principal.FindUserId();
var userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();
var user = await userManager.GetByIdAsync(userId.Value);
var userClaimsPrincipalFactory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<IdentityUser>>();
var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user);
claimsPrincipal.SetScopes(principal.GetScopes());
claimsPrincipal.SetResources(await GetResourcesAsync(context, principal.GetScopes()));
//abp version < 7.3
await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimDestinationsManager>().SetAsync(claimsPrincipal);
// Prepare the scopes
var scopes = GetScopes(context);
claimsPrincipal.SetScopes(scopes);
claimsPrincipal.SetResources(await GetResourcesAsync(context, scopes));
await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>().HandleAsync(context.Request, principal);
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal);
}
//For abp version >= 7.3
await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>().HandleAsync(context.Request, claimsPrincipal);
protected async Task<IActionResult> HandleUserApiKeyAsync(ExtensionGrantContext context)
{
var userApiKey = context.Request.GetParameter("user_api_key").ToString();
if (string.IsNullOrEmpty(userApiKey))
{
return new ForbidResult(
new[] {OpenIddictServerAspNetCoreDefaults.AuthenticationScheme},
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
}!));
}
// Here we can validate the user API key and get the user id
if (false) // Add your own logic here
{
// If the user API key is invalid
return new ForbidResult(
new[] {OpenIddictServerAspNetCoreDefaults.AuthenticationScheme},
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
}!));
}
// Add your own logic to get the user by API key
var userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();
var user = await userManager.FindByNameAsync("admin");
if (user == null)
{
return new ForbidResult(
new[] {OpenIddictServerAspNetCoreDefaults.AuthenticationScheme},
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest
}!));
}
// Create a principal for the user
var userClaimsPrincipalFactory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<IdentityUser>>();
var claimsPrincipal = await userClaimsPrincipalFactory.CreateAsync(user);
// Prepare the scopes
var scopes = GetScopes(context);
claimsPrincipal.SetScopes(scopes);
claimsPrincipal.SetResources(await GetResourcesAsync(context, scopes));
await context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>().HandleAsync(context.Request, claimsPrincipal);
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, claimsPrincipal);
}
private ImmutableArray<string> GetScopes(ExtensionGrantContext context)
{
// Prepare the scopes
// The scopes must be defined in the OpenIddict server
// If you want to get the scopes from the request, you have to add `scope` parameter in the request
// scope: AbpAPI profile roles email phone offline_access
//var scopes = context.Request.GetScopes();
// If you want to set the scopes here, you can use the following code
var scopes = new[] { "AbpAPI", "profile", "roles", "email", "phone", "offline_access" }.ToImmutableArray();
return scopes;
}
private async Task<IEnumerable<string>> GetResourcesAsync(ExtensionGrantContext context, ImmutableArray<string> scopes)
{
var resources = new List<string>();
@ -98,38 +225,24 @@ public class MyTokenExtensionGrant : ITokenExtensionGrant
}
```
```cs
public override void PreConfigureServices(ServiceConfigurationContext context)
{
//...
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.Configure(openIddictServerOptions =>
{
openIddictServerOptions.GrantTypes.Add(MyTokenExtensionGrant.ExtensionGrantName);
});
});
//...
}
### Get a new token using user access token
public override void ConfigureServices(ServiceConfigurationContext context)
{
//...
Configure<AbpOpenIddictExtensionGrantsOptions>(options =>
{
options.Grants.Add(MyTokenExtensionGrant.ExtensionGrantName, new MyTokenExtensionGrant());
});
//...
}
```
* Get a user token using the `password` grant type.
![Http request 1](1.png)
![Http request 1](postman1.png)
* Use the user token to get a new token using the `HandleUserAccessTokenAsync` method.
![Http request 2](postman2.png)
![Http request 2](2.png)
## Source code
### Get a new token using user API key
* Directly get a new token using the `HandleUserApiKeyAsync` method.
https://github.com/abpframework/abp/commit/3210f138454697647689b4868c8d4b7b3da02d44
![Http request 3](3.png)
## Source code
https://github.com/abpframework/abp/blob/dev/modules/openiddict/app/OpenIddict.Demo.Server/ExtensionGrants/MyTokenExtensionGrant.cs

BIN
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

BIN
docs/en/Community-Articles/2022-11-14-How-to-add-a-custom-grant-type-in-OpenIddict/postman2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

177
docs/en/Community-Articles/2023-10-03-basta-mainz-2023-what-a-blast-in-germany/post.md

@ -1,59 +1,118 @@
BASTA! Mainz 2023 has wrapped up, and what an extraordinary journey it has been! We can’t wait to share our impressions, highlights, and the incredible impact it had on the tech community in Germany and beyond.
### A Glance Back at BASTA! Mainz 2023
We just came back from the[ BASTA! Conference 2023](https://basta.net/), which is an incredible .NET event with over 600 in-person attendees and an additional 200+ tuning in virtually from across the globe. The buzz of excitement and anticipation in the air was undeniable, setting the stage for an unforgettable event for [ABP.IO](https://abp.io/).
![53227564247\_b7acafea68\_c.jpg](3a0e074506bcc60cad473655b6fd5da6.jpg)
![53227631612\_eb38958391\_c.jpg](3a0e074555e8d6f62bc3d47c00f86762.jpg)
![53227631792\_a6d87b4abf\_k.jpg](3a0e0745b6418472a66d905053d492e6.jpg)
As one of the proud sponsors, we set up our booth, engaged with attendees, and delved into a wide range of sessions, workshops, and keynotes to absorb the latest software development trends.
### Engaging with Enthusiastic Minds
As the lead developers of ABP Core Team, *[Alper](https://twitter.com/alperebicoglu)* and *[Ismail](https://twitter.com/ismcagdas)* presented the ABP.IO platform modules and features were quite busy enjoying their presenting work with latest version of demos. Alper also gave a great speech on “Building Multi-tenant ASP.NET Core Application & the ABP Framework” at the BASTA! Mainz conference.
![53229004005\_3312b6bfa2\_k.jpg](3a0e074625fddc3097efdc1bef3058d8.jpg)
![20230926\_170335.jpg](3a0e074675731c4a59167a878d4cd04e.jpg)
We introduced **[ABP Framework](https://abp.io/)**, community-driven open-source web application framework, and **[ABP Commercial](https://commercial.abp.io/)**, our enterprise-ready web development platform that is built on top of the open-source ABP Framework to the crowds.
![53227632222\_0a5af1fcc1\_k.jpg](3a0e074703a63129e5713ff51653aaac.jpg)
![53228506206\_24e22498fa\_o.jpg](3a0e07477e980a4c2104300557e8f7fb.jpg)
![53228505931\_b2aea761f9\_k.jpg](3a0e0747c283919bbc5edfdc289db1d8.jpg)
Apart from that, we were thrilled to give away the **[ABP Commercial Licenses](https://commercial.abp.io/pricing)** to the eager attendees on the venue, including the Raffle prize, **Meta Quest 2**, on our last day in BASTA! Conference.
![截屏2023-10-03 11.06.01.png](3a0e0748c8752dfdc2bcb5b9b2ce5908.png)
We are profoundly satisfied with the huge interest and engagement shown by the attendees. The sense of community, collaboration, and shared passion for .NET solutions was palpable throughout the event.
![20230927\_171046.jpg](3a0e07495efa58bb9f1e523d982dbc65.jpg)
![20230928\_150135.jpg](3a0e0749c23a9e3b06d66ed54bed0c7a.jpg)
![20230928\_150533 20.57.50.jpg](3a0e074a268272e630d8422ace32b4c8.jpg)
### A Great Partnership with LIS GmbH
![20230928\_161843.jpg](3a0e074aa3857a696cb941edc46dcb52.jpg)
At BASTA! Mainz 2023, we were particularly thrilled to celebrate our collaboration with ***LIS GmbH***, a leading software solutions provider. This partnership added a unique dimension to the conference, brought us a fresh perspective to the conference, fostering innovation and opening up new avenues for attendees.
### A Big Thank-You from ABP Team
![53228503821\_27860119d9\_k.jpg](3a0e074b0a46662cf39a6796d0f23086.jpg)
Now, a shout-out to BASTA! Mainz 2023. We want to thank to everyone who contributed to the success of this event – attendees, speakers, sponsors, and partners.The conference may have ended, but the knowledge gained, connections formed, and inspiration ignited will continue to shape the tech landscape for years to come.
Until next time!
BASTA! Mainz 2023 has wrapped up, and what an extraordinary journey it has been! We can’t wait to share our impressions, highlights, and the incredible impact it had on the tech community in Germany and beyond.
### A Glance Back at BASTA! Mainz 2023
We just came back from the[ BASTA! Conference 2023](https://basta.net/), which is an incredible .NET event with over 600 in-person attendees and an additional 200+ tuning in virtually from across the globe. The buzz of excitement and anticipation in the air was undeniable, setting the stage for an unforgettable event for [ABP.IO](https://abp.io/).
![53227564247\_b7acafea68\_c.jpg](3a0e074506bcc60cad473655b6fd5da6.jpg)
![53227631612\_eb38958391\_c.jpg](3a0e074555e8d6f62bc3d47c00f86762.jpg)
![53227631792\_a6d87b4abf\_k.jpg](3a0e0745b6418472a66d905053d492e6.jpg)
As one of the proud sponsors, we set up our booth, engaged with attendees, and delved into a wide range of sessions, workshops, and keynotes to absorb the latest software development trends.
### Engaging with Enthusiastic Minds
As the lead developers of ABP Core Team, *[Alper](https://twitter.com/alperebicoglu)* and *[Ismail](https://twitter.com/ismcagdas)* presented the ABP.IO platform modules and features were quite busy enjoying their presenting work with latest version of demos. Alper also gave a great speech on “Building Multi-tenant ASP.NET Core Application & the ABP Framework” at the BASTA! Mainz conference.
![53229004005\_3312b6bfa2\_k.jpg](3a0e074625fddc3097efdc1bef3058d8.jpg)
![20230926\_170335.jpg](3a0e074675731c4a59167a878d4cd04e.jpg)
We introduced **[ABP Framework](https://abp.io/)**, community-driven open-source web application framework, and **[ABP Commercial](https://commercial.abp.io/)**, our enterprise-ready web development platform that is built on top of the open-source ABP Framework to the crowds.
![53227632222\_0a5af1fcc1\_k.jpg](3a0e074703a63129e5713ff51653aaac.jpg)
![53228506206\_24e22498fa\_o.jpg](3a0e07477e980a4c2104300557e8f7fb.jpg)
![53228505931\_b2aea761f9\_k.jpg](3a0e0747c283919bbc5edfdc289db1d8.jpg)
Apart from that, we were thrilled to give away the **[ABP Commercial Licenses](https://commercial.abp.io/pricing)** to the eager attendees on the venue, including the Raffle prize, **Meta Quest 2**, on our last day in BASTA! Conference.
![截屏2023-10-03 11.06.01.png](3a0e0748c8752dfdc2bcb5b9b2ce5908.png)
We are profoundly satisfied with the huge interest and engagement shown by the attendees. The sense of community, collaboration, and shared passion for .NET solutions was palpable throughout the event.
![20230927\_171046.jpg](3a0e07495efa58bb9f1e523d982dbc65.jpg)
![20230928\_150135.jpg](3a0e0749c23a9e3b06d66ed54bed0c7a.jpg)
![20230928\_150533 20.57.50.jpg](3a0e074a268272e630d8422ace32b4c8.jpg)
### A Great Partnership with LIS GmbH
![3a0e074aa3857a696cb941edc46dcb52](https://github.com/user-attachments/assets/ea0f6d32-e49b-4573-997f-9751ef16b303)
At BASTA! Mainz 2023, we were particularly thrilled to celebrate our collaboration with ***LIS GmbH***, a leading software solutions provider. This partnership added a unique dimension to the conference, brought us a fresh perspective to the conference, fostering innovation and opening up new avenues for attendees.
### A Big Thank-You from ABP Team
![53228503821\_27860119d9\_k.jpg](3a0e074b0a46662cf39a6796d0f23086.jpg)
Now, a shout-out to BASTA! Mainz 2023. We want to thank to everyone who contributed to the success of this event – attendees, speakers, sponsors, and partners.The conference may have ended, but the knowledge gained, connections formed, and inspiration ignited will continue to shape the tech landscape for years to come.
Until next time!

134
docs/en/Community-Articles/2024-03-05-URL-Based-Localization/Post.md

@ -0,0 +1,134 @@
# URL-Based Localization
In this article I'll show you how to optimize your ABP website localization with a URL parameter. URL Paths are commonly being used to change the current UI culture. This method makes our website SEO-Friendly as you structuring the URLs for multiple languages. And you can also share the link of your website with a specific language. Let's see implementing multi-language support with URL parameters in an ABP project.
![Turkish and English Localization within URL](scrshot1.jpg)
## Mastering Website Localization: Language Codes in URLs Explained
Enhancing UX with URL-Based website localization is mainly done with ASP.NET Core's routing system. Also we need to automatically redirect the links without language code parameter. Our URL structures for the localization will be as following:
- https://mywebsite.com/en/dashboard (English)
- https://mywebsite.com/tr/dashboard (Turkish)
## Routing
Before starting to explain how to do this, you can see [this commit](https://github.com/salihozkara/MultiLangRoute/commit/09e40cfd751562dec0dab890e54e0c5ca9ee256c) which implements this functionality. The routing module consists of these fundamental classes:
### 1. **MultiLanguageSupportMetaData.cs**
This class is used to add a language parameter to route templates. For example, it changes the `/about` route to `/language/about`.
### 2. **MultiLanguageRedirectRequiredMetaData.cs**
This class is used to redirect users to the correct language version. If a user visits the `/about` page and the current culture is `"tr-TR"`, this class will redirect them to `/tr-TR/about`.
### 3. **UrlNormalizer.cs**
This static class is used to normalize URLs by adding language information and improving performance using caching.
```csharp
public static string NormalizeUrl(EndpointDataSource endpointDataSource, HttpContext httpContext, string url)
{
// Normalize the URL and cache it
return Cache.GetOrAdd(url, (key) =>
{
var absoluteUrl = GetAbsoluteUrl(key);
var multiLanguageRedirectRequiredMetaData =
GetMultiLanguageRedirectRequiredMetaData(endpointDataSource, absoluteUrl);
return multiLanguageRedirectRequiredMetaData?.ReBuildUrl(httpContext, key) ?? key;
});
}
```
### 4. **MyLinkGenerator.cs**
This class extends ASP.NET Core’s `LinkGenerator` class to automatically add language information to all generated links.
### 5. **MyRouteDataRequestCultureProvider.cs**
This class determines the current culture using the language parameter in the URL. For example, it extracts the `"tr-TR"` culture from the `/tr-TR/about` URL.
### 6. **RoutingMiddleware.cs**
This middleware processes HTTP requests and redirects users to the correct language version when necessary.
```csharp
public override Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var endpoint = context.GetEndpoint();
if(endpoint is not RouteEndpoint)
{
return next(context);
}
// Redirect if necessary
var redirectMetaData = endpoint.Metadata.GetMetadata<IRedirectMetaData>();
if (redirectMetaData is not null)
{
redirectMetaData.Redirect(context);
return Task.CompletedTask;
}
// ...
}
```
## CultureAnchorTagHelper.cs
This **Tag Helper** processes `<a>` tags in a Razor page and automatically adds language information to URLs if it's missing.
```csharp
[HtmlTargetElement("a", Attributes = "href", TagStructure = TagStructure.NormalOrSelfClosing)]
public class CultureAnchorTagHelper(EndpointDataSource endpointDataSource, IHttpContextAccessor contextAccessor)
: TagHelper, ITransientDependency
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var href = output.Attributes["href"].Value.ToString();
if (href != null)
{
output.Attributes.SetAttribute("href",
UrlNormalizer.NormalizeUrl(endpointDataSource, contextAccessor.HttpContext!, href));
}
}
}
```
**How This Tag Helper Works:**
- Finds all `<a href="...">` tags within your Razor pages.
- Retrieves the `href` attribute of each link.
- Uses the `UrlNormalizer.NormalizeUrl()` method to normalize the URL with the current culture information.
- Replaces the original URL with the normalized one.
For example, if the current culture is `"tr"` and a page contains `<a href="/about">`, this Tag Helper will transform it into `<a href="/tr-TR/about">`.
## Sample Project
Salih Özkara from ABP team created a sample working project which implements URL localization. He used ABP free tier MVC template and MongoDB. You can check out the related commit which implements URL localization:
https://github.com/salihozkara/MultiLangRoute/commit/09e40cfd751562dec0dab890e54e0c5ca9ee256c
And full working demo is available at:
https://github.com/salihozkara/MultiLangRoute
You can download the demo project at:
[UrlLocalizationSampleProject.zip](https://github.com/abpframework/abp/blob/634ff52fb07d0b1281640695dbeffccdc943ca53/docs/en/Community-Articles/2024-03-05-URL-Based-Localization/UrlLocalizationSampleProject.zip)
## Summary
This implementation extends ASP.NET Core’s routing mechanism and utilizes caching for improved performance. It ensures multilingual support by normalizing URLs, redirecting users to the appropriate language version, and automatically handling language-specific links.

BIN
docs/en/Community-Articles/2024-03-05-URL-Based-Localization/UrlLocalizationSampleProject.zip

Binary file not shown.

BIN
docs/en/Community-Articles/2024-03-05-URL-Based-Localization/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

BIN
docs/en/Community-Articles/2024-03-05-URL-Based-Localization/scrshot1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/en/Community-Articles/2025-02-07-event-recap-ndc-london-2025/3a180ca3de1eb496455c4c3341721ab3.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
docs/en/Community-Articles/2025-02-07-event-recap-ndc-london-2025/3a180ca5122043881595c75c6ea661de.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
docs/en/Community-Articles/2025-02-07-event-recap-ndc-london-2025/3a180ca776481fe9cb89b439988e99e1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

234
docs/en/Community-Articles/2025-02-20-Using-OutboxInbox-Pattern-for-Reliable-Event-Handling-in-a-Multi-Module-Monolithic-Application/POST.md

@ -0,0 +1,234 @@
# Using Outbox/Inbox Pattern for Reliable Event Handling in a Multi-Module Monolithic Application
This article explains how to implement reliable event handling using the `Outbox/Inbox` pattern in a modular monolithic application with multiple databases. We'll use the `ModularCRM` project as an example (how that project was created is explained in [this document](https://abp.io/docs/latest/tutorials/modular-crm)).
## Project Background
`ModularCRM` is a monolithic application that integrates multiple ABP framework open-source modules, including:
- `Account`
- `Identity`
- `Tenant Management`
- `Permission Management`
- `Setting Management`
- And other open-source modules
Besides the ABP framework modules, the project contains three business modules:
- Order module (`Ordering`), using `MongoDB` database
- Product module (`Products`), using `SQL Server` database
- Payment module (`Payment`), using `MongoDB` database
The project configures separate database connection strings for `ModularCRM` and the three business modules in `appsettings.json`:
```json
{
"ConnectionStrings": {
"Default": "Server=localhost,1434;Database=ModularCrm;User Id=sa;Password=1q2w3E***;TrustServerCertificate=true",
"Products": "Server=localhost,1434;Database=ModularCrm_Products;User Id=sa;Password=1q2w3E***;TrustServerCertificate=true",
"Ordering": "mongodb://localhost:27017/ModularCrm_Ordering?replicaSet=rs0",
"Payment": "mongodb://localhost:27017/ModularCrm_Payment?replicaSet=rs0"
}
}
```
## Business Scenario
These modules communicate through the ABP framework's `DistributedEventBus` to implement the following business flow:
> This is a simple example flow. Real business flows are more complex. The sample code is for demonstration purposes.
1. Order module: Publishes `OrderPlacedEto` event when an order is placed
2. Product module: Subscribes to `OrderPlacedEto` event and reduce product stock
3. Payment module: Subscribes to `OrderPlacedEto` event, processes payment, then publishes `PaymentCompletedEto` event
4. Order module: Subscribes to `PaymentCompletedEto` event and updates order status to `Delivered`
When implementing this flow, we need to ensure:
- Transaction consistency between order creation and event publishing
- Transaction consistency when modules process messages
- Reliable message delivery (including persistence, confirmation, and retry mechanisms)
Using the default implementation of the ABP framework's distributed event bus cannot meet these requirements, so we need to add a new mechanism that is also provided by the ABP Framework.
## Outbox/Inbox Pattern Solution
To meet these requirements, we use the `Outbox/Inbox` pattern:
### Outbox Pattern
- Saves distributed events with database operations in the same transaction
- Sends events to distributed message service through background jobs
- Ensures consistency between data updates and event publishing
- Prevents message loss during system failures
### Inbox Pattern
- First saves received distributed events to the database
- Processes events in a transactional way
- Ensures messages are processed only once by saving processed message records
- Maintains processing state for reliable handling
> For how to enable and configure `Outbox/Inbox` in projects and modules, see: https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events
### Module Configuration
Each module needs to configure separate `Outbox/Inbox`. Since it's a monolithic application, all message processing classes are in the same project, so we need to configure `Outbox/Inbox` for each module with `Selector/EventSelector` to ensure that the module only sends and receives the messages it cares about, avoiding message duplication processing.
**ModularCRM Main Application Configuration**
It will send and receive messages from all ABP framework open-source modules.
```csharp
// This selector will match all abp built-in modules and the current module.
Func<Type, bool> abpModuleSelector = type => type.Namespace != null && (type.Namespace.StartsWith("Volo.") || type.Assembly == typeof(ModularCrmModule).Assembly);
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure("ModularCrm", config =>
{
config.UseDbContext<ModularCrmDbContext>();
config.EventSelector = abpModuleSelector;
config.HandlerSelector = abpModuleSelector;
});
options.Outboxes.Configure("ModularCrm", config =>
{
config.UseDbContext<ModularCrmDbContext>();
config.Selector = abpModuleSelector;
});
});
```
**Order Module Configuration**
It only sends `OrderPlacedEto` events and receives `PaymentCompletedEto` events and executes `OrderPaymentCompletedEventHandler`.
```csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure(OrderingDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext<IOrderingDbContext>();
config.EventSelector = type => type == typeof(PaymentCompletedEto);
config.HandlerSelector = type => type == typeof(OrderPaymentCompletedEventHandler);
});
options.Outboxes.Configure(OrderingDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext<IOrderingDbContext>();
config.Selector = type => type == typeof(OrderPlacedEto);
});
});
```
> Here, the `EventSelector` and `HandlerSelector` checks only a single type. If you have multiple events and event handlers, you can check the given type if it is included in an array of types.
**Product Module Configuration**
It only receives `EntityCreatedEto<UserEto>` and `OrderPlacedEto` events and executes `ProductsOrderPlacedEventHandler` and `ProductsUserCreatedEventHandler`. It does not send any events now.
```csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure(ProductsDbProperties.ConnectionStringName, config =>
{
config.UseDbContext<IProductsDbContext>();
config.EventSelector = type => type == typeof(EntityCreatedEto<UserEto>) || type == typeof(OrderPlacedEto);
config.HandlerSelector = type => type == typeof(ProductsOrderPlacedEventHandler) || type == typeof(ProductsUserCreatedEventHandler);
});
// Outboxes are not used in this module
options.Outboxes.Configure(ProductsDbProperties.ConnectionStringName, config =>
{
config.UseDbContext<IProductsDbContext>();
config.Selector = type => false;
});
});
```
**Payment Module Configuration**
It only sends `PaymentCompletedEto` events and receives `OrderPlacedEto` events and executes `PaymentOrderPlacedEventHandler`.
```csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure(PaymentDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext<IPaymentMongoDbContext>();
config.EventSelector = type => type == typeof(OrderPlacedEto);
config.HandlerSelector = type => type == typeof(PaymentOrderPlacedEventHandler);
});
options.Outboxes.Configure(PaymentDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext<IPaymentMongoDbContext>();
config.Selector = type => type == typeof(PaymentCompletedEto);
});
});
```
## Running ModularCRM Simulation Business Flow
1. Run the following command in the `ModularCrm` directory:
```
# Start SQL Server and MongoDB databases in Docker
docker-compose up -d
# Restore and install project npm dependencies
abp install-lib
# Migrate databases
dotnet run --project ModularCrm --migrate-database
# Start the application
dotnet run --project ModularCrm
```
2. Navigate to `https://localhost:44303/` to view the application homepage
![index](index.png)
3. Enter a customer name and select a product, then submit an order. After a moment, refresh the page to see the order, product, and payment information.
![order](order.png)
Application logs display the complete processing flow:
```
[Ordering Module] Order created: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9, CustomerName: john
[Products Module] OrderPlacedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, CustomerName: john, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9
[Products Module] Stock count decreased for ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9
[Payment Module] OrderPlacedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, CustomerName: john, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9
[Payment Module] Payment processing completed for OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88
[Ordering Module] PaymentCompletedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, PaymentId: d0a41ead-ee0f-714c-e254-3a1834504d65, PaymentMethod: CreditCard, PaymentAmount: ModularCrm.Payment.Payment.PaymentCompletedEto
[Ordering Module] Order state updated to Delivered for OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88
```
In addition, when a new user registers, the product module will also receive the `EntityCreatedEto<UserEto>` event, and we will send an email to the new user, just to demonstrate the `Outbox/Inbox Selector` mechanism.
```
[Products Module] UserCreated event received: UserId: "9a1f2bd0-5b28-210a-9e56-3a18344d310a", UserName: admin
[Products Module] Sending a popular products email to admin@abp.io...
```
## Summary
By introducing the `Outbox/Inbox` pattern, we have achieved:
1. Transactional message sending and receiving
2. Reliable message processing mechanism
3. Modular event processing in a multi-database environment
ModularCRM project not only implements reliable message processing but also demonstrates how to handle multi-database scenarios gracefully in a monolithic application. Project source code: https://github.com/abpframework/abp-samples/tree/master/ModularCrm-OutboxInbox-Pattern
## Reference
- [Outbox/Inbox for transactional events](https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events)
- [ConnectionStrings](https://abp.io/docs/latest/framework/fundamentals/connection-strings)
- [ABP Studio: Single Layer Solution Template](https://abp.io/docs/latest/solution-templates/single-layer-web-application)

BIN
docs/en/Community-Articles/2025-02-20-Using-OutboxInbox-Pattern-for-Reliable-Event-Handling-in-a-Multi-Module-Monolithic-Application/index.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
docs/en/Community-Articles/2025-02-20-Using-OutboxInbox-Pattern-for-Reliable-Event-Handling-in-a-Multi-Module-Monolithic-Application/order.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/3a183abfe065ba8bacdd56ef05d9a4cd.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

BIN
docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/3a183ac7b768a9a7523278ab07847b3c.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/3a183ac7c4767cabf12d657ac90f4c17.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

28
docs/en/Community-Articles/2025-02-21-catch-us-at-basta-frankfurt-2025/post.md

@ -0,0 +1,28 @@
We are excited to share some fantastic news with our community! We are proud to announce that ABP.IO is going to be at the BASTA! Conference on March 03-07, 2025 in Frankfurt and İsmail Çağdaş from our dev team is going to be a speaker on March 04!
#### **About BASTA!**
For those who don’t know, BASTA! is the leading independent conference for Microsoft technologies in the German-speaking world. For over 20 years, it has been setting standards in the areas of C#, .NET and cloud and web technologies and is considered a must-attend event for Microsoft, cloud, web developers and key players in the software industry. BASTA! is a conference for developers and IT professionals who want to stay up to date with the latest technologies.
#### **ABP at [BASTA! Mainz 2023](https://abp.io/blog/BASTA-Mainz-2023-What-a-Blast-in-Germany)**
![basta 2023.jpg](3a183abfe065ba8bacdd56ef05d9a4cd.jpg)
![2222.jpg](3a183ac7b768a9a7523278ab07847b3c.jpg)
![3333.jpg](3a183ac7c4767cabf12d657ac90f4c17.jpg)
#####
#### **What to Expect at [BASTA! 2025](https://basta.net/frankfurt-en/)**
The most exciting part is İsmail Çağdaş from the ABP developer team will be speaking at the conference on March 04 about the concepts of monoliths and microservices will be briefly explored, along with how modular monoliths bring together the advantages of these two architectures. Using the ABP Framework as an example, the session will highlight its modularity features and demonstrate how it can assist in creating and developing a modular monolith application. Finally, best practices for developing modular monoliths will be discussed, showing how these practices can pave the way for transitioning to a microservice-based architecture when necessary.
If you want to find out more information about İsmail Çağdaş's session, [check here](https://basta.net/microservices-apis/modular-monoliths-architecture-abp/?loc=ffm):
#### **Connect with Us**
We have exciting raffles and surprises planned at our booth and look forward to sharing more information about our solutions with you there.
#### **Join Us Online**
Don't worry if you can't join us in person, our online booth is going to be there for you! The Expo of the online version of BASTA! is open for the main conference days.
Tuesday, March 4, 2025: 9:00 am – approx. 6:00 pm
Wednesday, March 5, 2025: 9:00 am – approx. 6:00 pm
Thursday, March 6, 2025: 9:00 am – approx. 5:45 pm

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_122158.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_153010.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_153413.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250305_151043.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4732-2000px.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4770-2000px.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4915-2000px.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/Screenshot_2025-03-04_at_13.47.28.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

39
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/post.md

@ -0,0 +1,39 @@
![](20250304_122158.webp)
Our team had an amazing time at BASTA\! Frankfurt 2025, held from March 3 to 8 at the Frankfurt am Main Marriott Hotel. As a sponsor for this major conference for asp.net web developers, we were thrilled to connect with so many talented participants.
**Event Highlights**
![](20250304_153010.webp)
![](20250304_153413.webp)
The conference hosted many talented developers as speakers. One of them was İsmail Çağdaş who’s a lead developer from ABP team who talked about the concepts of monoliths and microservices. He explained how modular monoliths combine the strengths of these two architectures, using the ABP Framework as an example to showcase its modularity features and demonstrate how to build and develop a modular monolith application.
**ABP’S Presence**
![](IMG_4732-2000px.webp)
![](IMG_4770-2000px.webp)
At our booth we displayed the latest features of the ABP Framework and gathered valuable feedback from especially dotnet developers. So many attendees showed interest in ABP which was very exciting. It was great engaging with so many participants who wanted to learn more about how ABP provides the infrastructure and tools to create business solutions.
We're also very grateful to our booth neighbor, Xceed and it was a lot of fun connecting with them during the conference. For those who don’t know, Xceed provides comprehensive UI components that allow developers to focus on innovation and their business requirements.
**Networking and Community Engagement**
We organized two raffles during the event, where attendees had the chance to win 2 great prizes. One attendee won a LEGO set and another won an Amazon Kindle. We were happy to see many people attending our raffles, it definitely made this event more fun. Congratulations to the winners and thanks to those who participated\!
![](IMG_4915-2000px.webp)
![](20250305_151043.webp)
**Looking Ahead**
BASTA\! Frankfurt 2025 strengthened our commitment to the developer community. We want to continue our support for asp.net core developers, helping them create asp.net applications and optimize their workflows for web applications.
**Gratitude and Future Events**
Thank you to the organizers, speakers, and attendees for making BASTA\! Frankfurt 2025 such a fantastic experience. We look forward to future events and continued contributions to the net framework developers.
We look forward to sharing more updates with you soon. We hope to see you at our next event\!

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/abp-js-proxy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

324
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md

@ -0,0 +1,324 @@
# Using Vue components in a Razor Pages ABP Application
In modern web development, integrating dynamic front-end frameworks with server-side technologies has become increasingly essential for creating responsive and interactive applications. This article explores how to effectively use Vue components within Razor Pages in an ABP Framework application. We will delve into the process of consuming endpoints through ABP Client Proxies, leveraging ABP's powerful localization features to enhance user experience, and implementing ABP permissions to ensure secure access control. By the end of this guide, you will have a comprehensive understanding of how to seamlessly blend Vue.js with Razor Pages, empowering you to build robust and user-friendly applications.
This article won't use any SPA approach. The goal of this article is to use Razor Pages with simple Vue components to eliminate jQuery while developing MVC application.
> **🎉 Also video version is available!**
>
> [Watch on YouTube Now!](https://youtu.be/sZ8iSMovHZs?si=GynuJjsLEI1p2g6w)
## Creating the Solution
Let's create a simple TODO list application to demonstrate how to use Vue components in Razor Pages. I'll build a really simple backend without a connection to a database for demonstration purposes. We will focus on the frontend part.
- Creating a solution with ABP CLI:
```bash
abp new MyTodoApp -t app-nolayers -csf
```
## Configure Vue
We need to add the `@abp/vue` package to the project to use Vue components.
```bash
npm install @abp/vue
```
- Install client libraries by using ABP CLI:
```bash
abp install-libs
```
As a last step, we need to configure our bundle in the `ConfigureBundles` method in the `MyTodoAppModule.cs` file:
```csharp
private void ConfigureBundles()
{
Configure<AbpBundlingOptions>(options =>
{
// ...
options.ScriptBundles.Configure(
// Or BasicThemeBundles.Scripts.Global
// Or LeptonXLiteThemeBundles.Scripts.Global
// 👇 Depends on the theme you are using
LeptonXThemeBundles.Scripts.Global,
bundle =>
{
bundle.AddFiles("/global-scripts.js");
// 👇 Make sure to add this line
bundle.AddContributors(typeof(VueScriptContributor));
}
);
});
}
```
> If your IDE doesn't recognize the namespace of the `VueScriptContributor`, you can add it manually:
>
> ```csharp
> using Volo.Abp.AspNetCore.Mvc.UI.Packages.Vue;
> ```
Now we're ready to use Vue components in our Razor Pages.
## Creating a Vue Component
Let's create a simple Vue component to display the TODO list.
### Passing a simple message to the component
- Remove existing HTML codes in `Index.cshtml` and replace with the following code:
```html
<div id="vue-app">
<message-component :message="'Welcome, @CurrentUser.UserName !'"></todo-component>
</div>
```
- Navigate to the `Index.cshtml.js` file and add the following code:
```js
Vue.component('message-component', {
template: '<div>Hello, {{ message }}</div>',
props: ['message']
});
new Vue({
el: '#vue-app'
});
```
Run the application and you should see the following output:
![Vue Component](./vue-message.png)
> _Hard refresh might be required to see the component since we added a new vue js file to the bundle._
>
> If still you can't see the component, please check the browser console for any errors.
### Interacting with the component
Let's add a button to the component to interact with the component.
- Add another component in the `Index.cshtml` file:
```html
<div id="vue-app">
<message-component :message="'Welcome, @CurrentUser.UserName !'"></message-component>
<counter-component></counter-component>
</div>
```
```js
Vue.component('counter-component', {
template:`
<div class="card">
<div class="card-body">
<p>Count: {{ count }}</p>
<button class="btn btn-primary" @click="increment">Increment</button>
</div>
</div>
`,
data: function () {
return {
count: 0
};
},
methods: {
increment: function () {
this.count++;
}
}
});
```
> _Do not replicate `new Vue({})` code block in the file. It's already in the `Index.cshtml.js` file. Keep it at the bottom of the file as it is._
Run the application and you should see the following output:
![Vue Component](./vue-counter-result.gif)
## Using ABP Client Proxy, Authorization and Localization
### Building the backend
Before we go, let's build our backend to use in the component.
- Creating a simple Application Service:
```csharp
public class TodoAppService : MyTodoAppAppService, ITodoAppService
{
public static List<TodoItem> Items { get; } = new List<TodoItem>();
[Authorize("Todo.Create")]
public async Task<TodoItem> AddTodoItemAsync(TodoItem input)
{
Items.Add(input);
return input;
}
[Authorize("Todo")]
public async Task<List<TodoItem>> GetAllAsync()
{
await Task.Delay(1500);
return Items;
}
}
```
- `TodoItem.cs`
```csharp
public class TodoItem
{
public string Description { get; set; }
public bool IsDone { get; set; }
}
```
- `ITodoAppService.cs`
```csharp
public interface ITodoAppService
{
Task<List<TodoItem>> GetAllAsync();
Task<TodoItem> AddTodoItemAsync(TodoItem input);
}
```
- Run the application and if you can see the following client proxy in the browser console, you're ready to go:
![Client Proxy](./abp-js-proxy.png)
> [!NOTE]
> If you can't see the client proxy in the browser console, please check the [Dynamic JavaScript Proxies](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/dynamic-javascript-proxies) to learn how to enable it.
- Add a new permission in the `MyTodoAppPermissionDefinitionProvider.cs` file:
```csharp
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(MyTodoAppPermissions.GroupName);
var todo = myGroup.AddPermission("Todo");
todo.AddChild("Todo.Create");
}
```
> _I go without localization or constants for simplicity._
- Add a localization key in the `en.json` file:
```json
{
"TodoItems": "Todo Items Localized"
}
```
### Building the Vue Component: Using ABP Localization, Authorization and Client Proxy
Since the component it directly loaded into the page, we can access the `abp` object on the page.
So we can use:
- `abp.localization.localize()` to localize a string.
- `abp.auth.isGranted()` to check the authorization.
- `myTodoApp.todo.getAll()` and `myTodoApp.todo.addTodoItem` to call the Application Service.
inside **Vue Component** code.
- Let's add another component named `todo-component` and usee all the **ABP Features** in it.
```html
<div id="vue-app">
<!-- ... -->
<todo-component></todo-component>
</div>
```
- Implement the `todo-component` in `Index.cshtml.js` file:
```js
Vue.component('todo-component', {
template: `
<div class="card" v-if="abp.auth.isGranted('Todo')">
<div class="card-header border-bottom">
<h3>{{ abp.localization.localize('TodoItems') }}</h3>
</div>
<div class="card-body">
<div v-if="isBusy" class="w-100 text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<ul v-else-if="todos.length > 0" class="list-group">
<li class="list-group-item" v-for="item in todos" :key="item.description">
<input class="form-check-input" type="checkbox" v-model="item.isDone">
<label class="form-check-label">{{ item.description }}</label>
</li>
</ul>
<p v-else>No todos yet</p>
</div>
<div v-if="abp.auth.isGranted('Todo.Create')" class="card-footer d-flex flex-column gap-2 border-top pt-2">
<input class="form-control" type="text" v-model="newTodo.description" placeholder="Add a new todo">
<div class="form-check">
<input class="form-check-input" type="checkbox" v-model="newTodo.isDone" id="isDone">
<label class="form-check-label" for="isDone">Is Done</label>
</div>
<button class="btn btn-primary" @click="addTodo">Add</button>
</div>
</div>
`,
data: function () {
return {
newTodo: {
description: '',
isDone: false
},
isBusy: false,
todos: []
};
},
methods: {
addTodo() {
myTodoApp.todo.addTodoItem(this.newTodo);
this.newTodo = { description: '', isDone: false };
this.todos.push(this.newTodo);
// Preferrable, you can load entire list of todos again.
// this.loadTodos();
},
async loadTodos() {
if (!abp.auth.isGranted('Todo')) {
return;
}
this.isBusy = true;
this.todos = await myTodoApp.todo.getAll();
this.isBusy = false;
}
},
mounted() {
this.loadTodos();
}
});
```
And see the result:
![Vue Component](./todo-component-result.gif)
Since we use `abp.auth.isGranted()` to check the authorization, we can see the component only if we have the permission.
Whenever you remove `Todo.Create` permission, you can see the component is not rendered.
![Todo Permission](./todo-permission.png)
You won't see the card footer:
![Todo Permission](./todo-permission-vue.png)

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-component-result.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission-vue.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-counter-result.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-message.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

BIN
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

248
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/post.md

@ -0,0 +1,248 @@
# Understanding the Embedded Files in ABP Framework
Embedded Files functionality in .NET applications allows external files (like configuration files, images, etc.) to be directly embedded into assemblies (.exe or .dll). This simplifies deployment, prevents file loss or tampering, improves security and performance, and reduces path and dependency management issues. Through embedded resources, programs can access these files more conveniently without additional file operations.
## Embedding Files in Your Project
We embed `Volo\Abp\MyModule\Localization\*.json` files into the assembly in our `MyModule.csproj`.
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<None Remove="Volo\Abp\MyModule\Localization\*.json" />
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" />
</ItemGroup>
</Project>
```
If we check the `en.json` file in our IDE, we'll see it's embedded in the assembly.
![image](1.png)
When we decompile the built `MyModule.dll` file, we can also see the `en.json` file.
![image](2.png)
## Accessing Embedded Files in Code
```csharp
public class Program
{
public static async Task<int> Main(string[] args)
{
var embeddedFiles = typeof(Program).Assembly.GetManifestResourceNames();
foreach (var embeddedFile in embeddedFiles)
{
Console.WriteLine(embeddedFile);
var fileStream = typeof(Program).Assembly.GetManifestResourceStream(embeddedFile);
if (fileStream != null)
{
using var reader = new System.IO.StreamReader(fileStream);
var content = await reader.ReadToEndAsync();
Console.WriteLine(content);
}
}
}
}
```
This code will output the embedded file names and their contents.
```
MyModule.Volo.Abp.MyModule.Localization.en.json
{
"key":"value"
}
```
## Integrating with ABP Virtual File System
The ABP Virtual File System makes it possible to manage files that don't physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime.
The following code shows how to add embedded files from the current application assembly to the ABP virtual file system:
```csharp
[DependsOn(typeof(AbpVirtualFileSystemModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>();
});
}
}
```
ABP creates an `AbpEmbeddedFileProvider` to access the embedded files.
The full name of `en.json` is `MyModule.Volo.Abp.MyModule.Localization.en.json`. Without directory information, ABP uses `.` to split and assume directory information. This creates the following directory structure in the virtual file system:
```
[Dir] [/MyModule]
[Dir] [/MyModule/Volo]
[Dir] [/MyModule/Volo/Abp]
[Dir] [/MyModule/Volo/Abp/MyModule]
[Dir] [/MyModule/Volo/Abp/MyModule/Localization]
[File] [/MyModule/Volo/Abp/MyModule/Localization/en.json]
```
Now you can inject `IVirtualFileProvider` to access embedded files using the directory/file structure above.
## Manifest Embedded File Provider
You might have noticed that using `.` to split and assume directory information can cause confusion if filenames contain dots.
For example, if your filename is `zh.hans.json`, ABP will generate the following directory structure, which isn't what we want:
```
[Dir] [/MyModule]
[Dir] [/MyModule/Volo]
[Dir] [/MyModule/Volo/Abp]
[Dir] [/MyModule/Volo/Abp/MyModule]
[Dir] [/MyModule/Volo/Abp/MyModule/Localization]
[Dir] [/MyModule/Volo/Abp/MyModule/Localization/zh]
[File] [/MyModule/Volo/Abp/MyModule/Localization/zh/hans.json]
```
Microsoft provides the `Microsoft.Extensions.FileProviders.Manifest` library to solve this problem.
We need to add this package dependency and set `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` in our project:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Manifest" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<None Remove="Volo\Abp\MyModule\Localization\*.json" />
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" />
</ItemGroup>
</Project>
```
After rebuilding the project, when we decompile `MyModule.dll`, we'll see an additional `Microsoft.Extensions.FileProviders.Embedded.Manifest.xml` file.
![image](3.png)
This manifest file stores all the directory and file information of embedded resources. When ABP finds this file, it will use `ManifestEmbeddedFileProvider` instead of `AbpEmbeddedFileProvider` to access embedded files:
```xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Manifest>
<ManifestVersion>1.0</ManifestVersion>
<FileSystem>
<File Name="Microsoft.Extensions.FileProviders.Embedded.Manifest.xml">
<ResourcePath>Microsoft.Extensions.FileProviders.Embedded.Manifest.xml</ResourcePath>
</File>
<Directory Name="Volo">
<Directory Name="Abp">
<Directory Name="MyModule">
<Directory Name="Localization">
<File Name="zh.hans.json">
<ResourcePath>MyModule.Volo.Abp.MyModule.Localization.zh.hans.json</ResourcePath>
</File>
</Directory>
</Directory>
</Directory>
</Directory>
</FileSystem>
</Manifest>
```
## Parameters of AddEmbedded Method
The `AddEmbedded` method can take two parameters:
### baseNamespace
This may only be needed if you haven't used the `Manifest Embedded File Provider` and your project's `root namespace` isn't empty. In this case, set your root namespace here.
The `root namespace` is your project's name by default. You can change it or set it to empty in the `csproj` file.
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>MyModule</RootNamespace>
</PropertyGroup>
</Project>
```
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace></RootNamespace>
</PropertyGroup>
</Project>
```
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>(baseNamespace: "MyModule");
});
```
```
[Dir] [/Volo]
[Dir] [/Volo/Abp]
[Dir] [/Volo/Abp/MyModule]
[Dir] [/Volo/Abp/MyModule/Localization]
[File] [/Volo/Abp/MyModule/Localization/en.json]
```
### baseFolder
If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), you can set the base folder relative to your project root folder.
> baseFolder is only effective when using `Manifest Embedded File Provider`.
You can set the `baseFolder` parameter to `/Volo/Abp/MyModule`, resulting in this directory structure:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>(baseFolder: "/Volo/Abp/MyModule");
});
```
```
[Dir] [Localization]
[File] [Localization/en.json]
```
## Summary
We recommend using the `Manifest Embedded File Provider` in your projects and libraries. Hope this article has been helpful.
## References
[ABP Virtual File System](https://abp.io/docs/latest/framework/infrastructure/virtual-file-system)
[Manifest Embedded File Provider](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/file-providers#manifest-embedded-file-provider)

2
docs/en/cli/index.md

@ -918,6 +918,8 @@ Usage:
abp bundle [options]
````
> This command is no longer needed if you are using Global Assets feature. See [Managing Global Scripts & Styles](../framework/ui/blazor/global-scripts-styles.md) for more information.
#### Options
* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file.

86
docs/en/docs-nav.json

@ -783,78 +783,6 @@
"path": "framework/architecture",
"isIndex": true
},
{
"text": "Module Development Best Practices",
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices",
"isIndex": true
},
{
"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",
"isIndex": true
},
{
"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",
"isIndex": true
},
{
"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",
"isIndex": true
},
{
"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"
}
]
}
]
},
{
"text": "Modularity",
"items": [
@ -970,7 +898,8 @@
"items": [
{
"text": "Overview",
"path": "framework/architecture/best-practices"
"path": "framework/architecture/best-practices",
"isIndex": true
},
{
"text": "Module Architecture",
@ -981,7 +910,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",
@ -1002,7 +932,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",
@ -1019,7 +950,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",
@ -1032,7 +964,7 @@
]
}
]
}
},
]
},
{

26
docs/en/framework/api-development/dynamic-csharp-clients.md

@ -163,6 +163,30 @@ context.Services.AddHttpClientProxies(
`remoteServiceConfigurationName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
#### Remote Service Configuration Provider
You may need to get the remote service configuration for a specific remote service in some cases. For this, you can use the `IRemoteServiceConfigurationProvider` interface.
**Example: Get the remote service configuration for the "BookStore" remote service**
````csharp
public class MyService : ITransientDependency
{
private readonly IRemoteServiceConfigurationProvider _remoteServiceConfigurationProvider;
public MyService(IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider)
{
_remoteServiceConfigurationProvider = remoteServiceConfigurationProvider;
}
public async Task GetRemoteServiceConfiguration()
{
var configuration = await _remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("BookStore");
Console.WriteLine(configuration.BaseUrl);
}
}
````
### As Default Services
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature.
@ -204,6 +228,8 @@ public override void PreConfigureServices(ServiceConfigurationContext context)
This example uses the [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly) package. You also need to import the `Polly` namespace (`using Polly;`) to be able to use the `WaitAndRetryAsync` method.
## See Also
* [Static C# Client Proxies](./static-csharp-clients.md)

24
docs/en/framework/api-development/static-csharp-clients.md

@ -244,6 +244,30 @@ context.Services.AddStaticHttpClientProxies(
`remoteServiceConfigurationName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint.
#### Remote Service Configuration Provider
You may need to get the remote service configuration for a specific remote service in some cases. For this, you can use the `IRemoteServiceConfigurationProvider` interface.
**Example: Get the remote service configuration for the "BookStore" remote service**
````csharp
public class MyService : ITransientDependency
{
private readonly IRemoteServiceConfigurationProvider _remoteServiceConfigurationProvider;
public MyService(IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider)
{
_remoteServiceConfigurationProvider = remoteServiceConfigurationProvider;
}
public async Task GetRemoteServiceConfiguration()
{
var configuration = await _remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("BookStore");
Console.WriteLine(configuration.BaseUrl);
}
}
````
### Retry/Failure Logic & Polly Integration
If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module class.

39
docs/en/framework/data/mongodb/index.md

@ -309,16 +309,39 @@ public class BookService
### Transactions
MongoDB supports multi-document transactions starting from the version 4.0 and the ABP supports it. However, the [startup template](../../../solution-templates) **disables** transactions by default. If your MongoDB **server** supports transactions, you can enable the it in the *YourProjectMongoDbModule* class:
MongoDB supports multi-document transactions starting from the version 4.0 and the ABP supports it. However, the [startup template](../../../solution-templates) **disables** transactions by default. If your MongoDB **server** supports transactions, you can enable them in the *YourProjectMongoDbModule* class:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
});
Remove the following code to enable transactions:
```diff
- context.Services.AddAlwaysDisableUnitOfWorkTransaction();
- Configure<AbpUnitOfWorkDefaultOptions>(options =>
- {
- options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
- });
```
> Or you can delete this code since this is already the default behavior.
#### Setting up a Transaction-Enabled MongoDB Replica Set in Docker
Use the following `docker-compose.yml` to create a local MongoDB Replica Set that supports transactions. The connection string will be `mongodb://localhost:27017/YourProjectName?replicaSet=rs0`.
```yaml
version: "3.8"
services:
mongo:
image: mongo:8.0
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports:
- 27017:27017
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]}) }" | mongosh --port 27017 --quiet
interval: 5s
timeout: 30s
start_period: 0s
start_interval: 1s
retries: 30
```
### Advanced Topics
@ -510,4 +533,4 @@ public class MyCustomMongoDbBulkOperationProvider
* [Entities](../../architecture/domain-driven-design/entities.md)
* [Repositories](../../architecture/domain-driven-design/repositories.md)
* [Video tutorial](https://abp.io/video-courses/essentials/abp-mongodb)
* [Video tutorial](https://abp.io/video-courses/essentials/abp-mongodb)

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

@ -236,6 +236,79 @@ Background Jobs module implements `IBackgroundJobStore` using various data acces
> Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice.
### Using the Same Storage for Background Jobs & Workers
If multiple applications share the same storage for background jobs and workers (`Default, Hangfire, RabbitMQ, and Quartz`), you should configure the provider options to use the application name for isolation.
#### Default Background Job/Workers
Set `ApplicationName` property in `AbpBackgroundJobWorkerOptions` to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
{
options.ApplicationName = context.Services.GetApplicationName()!;
});
}
````
#### Hangfire Background Job/Workers
Set `DefaultQueuePrefix` property in `AbpHangfireOptions` to your application's name:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpHangfireOptions>(options =>
{
options.DefaultQueuePrefix = context.Services.GetApplicationName()!;
});
}
````
#### Quartz Background Job/Workers
Set the `quartz.scheduler.instanceName` property to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzOptions>(options =>
{
options.Properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = context.Services.GetApplicationName(),
["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp",
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
["quartz.serializer.type"] = "json",
["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Default"),
["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer",
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
};
});
}
````
#### RabbitMQ Background Job
Set `DefaultQueueNamePrefix` and `DefaultDelayedQueueNamePrefix` properties in `AbpRabbitMqBackgroundJobOptions` to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpRabbitMqBackgroundJobOptions>(options =>
{
options.DefaultQueueNamePrefix = context.Services.GetApplicationName()!.EndsWith('.') + options.DefaultQueueNamePrefix;
options.DefaultDelayedQueueNamePrefix = context.Services.GetApplicationName()!.EndsWith('.') + options.DefaultDelayedQueueNamePrefix;
});
}
````
### Clustered Deployment
The default background job manager is compatible with [clustered environments](../../../deployment/clustered-environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](../distributed-locking.md) to ensure that the jobs are executed only in a single application instance at a time.

74
docs/en/framework/infrastructure/background-workers/index.md

@ -132,6 +132,80 @@ If that's a problem for your workers, you have the following options:
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers.
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance.
### Using the Same Storage for Background Jobs & Workers
If multiple applications share the same storage for background jobs and workers (`Default, Hangfire, RabbitMQ, and Quartz`), you should configure the provider options to use the application name for isolation.
#### Default Background Job/Workers
Set `ApplicationName` property in `AbpBackgroundJobWorkerOptions` to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
{
options.ApplicationName = context.Services.GetApplicationName()!;
});
}
````
#### Hangfire Background Job/Workers
Set `DefaultQueuePrefix` property in `AbpHangfireOptions` to your application's name:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpHangfireOptions>(options =>
{
options.DefaultQueuePrefix = context.Services.GetApplicationName()!;
});
}
````
#### Quartz Background Job/Workers
Set `quartz.scheduler.instanceName` property to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzOptions>(options =>
{
options.Properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = context.Services.GetApplicationName(),
["quartz.jobStore.dataSource"] = "BackgroundJobsDemoApp",
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
["quartz.serializer.type"] = "json",
["quartz.dataSource.BackgroundJobsDemoApp.connectionString"] = configuration.GetConnectionString("Default"),
["quartz.dataSource.BackgroundJobsDemoApp.provider"] = "SqlServer",
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
};
});
}
````
#### RabbitMQ Background Job
Set `DefaultQueueNamePrefix` and `DefaultDelayedQueueNamePrefix` properties in `AbpRabbitMqBackgroundJobOptions` to your application's name:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpRabbitMqBackgroundJobOptions>(options =>
{
options.DefaultQueueNamePrefix = context.Services.GetApplicationName()!.EndsWith('.') + options.DefaultQueueNamePrefix;
options.DefaultDelayedQueueNamePrefix = context.Services.GetApplicationName()!.EndsWith('.') + options.DefaultDelayedQueueNamePrefix;
});
}
````
## Integrations
Background worker system is extensible and you can change the default background worker manager with your own implementation or on of the pre-built integrations.

34
docs/en/framework/infrastructure/cancellation-token-provider.md

@ -1,10 +1,14 @@
# Cancellation Token Provider
A `CancellationToken` enables cooperative cancellation between threads, thread pool work items, or `Task` objects. To handle the possible cancellation of the operation, ABP provides `ICancellationTokenProvider` to obtain the `CancellationToken` itself from the source.
A [`CancellationToken`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) enables cooperative cancellation between threads, thread pool work items, or `Task` objects. To handle the possible cancellation of the operation, ABP provides `ICancellationTokenProvider` to obtain the `CancellationToken` itself from the source.
> To get more information about `CancellationToken`, see [Microsoft Documentation](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken).
## When To Use Manual Cancellation Tokens
## ICancellationTokenProvider
**ABP automates cancellation token usage wherever possible**. For example, in ASP.NET Core applications, ABP automatically obtains the `CancellationToken` from the `HttpContext.RequestAborted` and uses it in database queries and other cancellable places. So, most of the times, you don't need to deal with `CancellationToken` objects to pass them between methods.
> You do not need to use the `ICancellationTokenProvider` unless you want to add cancellation support in your own logic or to pass a cancellation token to a method outside of the ABP framework.
## ICancellationTokenProvider Service
`ICancellationTokenProvider` is an abstraction to provide `CancellationToken` for different scenarios.
@ -39,33 +43,27 @@ namespace MyProject
}
```
## Built-in providers
## Built-in Providers
- `NullCancellationTokenProvider`
- `HttpContextCancellationTokenProvider`: The **default provider** for ASP.NET Core applications. It simply provides a `CancellationToken` that is source of the web request from the `HttpContext.RequestAborted`.
The `NullCancellationTokenProvider` is a built in provider and it supply always `CancellationToken.None`.
- `NullCancellationTokenProvider`: A built in provider and it supply always `CancellationToken.None`. It is used if no other providers can be used.
- `HttpContextCancellationTokenProvider`
The `HttpContextCancellationTokenProvider` is a built in default provider for ABP Web applications. It simply provides a `CancellationToken` that is source of the web request from the `HttpContext`.
## Implementing a Custom Cancellation Token Provider
## Implementing the ICancellationTokenProvider
You can easily create your CancellationTokenProvider by creating a class that implements the `ICancellationTokenProvider` interface, as shown below:
You can easily create your `ICancellationTokenProvider` implementation by creating a class that implements the `ICancellationTokenProvider` interface, as shown below:
```csharp
using System.Threading;
namespace AbpDemo
{
public class MyCancellationTokenProvider : ICancellationTokenProvider
public class MyCancellationTokenProvider
: ICancellationTokenProvider,
ITransientDependency // Can also be singleton or scoped
{
public CancellationToken Token { get; }
private MyCancellationTokenProvider()
{
}
public CancellationToken Token { get; } // TODO: Return a cancellation token
}
}
```

64
docs/en/framework/ui/blazor/basic-theme.md

@ -19,17 +19,26 @@ If you need to manually this theme, follow the steps below:
{{if UI == "Blazor"}}
* Install the [Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) NuGet package to your web project.
* Add `AbpAspNetCoreComponentsWebAssemblyBasicThemeModule` into the `[DependsOn(...)]` attribute for your [module class](../../architecture/modularity/basics.md) in the your Blazor UI project.
* Use `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic.App` as the root component of your application in the `ConfigureServices` method of your module:
```csharp
var builder = context.Services.GetSingletonInstance<WebAssemblyHostBuilder>();
builder.RootComponents.Add<App>("#ApplicationContainer");
```
`#ApplicationContainer` is a selector (like `<div id="ApplicationContainer">Loading...</div>`) in the `index.html`.
* Execute `abp bundle` command under blazor project once.
* Install the [Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling) NuGet package to your `Blazor` project.
* Add `AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule` into the `[DependsOn(...)]` attribute for your [module class](../../architecture/modularity/basics.md) in the your `Blazor` project.
* Install the [Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) NuGet package to your `Blazor.Client` project.
* Add `AbpAspNetCoreComponentsWebAssemblyBasicThemeModule` into the `[DependsOn(...)]` attribute for your [module class](../../architecture/modularity/basics.md) in the your `Blazor.Client` project.
Update `Routes.razor` file in `Blazor.Client` project as below:
````csharp
@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic
@using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="WebAppAdditionalAssembliesHelper.GetAssemblies<YourBlazorClientModule>()">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
</Found>
</Router>
````
{{end}}
@ -41,21 +50,38 @@ If you need to manually this theme, follow the steps below:
* Add `AbpAspNetCoreComponentsServerBasicThemeModule` into the `[DependsOn(...)]` attribute for your [module class](../../architecture/modularity/basics.md) in the your Blazor UI project.
* Perform following changes in `Pages/_Host.cshtml` file
* Perform following changes in `App.razor` file
* Add usings at the top of the page.
```html
@using Volo.Abp.AspNetCore.Components.Server.BasicTheme.Bundling
@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic
```
* Add Basic theme style bundles between `<head>` tags.
```html
<abp-style-bundle name="@BlazorBasicThemeBundles.Styles.Global" />
* Then replace script & style bunles as following
```
* Add `App` component of Basic Theme in the body section of page.
```html
<component type="typeof(App)" render-mode="Server" />
<AbpStyles BundleName="@BlazorBasicThemeBundles.Styles.Global" />
```
```
<AbpScripts BundleName="@BlazorBasicThemeBundles.Scripts.Global" />
```
Update `Routes.razor` file as below:
````csharp
@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic
@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing
@using Microsoft.Extensions.Options
@inject IOptions<AbpRouterOptions> RouterOptions
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="RouterOptions.Value.AdditionalAssemblies">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
</Found>
</Router>
````
{{end}}
## The Layout

19
docs/en/framework/ui/blazor/navigation-menu.md

@ -123,6 +123,25 @@ if (await context.IsGrantedAsync("MyPermissionName"))
> You can use `context.AuthorizationService` to directly access to the `IAuthorizationService`.
### Custom Components
Custom components can be used to render the menu items instead of the default components of the theme. The extension method named `UseComponent` from `Volo.Abp.UI.Navigation` namespace can be used to set a custom component for a menu item.
**Example: Set a custom component for a menu item**
```csharp
context.Menu.Items.Add(
new ApplicationMenuItem("MyProject.Crm.About", "About", "/about")
.UseComponent<AboutMenuItemComponent>()
);
```
_AboutMenuItemComponent.razor component will be renderd for the menu item._
### Resolving Dependencies
`context.ServiceProvider` can be used to resolve any service dependency.

29
docs/en/framework/ui/blazor/theming.md

@ -106,22 +106,35 @@ using Volo.Abp.Bundling;
namespace MyTheme
{
public class MyThemeBundleContributor : IBundleContributor
public class MyThemeBundleContributor : BundleContributor
{
public void AddScripts(BundleContext context)
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("_content/MyTheme/styles.css");
}
}
}
````
public void AddStyles(BundleContext context)
```cs
[DependsOn(
typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule)
)]
public class MyBlazorWebAssemblyBundlingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBundlingOptions>(options =>
{
context.Add("_content/MyTheme/styles.css");
}
// Add style bundle
options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global)
.AddContributors(typeof(MyThemeBundleContributor));
});
}
}
````
```
`styles.css` file should be added into the `wwwroot` folder of the theme project for this example. When you use the `abp bundle` command, this class is automatically discovered and executed to add the style to the page.
`styles.css` file should be added into the `wwwroot` folder of the theme project for this example.
See the [Global Styles and Scripts](global-scripts-styles.md) document for more.

BIN
docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 44 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 55 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 48 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 58 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 43 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 90 KiB

BIN
docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 45 KiB

16
docs/en/get-started/microservice.md

@ -49,7 +49,11 @@ On that screen, you can decide on your database provider by selecting one of the
![abp-studio-new-solution-dialog-database-configurations](images/abp-studio-new-solution-dialog-database-configurations-microservice.png)
Here, select the DBMS right for you, then click the *Next* button to navigate to the *UI Framework* selection:
Here, select the DBMS right for you, then click the *Next* button to navigate to the *Multi-Tenancy* selection:
![abp-studio-new-solution-dialog-multi-tenancy](images/abp-studio-new-solution-dialog-multi-tenancy.png)
On that screen, you can enable multi-tenancy for your solution. After selecting this option, click the Next button to proceed to the *UI Framework* selection screen.
![abp-studio-new-solution-dialog-ui-framework](images/abp-studio-new-solution-dialog-ui-framework-microservice.png)
@ -67,6 +71,10 @@ Pick the one best for you, or select the *None* if you don't want a mobile appli
You can select a public website to be created in your solution. The public website is a simple landing page that can be used to introduce your product, provide documentation, and so on.
![abp-studio-new-solution-dialog-dynamic-localization](images/abp-studio-new-solution-dialog-dynamic-localization.png)
On that screen, you can enable dynamic localization. After selecting this option, click the Next button to proceed to the *Optional Modules* selection screen.
![abp-studio-new-microservice-solution-dialog-optional-modules](images/abp-studio-new-microservice-solution-dialog-optional-modules.png)
Each item in that list is a pre-built application module. You can click the blue icon near to the module name to get more information about the module. You can leave the list as is (so, it installs the most common and used modules for you) or customize based on your preference.
@ -88,6 +96,12 @@ Click the Next button to see *Additional Options* selection:
If you unchecked the *Kubernetes Configuration* option, the solution will not include the Kubernetes configuration files which include the Helm charts and other Kubernetes related files. You can also specify *Social Logins*; if you uncheck this option, the solution will not be configured for social login. Lastly, you can specify the *Include Tests* option to include the test projects in the solution.
Click the Next button to see *Additional Services* screen:
![abp-studio-new-solution-dialog-additional-services](images/abp-studio-new-solution-dialog-additional-services.png)
On that screen, allows you to include extra microservices in your ABP solution during the creation process. This feature lets you extend your solution with business-specific services right from the start.
Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the *Create* button, the dialog is closed and your solution is loaded into ABP Studio:
![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png)

18
docs/en/guides/microservice-mongodb.md

@ -1,12 +1,12 @@
# Change Microservice project to use MongoDB Provider
This guide explains how to change Microservice project to use MongoDB as the database provider.
This guide explains how to change the Microservice project to use MongoDB as the database provider.
## Create a new Microservice project
Use the ABP Suite to create a new Microservice project, in this guide we use `BookStore` as project name.
> You can use [ABP Studio](../studio/index.md) to create a new Microservice project. In this guide, we assume that you have already created a microservice solution and used `BookStore` as your project name.
You will get the solution as shown below:
Currently, your microservice solution should be similar to that in the figure below:
![microservce-mongodb-project](../images/microservice-mongodb.png)
@ -16,9 +16,9 @@ You need to replace all EF Core references with MongoDB and update module depend
Example:
* Remove `Volo.Abp.EntityFrameworkCore.SqlServer` Nuget package reference.
* Remove `Microsoft.EntityFrameworkCore.Tools` Nuget package reference
* Remove `AbpEntityFrameworkCoreSqlServerModule` depend.
* Remove `Volo.Abp.EntityFrameworkCore.SqlServer` NuGet package reference. (instead use `Volo.Abp.MongoDB` NuGet package and also add the relevant depends on statement)
* Remove `Microsoft.EntityFrameworkCore.Tools` NuGet package reference.
* Remove `AbpEntityFrameworkCoreSqlServerModule` dependency.
* Replace `Volo.Abp.PermissionManagement.EntityFrameworkCore` with `Volo.Abp.PermissionManagement.MongoDB`.
* Update `AbpPermissionManagementEntityFrameworkCoreModule` to `AbpPermissionManagementMongoDbModule`.
@ -43,9 +43,9 @@ Here we use `BookStore.ProductService` project as an example:
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(b =>
{
b.CollectionName = ProductServiceDbProperties.DbTablePrefix + "Products";
});
{
b.CollectionName = ProductServiceDbProperties.DbTablePrefix + "Products";
});
}
}
```

BIN
docs/en/images/account-pro-external-login-settings.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 485 KiB

BIN
docs/en/images/account-pro-select-account-parameter.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/images/account-pro-select-account.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
docs/en/images/books.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

17
docs/en/index.md

@ -93,21 +93,12 @@ ABP provides a UI theming system where you can use [pre-built themes](ui-themes)
## Books
### Mastering ABP Framework
You can check the following books to expertise the ABP platform:
<img width="302" height="372" src="images/abp-book.png" alt="Mastering ABP - Halil İbrahim Kalkan" />
![books](images/books.png)
*Mastering ABP Framework* is an ultimate guide to get started and expertise with the ABP Framework. It is authored by Halil İbrahim Kalkan, the creator and the lead developer of the ABP Framework.
**[Click here to see the details and order the book](https://abp.io/books/mastering-abp-framework)**.
### Free E-Book: Implementing Domain Driven Design
<img width="441" height="331" src="images/implementing-domain-driven-design-book.png" alt="Implementing Domain Driven Design">
A **practical guide** for implementing Domain Driven Design (DDD). While the implementation details are **based on the ABP** infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution.
**[Click here to get your free copy](https://abp.io/books/implementing-domain-driven-design?ref=doc).**
* [See the details and **order** the **Mastering ABP Framework** book](https://abp.io/books/mastering-abp-framework)
* [**Download** your free copy of the **Implementing Domain Driven Design** book](https://abp.io/books/implementing-domain-driven-design?ref=doc)
## Samples

39
docs/en/kb/can-not-login-with-admin-user.md

@ -1,5 +1,36 @@
# KB#0003: Can not login with the admin user
# KB#0003: Cannot login with the admin user
* Try username `admin` and Password `1q2w3E*`.
* Try to migrate database. If you have a `DbMigrator` application in your solution, use it. It will seed initial data and create the admin user for you.
* If not works, read the README.MD file in your solution, or check the [Getting Started](https://abp.io/docs/latest/get-started) document.
## Use the Correct Username and Password
You may have entered the wrong password. The username is `admin`, and the password is `1q2w3E*`. Note that the password is case-sensitive.
## Forgot to Seed Initial Data
You may need to add migrations and update the database using the EF Core CLI. If your solution includes a `DbMigrator` application, you must run the `DbMigrator` application to seed the initial data.
If your project does not include a `DbMigrator` application, there might be a `migrate-database.ps1` script available. You can use it to migrate and seed the initial data.
> The no-layer application typically support a `--migrate-database` option for migrating and seeding initial data.
> Example:
> ```bash
> dotnet run --migrate-database
> ```
## Tenant Admin User
If you cannot log in as a tenant admin user, ensure the tenant database is created and seeded, Use the password that was set during tenant creation.
> The tenant seeding process is handled by the template project. If it is not completed, please check the `Logs` file for any error logs.
## Check the `AbpUsers` Table
If you have performed migration and seeded the initial data, check the `AbpUsers` table in the database. Ensure that the user record exists. If your tenant has a separate database, check the tenant database as well.
Passwords are stored in hashed format, not plain text. If you suspect the password is incorrect, you can delete the user record and re-seed the initial data using the `DbMigrator` application or the `migrate-database.ps1` script.
## Other Issues
If the issue persists, refer to the `README.MD` file in your solution or consult the [Getting Started](https://abp.io/docs/latest/get-started) documentation.
Feel free to create an issue in the [ABP GitHub repository](https://github.com/abpframework/abp/issues/new/choose) or contact [ABP Commercial Support](https://abp.io/support/questions/New) for assistance.

58
docs/en/modules/account-pro.md

@ -104,6 +104,48 @@ If you use `Social / External Logins`, It is automatically called for authentica
![account-pro-module-local-login-setting](../images/account-pro-module-local-login-setting.png)
### Switching users during OAuth login
If you have an OAuth/Auth Server application using the Account Pro module, you can pass the `prompt=select_account` parameter to force the user to select an account.
Example to pass `prompt=select_account` parameter in OpenIdConnect:
```csharp
.AddAbpOpenIdConnect("oidc", options =>
{
// ...
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = redirectContext =>
{
redirectContext.ProtocolMessage.Prompt = "select_account";
return Task.CompletedTask;
}
};
// ...
});
```
![account-pro-secect-account](../images/account-pro-select-account-parameter.png)
You have three options:
- Continue: The login process will continue with the current account.
- Switch to another account: Will be redirected to the login page to log in with another account.
- Create a new account: Will be redirected to the register page to create a new account.
> The OAuth login process will continue after the user selects one of the options.
![account-pro-secect-account](../images/account-pro-select-account.png)
All available prompt parameters:
| **Parameter** | **Description** |
|------------------|-------------------------------------------------------------------------------------------------------------|
| `login` | Forces the user to re-authenticate, even if they are already logged in. |
| `consent` | Forces the user to re-consent to the requested permissions, even if they have consented before. |
| `select_account` | Forces the user to select an account, even if they are already logged in (especially relevant if multiple accounts are available). |
| `none` | Does not trigger any prompt. If the user is not logged in, or their consent is not granted, it will return an error or redirect accordingly. |
## Social / External Logins
@ -113,7 +155,7 @@ The application startup template comes with **Twitter**, **Google** and **Micros
![account-pro-external-login-settings](../images/account-pro-external-login-settings.png)
The social/External login system is compatible with the multi-tenancy. Each tenant can configure their own provider settings if your application is multi-tenant.
The social/External login system is compatible with the multi-tenancy. Each tenant can enable or disable the external login provider and configure their own provider settings if your application is multi-tenant.
### Install a new External Login
@ -137,7 +179,7 @@ context.Services.AddAuthentication()
facebook.Scope.Add("public_profile");
})
.WithDynamicOptions<FacebookOptions>(
FacebookDefaults.AuthenticationScheme,
FacebookDefaults.AuthenticationScheme, // Facebook
options =>
{
options.WithProperty(x => x.AppId);
@ -149,6 +191,18 @@ context.Services.AddAuthentication()
* `AddFacebook()` is the standard method that you can set hard-coded configuration.
* `WithDynamicOptions<FacebookOptions>` is provided by the Account Module which makes possible to configure the provided properties on the UI.
#### Localize Provider Properties
You can add following translation to localize the properties of the external login providers:
`en.json`:
````json
"ExternalProvider:Facebook": "Facebook",
"ExternalProvider:Facebook:AppId": "App ID",
"ExternalProvider:Facebook:AppSecret": "App Secret",
````
### IPostConfigureAccountExternalProviderOptions
Some external logins may be initialized based on dynamic properties. You can implement an `IPostConfigureAccountExternalProviderOptions` to initialize again after dynamic properties are initialized.

16
docs/en/modules/gdpr.md

@ -48,7 +48,14 @@ The "Personal Data" page is used to manage personal data requests. You can view
![gdpr](../images/gdpr-personal-data-page.png)
To see the other features of the GDPR module, visit [the module description page](https://abp.io/modules/Volo.Gdpr).
The GDPR module is designed for distributed architectures. When a user requests their personal data, the module publishes two events:
- `GdprUserDataRequestedEto`: Triggers personal data collectors to prepare user data
- `GdprUserDataDeletionRequestedEto`: Triggers personal data collectors to delete user data
You can subscribe to these events to implement custom data collection and deletion logic in your modules. See the [Distributed Events](#distributed-events) section for more details.
> To see the other features of the GDPR module, visit [the module description page](https://abp.io/modules/Volo.Gdpr).
## Options
@ -227,8 +234,13 @@ This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#e
### GdprUserDataPreparedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is used to save the collected personal data into a single JSON file by module.
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is used to save the collected personal data into a single JSON file per module. Typically, you don't need to implement this event handler since the module already has an implementation that returns the collected data within a zip file containing multiple JSON files, with each file containing data collected from a specific module.
### GdprUserDataDeletionRequestedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published when a user requests to permanently delete their personal data and account. By default, only the `IdentityGdprEventHandler` in the [Identity Pro Module](../modules/identity-pro) subscribes to this event to anonymize the user's data and delete their account (using soft-delete unless configured otherwise).
If you want to delete additional sensitive user data stored in other modules, you can subscribe to this event and implement custom deletion (or anonymization) logic in those modules.
## Cookie Consent

2
docs/en/release-info/migration-guides/abp-9-0.md

@ -2,7 +2,7 @@
This document is a guide for upgrading ABP v8.x solutions to ABP v9.0. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
> ABP has upgraded to .NET 9.0, so if you plan to use ABP 9.0, you’ll need to migrate your solutions to .NET 9.0. You can refer to the [Migrate from ASP.NET Core 8.0 to 9.0](https://learn.microsoft.com/en-us/aspnet/core/migration/80-90) documentation for guidance. However, ABP’s NuGet packages are compatible with both .NET 8 and .NET 9, allowing developers to continue using .NET 8 while still enjoying the latest features and improvements of the ABP Framework without upgrading their SDK.
> ABP has upgraded to .NET 9.0, so if you plan to use ABP 9.0, you’ll need to migrate your solutions to .NET 9.0. You can refer to the [Migrate from ASP.NET Core 8.0 to 9.0](https://learn.microsoft.com/en-us/aspnet/core/migration/80-90) documentation for guidance.
## Open-Source (Framework)

6
docs/en/release-info/migration-guides/abp-9-1.md

@ -2,6 +2,8 @@
This document is a guide for upgrading ABP v9.0 solutions to ABP v9.1. There are no breaking changes in this version that would affect your application.
Only you might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the following migration guide:
You might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the following migration guide:
- [OpenIddict 5.x to 6.x Migration Guide](./openiddict5-to-6.md)
- [OpenIddict 5.x to 6.x Migration Guide](./openiddict5-to-6.md)
In addition, ABP version 9.1 has been upgraded to incorporate Angular version 19. Consequently, we recommend migrating your application to [Angular v19](https://angular.dev/update-guide) to ensure compatibility.

2
docs/en/release-info/migration-guides/identityserver4-step-by-step.md

@ -76,7 +76,7 @@ typeof(AbpPermissionManagementDomainIdentityServerModule),
DataSeeder is the most important part for starting the application since it seeds the initial data for both OpenID providers.
- Create a folder named *IdentityServer* under the Domain project and copy the [IdentityServerDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/IdentityServer/IdentityServerDataSeedContributor.cs) under this folder. **Rename** all the `OpenId2Ids` with your project name.
- Create a folder named *IdentityServer* under the Domain project and copy the [IdentityServerDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/1dc297255ca22af02ef6d71092dbc1b394f9260a/Ids2OpenId/src/Ids2OpenId.Domain/IdentityServer/IdentityServerDataSeedContributor.cs) under this folder. **Rename** all the `OpenId2Ids` with your project name.
- Delete *OpenIddict* folder that contains `OpenIddictDataSeedContributor.cs` which is no longer needed.
### EntityFrameworkCore Layer

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

@ -8,8 +8,8 @@
"Path": "solution-templates/layered-web-application/blob-storing"
},
"Next": {
"Name": "Helm Charts and Kubernetes",
"Path": "solution-templates/layered-web-application/helm-charts-and-kubernetes"
"Name": "Health Check Configuration",
"Path": "solution-templates/layered-web-application/health-check-configuration"
}
}
```

29
docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md

@ -254,6 +254,35 @@ We can visit the websites from a browser.
![Tiered IIS deployment](../../../images/iis-sample-tiered-deployment.gif)
## Fix 405 Method Not Allowed Error
Remove `WebDAV` modules and handlers from the `Web.config` file.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
</configuration>
```
Also remove the `WebDAV Publishing` feature from your computer if it's not being used. To do so, follow these steps:
1. Select Start, type Turn Windows features on or off in the Start Search box, and then select Turn Windows features on or off.
2. In the Windows Features window, expand Internet Information Services -> World Wide Web Services -> Common HTTP Features.
3. Uncheck the WebDAV Publishing feature.
See:
- https://learn.microsoft.com/en-us/aspnet/web-api/overview/testing-and-debugging/troubleshooting-http-405-errors-after-publishing-web-api-applications#resolve-http-405-errors
- https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/site-behavior-performance/http-error-405-website#resolution-for-cause-3
## How to get stdout-log
If your application is running on IIS and getting errors like `502.5, 500.3x`, you can enable stdout logs to see the error details.

121
docs/en/solution-templates/layered-web-application/health-check-configuration.md

@ -0,0 +1,121 @@
# Layered Solution: Health Check Configuration
```json
//[doc-nav]
{
"Previous": {
"Name": "CORS Configuration",
"Path": "solution-templates/single-layer-web-application/cors-configuration"
},
"Next": {
"Name": "Helm Charts and Kubernetes",
"Path": "solution-templates/layered-web-application/helm-charts-and-kubernetes"
}
}
```
Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The layered solution template comes with pre-configured Health Check system.
In the layered solution template, Health Check configuration is applied in the following cases:
- When [MVC](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#mvc) is selected as the web application type.
- When [Blazor Server](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-server) is selected as the web application type.
- When [Blazor WebAssembly](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-webassembly) is selected as the web application type (configured at the backend).
- When [Blazor WebApp](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-webapp) is selected as the web application type (configured at the backend).
- When [Angular](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#angular) is selected as the web application type (configured at the backend).
- When [No UI](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#no-ui) is selected as the web application type (configured at the backend).
### Configuration in `HealthChecksBuilderExtensions.cs`
Health Checks are configured in the `HealthChecksBuilderExtensions` class. This class extends `IServiceCollection` to register health check services and configure health check UI endpoints.
#### Default Configuration
The default setup is as follows:
```csharp
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
namespace MyCompanyName.MyProjectName.HealthChecks;
public static class HealthChecksBuilderExtensions
{
public static void AddMyProjectNameHealthChecks(this IServiceCollection services)
{
// Add your health checks here
var healthChecksBuilder = services.AddHealthChecks();
healthChecksBuilder.AddCheck<MyProjectNameDatabaseCheck>("MyProjectName DbContext Check", tags: new string[] { "database" });
// Read configuration for health check URL
var configuration = services.GetConfiguration();
var healthCheckUrl = configuration["App:HealthCheckUrl"] ?? "/health-status";
services.ConfigureHealthCheckEndpoint(healthCheckUrl);
// Configure HealthChecks UI
var healthChecksUiBuilder = services.AddHealthChecksUI(settings =>
{
settings.AddHealthCheckEndpoint("MyProjectName Health Status", healthCheckUrl);
});
// Set HealthCheck UI storage
healthChecksUiBuilder.AddInMemoryStorage();
services.MapHealthChecksUiEndpoints(options =>
{
options.UIPath = "/health-ui";
options.ApiPath = "/health-api";
});
}
private static IServiceCollection ConfigureHealthCheckEndpoint(this IServiceCollection services, string path)
{
....
}
private static IServiceCollection MapHealthChecksUiEndpoints(this IServiceCollection services, Action<global::HealthChecks.UI.Configuration.Options>? setupOption = null)
{
....
}
}
```
### Database Health Check Implementation
The `MyProjectNameDatabaseCheck` class is a custom implementation of a health check that verifies database connectivity using `IIdentityRoleRepository`.
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
namespace MyCompanyName.MyProjectName.HealthChecks;
public class MyProjectNameDatabaseCheck : IHealthCheck, ITransientDependency
{
protected readonly IIdentityRoleRepository IdentityRoleRepository;
public MyProjectNameDatabaseCheck(IIdentityRoleRepository identityRoleRepository)
{
IdentityRoleRepository = identityRoleRepository;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
await IdentityRoleRepository.GetListAsync(sorting: nameof(IdentityRole.Id), maxResultCount: 1, cancellationToken: cancellationToken);
return HealthCheckResult.Healthy($"Could connect to database and get record.");
}
catch (Exception e)
{
return HealthCheckResult.Unhealthy($"Error when trying to get database record. ", e);
}
}
}
```

2
docs/en/solution-templates/microservice/adding-new-microservices.md

@ -24,6 +24,8 @@ To add a new microservice to the solution, you can use the `service_nolayers` te
In ABP Studio [Solution Explorer](../../studio/solution-explorer.md#adding-a-new-microservice-module), right-click on the `services` folder and select `Add` -> `New Module` -> `Microservice`.
You can also add microservices during the project creation process by using the Additional Services screen. For more details, refer to the [Additional Services](../../get-started/microservice.md) section.
![new-microservice](images/new-microservice.png)
It opens the `Create New Module` dialog. Enter the name of the new microservice, specify the output directory if needed, and click the `Next` button. There is a naming convention: the *Module name* should include the solution name as a prefix, and the use of the dot (.) character in the *Module name* is not allowed.

4
docs/en/solution-templates/microservice/cors-configuration.md

@ -4,8 +4,8 @@
//[doc-nav]
{
"Next": {
"Name": "Communication in the Microservice solution",
"Path": "solution-templates/microservice/communication"
"Name": "Health Check Configuration",
"Path": "solution-templates/microservice/health-check-configuration"
}
}
````

110
docs/en/solution-templates/microservice/health-check-configuration.md

@ -0,0 +1,110 @@
# Microservice Solution: Health Check Configuration
```json
//[doc-nav]
{
"Next": {
"Name": "Communication in the Microservice solution",
"Path": "solution-templates/microservice/communication"
}
}
```
Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The Microservice solution template comes with pre-configured Health Check system.
In the Microservice solution template, Health Check configuration is applied in all the services, gateways and UI applications (except Blazor Wasm & Blazor WebApp applications UI applications).
### Configuration in `HealthChecksBuilderExtensions.cs`
Health Checks are configured in the `HealthChecksBuilderExtensions` class. This class extends `IServiceCollection` to register health check services and configure health check UI endpoints.
#### Default Configuration
The default setup is as follows:
```csharp
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
namespace MyCompanyName.MyProjectName.HealthChecks;
public static class HealthChecksBuilderExtensions
{
public static void AddMyProjectNameHealthChecks(this IServiceCollection services)
{
// Add your health checks here
var healthChecksBuilder = services.AddHealthChecks();
healthChecksBuilder.AddCheck<MyProjectNameDatabaseCheck>("MyProjectName DbContext Check", tags: new string[] { "database" });
// Read configuration for health check URL
var configuration = services.GetConfiguration();
var healthCheckUrl = configuration["App:HealthCheckUrl"] ?? "/health-status";
services.ConfigureHealthCheckEndpoint(healthCheckUrl);
// Configure HealthChecks UI
var healthChecksUiBuilder = services.AddHealthChecksUI(settings =>
{
settings.AddHealthCheckEndpoint("MyProjectName Health Status", healthCheckUrl);
});
// Set HealthCheck UI storage
healthChecksUiBuilder.AddInMemoryStorage();
services.MapHealthChecksUiEndpoints(options =>
{
options.UIPath = "/health-ui";
options.ApiPath = "/health-api";
});
}
private static IServiceCollection ConfigureHealthCheckEndpoint(this IServiceCollection services, string path)
{
....
}
private static IServiceCollection MapHealthChecksUiEndpoints(this IServiceCollection services, Action<global::HealthChecks.UI.Configuration.Options>? setupOption = null)
{
....
}
}
```
### Database Health Check Implementation
The `MyProjectNameDatabaseCheck` class is a custom implementation of a health check that verifies database connectivity in the applications with database connection. Example:
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
namespace MyCompanyName.MyProjectName.HealthChecks;
public class MyProjectNameDatabaseCheck : IHealthCheck, ITransientDependency
{
protected readonly IIdentityRoleRepository IdentityRoleRepository;
public MyProjectNameDatabaseCheck(IIdentityRoleRepository identityRoleRepository)
{
IdentityRoleRepository = identityRoleRepository;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
await IdentityRoleRepository.GetListAsync(sorting: nameof(IdentityRole.Id), maxResultCount: 1, cancellationToken: cancellationToken);
return HealthCheckResult.Healthy($"Could connect to database and get record.");
}
catch (Exception e)
{
return HealthCheckResult.Unhealthy($"Error when trying to get database record. ", e);
}
}
}
```

4
docs/en/solution-templates/single-layer-web-application/cors-configuration.md

@ -6,6 +6,10 @@
"Previous": {
"Name": "BLOB Storing",
"Path": "solution-templates/single-layer-web-application/blob-storing"
},
"Next": {
"Name": "Health Check Configuration",
"Path": "solution-templates/single-layer-web-application/health-check-configuration"
}
}
```

115
docs/en/solution-templates/single-layer-web-application/health-check-configuration.md

@ -0,0 +1,115 @@
# Single Layer Solution: Health Check Configuration
```json
//[doc-nav]
{
"Previous": {
"Name": "CORS Configuration",
"Path": "solution-templates/single-layer-web-application/cors-configuration"
}
}
```
Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The single-layer solution template comes with pre-configured Health Check system.
In the single-layer solution template, Health Check configuration is applied in the following cases:
- When [MVC](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#mvc) is selected as the web application type.
- When [Blazor Server](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#blazor-server) is selected as the web application type.
- When [Angular](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#angular) is selected as the web application type (configured at the backend).
- When [No UI](https://abp.io/docs/latest/solution-templates/single-layer-web-application/web-applications#no-ui) is selected as the web application type (configured at the backend).
### Configuration in `HealthChecksBuilderExtensions.cs`
Health Checks are configured in the `HealthChecksBuilderExtensions` class. This class extends `IServiceCollection` to register health check services and configure health check UI endpoints.
#### Default Configuration
The default setup is as follows:
```csharp
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
namespace MyCompanyName.MyProjectName.HealthChecks;
public static class HealthChecksBuilderExtensions
{
public static void AddMyProjectNameHealthChecks(this IServiceCollection services)
{
// Add your health checks here
var healthChecksBuilder = services.AddHealthChecks();
healthChecksBuilder.AddCheck<MyProjectNameDatabaseCheck>("MyProjectName DbContext Check", tags: new string[] { "database" });
// Read configuration for health check URL
var configuration = services.GetConfiguration();
var healthCheckUrl = configuration["App:HealthCheckUrl"] ?? "/health-status";
services.ConfigureHealthCheckEndpoint(healthCheckUrl);
// Configure HealthChecks UI
var healthChecksUiBuilder = services.AddHealthChecksUI(settings =>
{
settings.AddHealthCheckEndpoint("MyProjectName Health Status", healthCheckUrl);
});
// Set HealthCheck UI storage
healthChecksUiBuilder.AddInMemoryStorage();
services.MapHealthChecksUiEndpoints(options =>
{
options.UIPath = "/health-ui";
options.ApiPath = "/health-api";
});
}
private static IServiceCollection ConfigureHealthCheckEndpoint(this IServiceCollection services, string path)
{
....
}
private static IServiceCollection MapHealthChecksUiEndpoints(this IServiceCollection services, Action<global::HealthChecks.UI.Configuration.Options>? setupOption = null)
{
....
}
}
```
### Database Health Check Implementation
The `MyProjectNameDatabaseCheck` class is a custom implementation of a health check that verifies database connectivity using `IIdentityRoleRepository`.
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
namespace MyCompanyName.MyProjectName.HealthChecks;
public class MyProjectNameDatabaseCheck : IHealthCheck, ITransientDependency
{
protected readonly IIdentityRoleRepository IdentityRoleRepository;
public MyProjectNameDatabaseCheck(IIdentityRoleRepository identityRoleRepository)
{
IdentityRoleRepository = identityRoleRepository;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
await IdentityRoleRepository.GetListAsync(sorting: nameof(IdentityRole.Id), maxResultCount: 1, cancellationToken: cancellationToken);
return HealthCheckResult.Healthy($"Could connect to database and get record.");
}
catch (Exception e)
{
return HealthCheckResult.Unhealthy($"Error when trying to get database record. ", e);
}
}
}
```

BIN
docs/en/studio/images/monitoring-applications/overall.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/studio/images/solution-runner/csharp-application-context-menu-build.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/en/studio/images/solution-runner/csharp-application-context-menu-monitor.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/en/studio/images/solution-runner/csharp-application-context-menu.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/studio/images/solution-runner/solutioın-runner-properties.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 30 KiB

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

Loading…
Cancel
Save