Browse Source

Merge branch 'dev' into docs-pdf

pull/22430/head
liangshiwei 1 year ago
parent
commit
0d73c68e11
  1. 2
      .github/ISSUE_TEMPLATE/config.yml
  2. 20
      .github/workflows/auto-pr.yml
  3. 8
      Directory.Packages.props
  4. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  5. 5
      build/common.ps1
  6. 4
      common.props
  7. 2
      docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md
  8. 2
      docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md
  9. 2
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md
  10. 2
      docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md
  11. 2
      docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md
  12. 2
      docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md
  13. 2
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md
  14. 2
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md
  15. 2
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md
  16. 151
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md
  17. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/cover-image.png
  18. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/require-email-verification-for-register.png
  19. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/select-account.png
  20. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/studio-switch-to-preview.png
  21. 2
      docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md
  22. 2
      docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md
  23. 2
      docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md
  24. 2
      docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md
  25. 2
      docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md
  26. 2
      docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md
  27. 2
      docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md
  28. 2
      docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md
  29. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list-utc1.png
  30. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list-utc3.png
  31. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list.png
  32. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/berlin.png
  33. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-create.png
  34. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-edit.png
  35. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-list.png
  36. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-create.png
  37. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-edit.png
  38. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list-utc1.png
  39. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list-utc3.png
  40. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list.png
  41. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-post.png
  42. 781
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md
  43. 2
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md
  44. 115
      docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md
  45. 38
      docs/en/Community-Articles/2025-03-27-Announcing-the-ABP-Contributor-Program/post.md
  46. 1
      docs/en/cli/index.md
  47. 4
      docs/en/docs-nav.json
  48. 6
      docs/en/framework/architecture/microservices/index.md
  49. 6
      docs/en/framework/infrastructure/event-bus/distributed/index.md
  50. 2
      docs/en/framework/infrastructure/settings.md
  51. 100
      docs/en/framework/infrastructure/timing.md
  52. BIN
      docs/en/images/account-pro-external-login-settings.png
  53. BIN
      docs/en/images/account-pro-time-zone-setting.png
  54. BIN
      docs/en/images/create-aspnet-core-application.png
  55. BIN
      docs/en/images/create-new-aspnet-core-application-v2.png
  56. BIN
      docs/en/images/pen-test-alert-list-9.1.png
  57. BIN
      docs/en/images/select-empty-web-application-v2.png
  58. BIN
      docs/en/images/setting-management-email-ui.png
  59. BIN
      docs/en/images/setting-management-feature-management-ui.png
  60. BIN
      docs/en/images/setting-management-time-zone-ui.png
  61. 6
      docs/en/modules/account-pro.md
  62. 6
      docs/en/modules/setting-management.md
  63. 67
      docs/en/others/penetration-test-report.md
  64. 64
      docs/en/release-info/migration-guides/abp-9-2.md
  65. BIN
      docs/en/release-info/migration-guides/images/require-email-verification-for-register.png
  66. 1
      docs/en/release-info/migration-guides/index.md
  67. 13
      docs/en/release-info/release-notes.md
  68. 20
      docs/en/release-info/road-map.md
  69. 30
      docs/en/studio/installation.md
  70. 15
      docs/en/tutorials/microservice/part-06.md
  71. 7
      framework/Volo.Abp.sln
  72. 3
      framework/src/Volo.Abp.AspNetCore.Bundling/FodyWeavers.xml
  73. 30
      framework/src/Volo.Abp.AspNetCore.Bundling/FodyWeavers.xsd
  74. 3
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/FodyWeavers.xml
  75. 30
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/FodyWeavers.xsd
  76. 32
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj
  77. 4
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/AbpAspNetCoreComponentsMauiBlazorBundlingModule.cs
  78. 2
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/AbpBlazorWebView.cs
  79. 9
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/BundleManager.cs
  80. 0
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/IMauiBlazorContentFileProvide.cs
  81. 0
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/MauiBlazorBundlerBase.cs
  82. 3
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/MauiBlazorContentFileProvider.cs
  83. 1
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/Scripts/ScriptBundler.cs
  84. 2
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/Styles/StyleBundler.cs
  85. 20
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/AbpMauiBlazorClientHttpMessageHandler.cs
  86. 30
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs
  87. 10
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs
  88. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor
  89. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor
  90. 19
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpBlazorClientHttpMessageHandler.cs
  91. 15
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/UserExceptionInformer.cs
  92. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/abp.js
  93. 23
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
  94. 10
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs
  95. 3
      framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpComponentBase.cs
  96. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs
  97. 111
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  98. 36
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs
  99. 58
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs
  100. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js

2
.github/ISSUE_TEMPLATE/config.yml

@ -1,4 +1,4 @@
blank_issues_enabled: true
blank_issues_enabled: false
contact_links:
- name: Issue with ABP Commercial
url: https://abp.io/support/questions

20
.github/workflows/auto-pr.yml

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

8
Directory.Packages.props

@ -47,8 +47,8 @@
<PackageVersion Include="MongoSandbox6.runtime.win-x64" Version="1.0.1" />
<PackageVersion Include="FluentValidation" Version="11.10.0" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.17" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.18" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.18" />
<PackageVersion Include="HtmlSanitizer" Version="8.1.870" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="IdentityServer4" Version="4.1.2" />
@ -145,7 +145,7 @@
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.13.0" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.13.0" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.13.0" />
<PackageVersion Include="RabbitMQ.Client" Version="6.8.1" />
<PackageVersion Include="RabbitMQ.Client" Version="7.1.2" />
<PackageVersion Include="Rebus" Version="8.6.0" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.2.0" />
<PackageVersion Include="Scriban" Version="5.10.0" />
@ -180,7 +180,7 @@
<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="TimeZoneConverter" Version="7.0.0" />
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.2" />

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

@ -1886,6 +1886,9 @@
"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>."
"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>.",
"BiographyContainsUrlValidationMessage": "Biography cannot contain URL.",
"CreatePostSEOTitleInfo": "The SEO Title is what appears in search engine results as the clickable headline. By default, it matches the article’s title, but you can customize it here for better search visibility. Keep it clear, compelling, and include target keywords—ideally near the beginning. Aim for around 60 characters to avoid truncation in search results.",
"SEOTitle": "SEO Title"
}
}

5
build/common.ps1

@ -23,11 +23,6 @@ $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

4
common.props

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

2
docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md

@ -187,7 +187,7 @@ We organized the 3rd live [ABP Community Talks](https://community.abp.io/events)
* [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) has also created [an article](https://community.abp.io/posts/using-autofilterer-with-abp-framework-uuqv81jm) to demonstrate how to use his own open source [AutoFilterer](https://github.com/enisn/AutoFilterer) library with the ABP Framework.
* [Jonathan Potts](https://github.com/jonathanpotts) has created his first ABP Community article that shows how to use Bootswatch themes with the ABP Framework. [See it here](https://community.abp.io/posts/customizing-the-abp-basic-theme-with-bootswatch-4luoqzr0).
Thanks to all the contributors. It is appreciated if you want to submit your post and share your knowledge with the ABP community: https://community.abp.io/posts/submit
Thanks to all the contributors. It is appreciated if you want to submit your post and share your knowledge with the ABP community: https://community.abp.io/posts/create
## Conclusion

2
docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md

@ -230,7 +230,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [A Best Practice for Designing Interfaces in .NET C#](https://community.abp.io/posts/a-best-practice-for-designing-interfaces-in-.net-c-9xqc4h8d)
* [Invariance, Covariance, and Contravariance in .NET C#](https://community.abp.io/posts/invariance-covariance-and-contravariance-in-.net-c-9blmuhme)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/create) to the ABP Community.
## Conclusion

2
docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md

@ -260,7 +260,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [Using FluentValidation with ABP Framework](https://community.abp.io/posts/using-fluentvalidation-with-abp-framework-2cxuwl70) by [Enes Döner](https://community.abp.io/members/Enes)
* [Using Blob Storage with ABP](https://community.abp.io/posts/using-blob-storage-with-abp-framework-jygtmhn4) by [Emre Kendirli](https://community.abp.io/members/emrekenderli)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/create) to the ABP Community.
## Conclusion

2
docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md

@ -168,7 +168,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [How to use .NET Aspire with ABP framework](https://abp.io/community/articles/how-to-use-.net-aspire-with-abp-framework-h29km4kk) by [Berkan Şaşmaz](https://twitter.com/berkansasmazz)
* [Exciting New Feature in ABP.IO CMS Kit: Marked Item System](https://abp.io/community/articles/exciting-new-feature-in-abp.io-cms-kit-marked-item-system.-2hvpq0me) by [Suhaib Mousa](https://abp.io/community/members/suhaibmousa032@gmail.com)
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.
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/create) to the ABP Community.
## Conclusion

2
docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md

@ -70,7 +70,7 @@ As always, exciting articles have been contributed by the ABP community. I will
* [Introducing the Google Cloud Storage BLOB Provider](https://abp.io/community/articles/introducing-the-google-cloud-storage-blob-provider-yrt6azc0) by [Engincan Veske](https://twitter.com/EngincanVeske)
* [Switching Between Organization Units](https://abp.io/community/articles/switching-between-organization-units-i5tokpzt) by [Liming Ma](https://github.com/maliming)
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.
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/create) to the ABP Community.
## About the Next Version

2
docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md

@ -214,7 +214,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [ABP-Powered Web App with Inertia.js, React, and Vite](https://abp.io/community/articles/abppowered-web-app-with-inertia.js-react-and-vite-j7cccvad) by [Anto Subash](https://antosubash.com/)
* [Multi-Tenancy Support in Angular Apps with ABP.IO](https://abp.io/community/articles/multitenancy-support-in-angular-apps-with-abp.io-lw9l36c5) by [HeadChannel Team](https://headchannel.co.uk/)
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.
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/create) to the ABP Community.
## Conclusion

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

@ -78,7 +78,7 @@ In addition to [the articles to highlight .NET 9.0 features written by our team]
* [How to create your Own AI Bot on WhatsApp Using an ABP.io Template](https://abp.io/community/articles/how-to-create-your-own-ai-bot-on-whatsapp-using-the-abp-framework-c6jgvt9c) by [Michael Kokula](https://abp.io/community/members/Michal_Kokula)
* [ABP Now Supports .NET 9](https://abp.io/community/articles/abp-now-supports-.net-9-zpkznc4f) by [Alper Ebiçoğlu](https://x.com/alperebicoglu)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
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/create) to the ABP Community.
### ABP Community Talks 2024.7: What’s New with .NET 9 & ABP 9?

2
docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md

@ -138,7 +138,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [The new Unit Test structure in ABP application](https://abp.io/community/articles/the-new-unit-test-structure-in-abp-application-4vvvp2oy) by [Liming Ma](https://github.com/maliming)
* [How to Use OpenAI API with ABP Framework](https://abp.io/community/articles/how-to-use-openai-api-with-abp-framework-rsfvihla) by [Berkan Şaşmaz](https://github.com/berkansasmaz)
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.
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/create) to the ABP Community.
## Conclusion

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

@ -67,7 +67,7 @@ As always, exciting articles have been contributed by the ABP community. I will
* [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.
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/create) to the ABP Community.
### ABP Community Talks 2025.2: Real World Problems and Solutions with AI

151
docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md

@ -0,0 +1,151 @@
# ABP Platform 9.2 RC Has Been Released
We are happy to release [ABP](https://abp.io) version **9.2 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
Try this version and provide feedback for a more stable version of ABP v9.2! Thanks to you in advance.
## Get Started with the 9.2 RC
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).
By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI:
![studio-switch-to-preview.png](studio-switch-to-preview.png)
## Migration Guide
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.x or earlier: [ABP Version 9.2 Migration Guide](https://abp.io/docs/9.2/release-info/migration-guides/abp-9-2)
## What's New with ABP v9.2?
In this section, I will introduce some major features released in this version.
Here is a brief list of titles explained in the next sections:
* Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
* Docs Module: Added "Alternative Words" to Filter Items
* Introducing the Bunny BLOB Storage Provider
* Upgraded `MongoDB.Driver` to v3.1.0
* Identity Pro Module: Require Email Verification to Register
* Switching users during OAuth login
### Added ApplicationName Property to Isolate Background Jobs & Background Workers
ABP's [Background Jobs Module](https://abp.io/docs/latest/modules/background-jobs) has been enhanced with a new `ApplicationName` property that helps isolate jobs and workers across multiple applications sharing the same database.
Previously, when different applications used the BackgroundJobs module and shared a database, an application might encounter jobs that didn't belong to it. This would lead to failed processing attempts and marking jobs as `IsAbandoned = true` with a "Undefined background job for the job name" error, preventing these jobs from ever being executed.
With the new `ApplicationName` property, applications now properly filter jobs at the repository level, ensuring each application only processes job types it recognizes. This prevents the incorrect abandonment of jobs and ensures consistent behavior in multi-application scenarios.
You can set `ApplicationName` of `AbpBackgroundJobWorkerOptions` to your application name to isolate jobs and workers across multiple applications sharing the same database:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpBackgroundJobWorkerOptions>(options =>
{
options.ApplicationName = context.Services.GetApplicationName()!;
});
}
```
> For more information, please refer to the [Background Jobs Module](https://abp.io/docs/latest/modules/background-jobs) documentation and the [PR](https://github.com/abpframework/abp/pull/22169) that added this feature.
### Docs Module: Added "Alternative Words" to Filter Items
[ABP's Docs Module](https://abp.io/docs/9.2/modules/docs) now supports "alternative words" to enhance the search functionality when filtering documentation items. This feature addresses a common user experience issue where users might search using terminology different from what appears in the documentation.
For example, when a user searches for "Error" in the documentation, they may actually be looking for content related to "Exception Handling." With this new feature, documentation items can now be configured with alternative keywords that are considered during filtering.
The implementation allows defining optional "keywords" for items in the navigation tree. For example:
```json
{
"text": "Exception Handling",
"path": "framework/fundamentals/exception-handling.md",
"keywords": ["Error", "Another Value"]
}
```
When users search or filter content, the system now considers both the original text and these alternative keywords, improving discoverability of relevant documentation sections. This enhancement makes the documentation more accessible and user-friendly, especially for newcomers who might not be familiar with the exact terminology used in the ABP documentation.
### Introducing the Bunny BLOB Storage Provider
ABP v9.2 RC introduces a new BLOB storage provider for [Bunny Storage](https://bunny.net/storage/), a global edge storage solution. This addition expands ABP's BLOB Storage options beyond the existing providers like Azure, AWS, and others.
The [Bunny BLOB Storage Provider](https://abp.io/docs/9.2/framework/infrastructure/blob-storing/bunny) allows ABP applications to seamlessly integrate with Bunny's CDN-backed storage service, which offers high-performance content delivery through its global network.
To use this new provider, you'll need to:
* Run `abp add-package Volo.Abp.BlobStoring.Bunny` command.
* And then configure the provider in your module's `ConfigureServices` method:
```csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseBunny(bunny =>
{
bunny.StorageZoneName = "your-storage-zone";
bunny.ApiKey = "your-api-key";
bunny.Region = "your-region"; // de, ny, la, sg, or sy
});
});
});
```
This integration provides ABP applications with an efficient and globally distributed storage solution, particularly beneficial for applications requiring fast content delivery across different geographical regions. To use this new provider and make the related configurations, you can refer to the [Bunny Storage Provider](https://abp.io/docs/9.2/framework/infrastructure/blob-storing/bunny) documentation always.
> This new BLOB Storage provider is contributed by [@suhaib-mousa](https://github.com/suhaib-mousa). Thanks to him for his contribution!
> We are always happy to see the community contributing to the ABP Framework and encouraging them to contribute more.
### Upgraded `MongoDB.Driver` to `v3.1.0`
ABP v9.2 RC includes an upgrade to `MongoDB.Driver` version `3.1.0`. This significant version bump from previous releases brings several improvements and new features that benefit ABP applications using MongoDB as their database.
The upgrade provides:
* Async/Await Support: Write non-blocking, asynchronous code easily.
* Fluent API: Build queries and updates intuitively with Builders.
* LINQ Support: Use LINQ for querying MongoDB collections.
* and more ...
> For more information, please refer to the [MongoDB.Driver release notes](https://github.com/mongodb/mongo-csharp-driver/releases/tag/v3.1.0).
We have prepared a [migration guide](https://abp.io/docs/9.2/release-info/migration-guides/MongoDB-Driver-2-to-3) for this upgrade. Please refer to it to learn more about the changes and how to migrate your application.
### Identity Pro Module: Require Email Verification to Register
[ABP Identity Pro module](https://abp.io/docs/9.2/modules/identity-pro) has been enhanced with a new feature that allows administrators to require email verification during the registration process. This security improvement ensures that users must verify their email addresses before their registration is considered complete. Enabling this feature is especially important for applications that want to prevent spam registrations.
Administrators can enable or disable this feature through the **Identity management -> Identity Verification (tab)** settings page (by checking the `Enforce email verification to register` checkbox):
![require-email-verification.png](./require-email-verification-for-register.png)
### Switching users during OAuth login
If you have an OAuth/Auth Server application using the [ABP Account Pro module](https://abp.io/docs/9.2/modules/account-pro) , you can pass the `prompt=select_account` parameter to force the user to select an account.
![select-account.png](./select-account.png)
For more information, please refer to the [Switching users during OAuth login](https://abp.io/docs/9.2/modules/account-pro#switching-users-during-oauth-login) documentation.
### New ABP Community Articles
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
* [Implementing CQRS with MediatR in ABP](https://abp.io/community/articles/implementing-cqrs-with-mediatr-in-abp-xiqz2iio) by [Engincan Veske](https://github.com/EngincanV)
* [Using Vue Components in a Razor Pages ABP Application](https://abp.io/community/articles/using-vue-components-in-a-razor-pages-abp-application-z3jr07tv) by [Enis Necipoglu](https://github.com/enisn)
* [Using ABP's AWS Blob Storing Provider with DigitalOcean Spaces](https://abp.io/community/articles/using-abps-aws-blob-storing-provider-with-digitalocean-spaces-7hlyb25g) by [Suhaib Mousa](https://abp.io/community/members/suhaib-mousa)
* [Video Post: Using Vue Components in a Razor Pages ABP Application](https://abp.io/community/articles/using-vue-components-in-a-razor-pages-abp-application-z3jr07tv) by [Enis Necipoglu](https://github.com/enisn)
* [Understanding the Embedded Files in ABP Framework](https://abp.io/community/articles/understanding-the-embedded-files-in-abp-framework-nsrp8aa9) by [Liming Ma](https://github.com/maliming)
* [How to Change the CurrentUser in ABP?](https://abp.io/community/articles/how-to-change-the-currentuser-in-abp-i3uu1m7g) 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/create) to the ABP Community.
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/9.2/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.2 RC and provide feedback to help us release a more stable version.
Thanks for being a part of this community!

BIN
docs/en/Blog-Posts/2025-03-27 v9_2_Preview/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

BIN
docs/en/Blog-Posts/2025-03-27 v9_2_Preview/require-email-verification-for-register.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
docs/en/Blog-Posts/2025-03-27 v9_2_Preview/select-account.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 KiB

BIN
docs/en/Blog-Posts/2025-03-27 v9_2_Preview/studio-switch-to-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

2
docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md

@ -186,7 +186,7 @@ We organized the 3rd live [ABP Community Talks](https://community.abp.io/events)
* [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) has also created [an article](https://community.abp.io/posts/using-autofilterer-with-abp-framework-uuqv81jm) to demonstrate how to use his own open source [AutoFilterer](https://github.com/enisn/AutoFilterer) library with the ABP Framework.
* [Jonathan Potts](https://github.com/jonathanpotts) has created his first ABP Community article that shows how to use Bootswatch themes with the ABP Framework. [See it here](https://community.abp.io/posts/customizing-the-abp-basic-theme-with-bootswatch-4luoqzr0).
Thanks to all the contributors. It is appreciated if you want to submit your post and share your knowledge with the ABP community: https://community.abp.io/posts/submit
Thanks to all the contributors. It is appreciated if you want to submit your post and share your knowledge with the ABP community: https://community.abp.io/posts/create
## Conclusion

2
docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md

@ -228,7 +228,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [A Best Practice for Designing Interfaces in .NET C#](https://community.abp.io/posts/a-best-practice-for-designing-interfaces-in-.net-c-9xqc4h8d)
* [Invariance, Covariance, and Contravariance in .NET C#](https://community.abp.io/posts/invariance-covariance-and-contravariance-in-.net-c-9blmuhme)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/create) to the ABP Community.
## Conclusion

2
docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md

@ -259,7 +259,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [Using FluentValidation with ABP Framework](https://community.abp.io/posts/using-fluentvalidation-with-abp-framework-2cxuwl70) by [Enes Döner](https://community.abp.io/members/Enes)
* [Using Blob Storage with ABP](https://community.abp.io/posts/using-blob-storage-with-abp-framework-jygtmhn4) by [Emre Kendirli](https://community.abp.io/members/emrekenderli)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/create) to the ABP Community.
## Conclusion

2
docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md

@ -165,7 +165,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [How to use .NET Aspire with ABP framework](https://abp.io/community/articles/how-to-use-.net-aspire-with-abp-framework-h29km4kk) by [Berkan Şaşmaz](https://twitter.com/berkansasmazz)
* [Exciting New Feature in ABP.IO CMS Kit: Marked Item System](https://abp.io/community/articles/exciting-new-feature-in-abp.io-cms-kit-marked-item-system.-2hvpq0me) by [Suhaib Mousa](https://abp.io/community/members/suhaibmousa032@gmail.com)
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.
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/create) to the ABP Community.
## Conclusion

2
docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md

@ -70,7 +70,7 @@ As always, exciting articles have been contributed by the ABP community. I will
* [Introducing the Google Cloud Storage BLOB Provider](https://abp.io/community/articles/introducing-the-google-cloud-storage-blob-provider-yrt6azc0) by [Engincan Veske](https://twitter.com/EngincanVeske)
* [Switching Between Organization Units](https://abp.io/community/articles/switching-between-organization-units-i5tokpzt) by [Liming Ma](https://github.com/maliming)
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.
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/create) to the ABP Community.
## About the Next Version

2
docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md

@ -210,7 +210,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [ABP-Powered Web App with Inertia.js, React, and Vite](https://abp.io/community/articles/abppowered-web-app-with-inertia.js-react-and-vite-j7cccvad) by [Anto Subash](https://antosubash.com/)
* [Multi-Tenancy Support in Angular Apps with ABP.IO](https://abp.io/community/articles/multitenancy-support-in-angular-apps-with-abp.io-lw9l36c5) by [HeadChannel Team](https://headchannel.co.uk/)
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.
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/create) to the ABP Community.
## Conclusion

2
docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md

@ -80,7 +80,7 @@ In addition to [the articles to highlight .NET 9.0 features written by our team]
* [How to create your Own AI Bot on WhatsApp Using an ABP.io Template](https://abp.io/community/articles/how-to-create-your-own-ai-bot-on-whatsapp-using-the-abp-framework-c6jgvt9c) by [Michael Kokula](https://abp.io/community/members/Michal_Kokula)
* [ABP Now Supports .NET 9](https://abp.io/community/articles/abp-now-supports-.net-9-zpkznc4f) by [Alper Ebiçoğlu](https://x.com/alperebicoglu)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
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/create) to the ABP Community.
## Conclusion

2
docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md

@ -136,7 +136,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [The new Unit Test structure in ABP application](https://abp.io/community/articles/the-new-unit-test-structure-in-abp-application-4vvvp2oy) by [Liming Ma](https://github.com/maliming)
* [How to Use OpenAI API with ABP Framework](https://abp.io/community/articles/how-to-use-openai-api-with-abp-framework-rsfvihla) by [Berkan Şaşmaz](https://github.com/berkansasmaz)
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.
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/create) to the ABP Community.
## Conclusion

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list-utc1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list-utc3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/berlin.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-create.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-create.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list-utc1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list-utc3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-post.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

781
docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md

@ -0,0 +1,781 @@
# Developing a Multi-Timezone Application Using the ABP Framework
When developing multi-timezone applications, we need to handle users from different time zones and make sure they see the correct time. The system also needs to support users changing their timezone (like when traveling or moving) and make sure all time displays update correctly to show accurate time information.
All these scenarios require us to handle timezone conversions correctly in our application. The ABP framework provides a complete solution for these challenges.
In this article, we'll show you step by step how to handle multi-timezone in the ABP framework.
> The content mentioned in this article will be available after the ABP 9.2 version
## Timezone Settings
The ABP framework provides a setting called `Abp.Timing.TimeZone` for setting and getting the timezone of users, tenants, or applications. The default value is empty, which means the application will use the server's time zone. Check out the [Timing documentation](https://abp.io/docs/latest/framework/infrastructure/timing) for more information.
## ISO 8601 Date Time Format
Different countries and regions may use different time formats:
* Year-Month-Day (YYYY-MM-DD): Mainly used in China, Japan, Korea, Canada (official standard), Germany (ISO standard), ISO 8601 international standard, etc. Example: 2025-03-11
* Day-Month-Year (DD-MM-YYYY): Mainly used in UK, India, Australia, New Zealand, most European countries (like France, Germany, Italy, Spain), some South American countries, etc. Example: 11-03-2025 or 11/03/2025
* Month-Day-Year (MM-DD-YYYY): Mainly used in USA, Philippines, some parts of Canada, etc. Example: 03-11-2025 or 03/11/2025
* Day.Month.Year (DD.MM.YYYY): Mainly used in Germany, Russia, Switzerland, Hungary, Czech Republic, etc. Example: 11.03.2025
Also, different countries/regions might use different separators (like slash /, hyphen -, dot .), and some countries use different month abbreviations or full names (like March 11, 2025).
ISO 8601 uses a standard format to avoid confusion between different date formats and ensure global compatibility.
It has 4 parts:
* Date part: `YYYY-MM-DD`
* `T` as a separator
* Time part: `HH:MM:SS`
* Timezone part: `Z` or `+/-HH:MM`
You'll usually see formats like: `YYYY-MM-DDTHH:MM:SSZ` or `YYYY-MM-DDTHH:MM:SS+/-HH:MM`, for example: `2025-03-11T10:30:00Z` or `2025-03-11T22:30:00+03:00`
When our application needs to handle multiple timezones, we usually use ISO 8601 to represent time.
## Enabling Multi-Timezone Support
When we set the `Kind` of `AbpClockOptions` to `DateTimeKind.Utc`, the ABP framework will normalize all times. Times written to the database and returned to the frontend will be in `UTC`. the `SupportsMultipleTimezone` property will be `true` in the `IClock` service.
```csharp
Configure<AbpClockOptions>(options =>
{
options.Kind = DateTimeKind.Utc;
});
```
### Using DateTime to Store Time
Assuming the `DateTime` stored in the database is `2025-03-01 10:30:00`, then the time returned to the front end will be `2025-03-01T10:30:00Z`. This is a time in ISO 8601 format. Because `DateTime` does not have timezone information, the framework will assume it is `UTC` time.
### Using DateTimeOffset to Store Time
If you use `DateTimeOffset` to store time, the ABP framework will not normalize `DateTimeOffset`, but will return it directly to the front end.
Assuming the `DateTimeOffset` stored in the database is `2025-03-01 13:30:00 +03:00`, then the time returned to the front end will be `2025-03-01T13:30:00+03:00`. This is also a time in ISO 8601 format.
We recommend using `DateTimeOffset` to store time because it has timezone information.
## Timezone Conversion
### Converting UTC Time to User Time
The `IClock` service has 2 methods to convert a given `UTC` time to the user time:
```csharp
DateTime ConvertToUserTime(utcDateTime dateTime)
DateTimeOffset ConvertToUserTime(DateTimeOffset dateTimeOffset)
```
> If `SupportsMultipleTimezone` is `false` or `dateTime.Kind` is not `Utc` or no timezone is set, it will return the given `DateTime` or `DateTimeOffset` without any changes.
**Example:**
If the user's timezone is `Europe/Istanbul`
```csharp
// 2025-03-01T05:30:00Z
var utcTime = new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Utc);
var userTime = Clock.ConvertToUserTime(utcTime);
// Europe/Istanbul has 3 hours difference with UTC. So, the result will be 3 hours later.
userTime.Kind.ShouldBe(DateTimeKind.Unspecified);
userTime.ToString("O").ShouldBe("2025-03-01T08:30:00");
```
```csharp
// 2025-03-01T05:30:00Z
var utcTime = new DateTimeOffset(new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Utc), TimeSpan.Zero);
var userTime = Clock.ConvertToUserTime(utcTime);
// Europe/Istanbul has 3 hours difference with UTC. So, the result will be 3 hours later.
userTime.Offset.ShouldBe(TimeSpan.FromHours(3));
userTime.ToString("O").ShouldBe("2025-03-01T08:30:00.0000000+03:00");
```
### Converting User Time to UTC
The `IClock` service has 1 method to convert a given user time to UTC.
```csharp
DateTime ConvertToUtc(DateTime dateTime)
```
> If `SupportsMultipleTimezone` is `false` or `dateTime.Kind` is `Utc` or no timezone is set, it will return the given `DateTime` without any changes.
**Example:**
If the user's timezone is `Europe/Istanbul`
```csharp
// 2025-03-01T05:30:00
var userTime = new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Unspecified); //Same as Local
var utcTime = Clock.ConvertToUtc(userTime);
// Europe/Istanbul has 3 hours difference with UTC. So, the result will be 3 hours earlier.
utcTime.Kind.ShouldBe(DateTimeKind.Utc);
utcTime.ToString("O").ShouldBe("2025-03-01T02:30:00.0000000Z");
```
## Handling Timezone in Different UIs
We'll use the `TimeZoneApp` project to demonstrate handling timezone in different UIs. It has a `Meeting` entity, with several time properties.
```csharp
public class Meeting : AggregateRoot<Guid>
{
public string Subject { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public DateTime ActualStartTime { get; set; }
public DateTime? CanceledTime { get; set; }
public DateTimeOffset ReminderTime { get; set; }
public DateTimeOffset? FollowUpTime { get; set; }
public string Description { get; set; }
}
```
`TimeZoneApp` project is an ABP layered architecture project, it sets a global `Europe/Istanbul` timezone, it contains 4 websites
* `API.Host`: API website, it does not have UI, it returns data in JSON format
* `AuthServer`: Authentication server, it uses Razor Pages as UI
* `Web`: Razor Pages website, it uses JavaScript to manage Meeting creation and editing and display
* `Blazor`: Blazor Server website, it uses Blazor to manage Meeting creation and editing and display
All 4 applications are enabled for multi-timezone support, and use the `UseAbpTimeZone` middleware.
> Blazor WASM and Angular do not need to use the `UseAbpTimeZone` middleware
### DateTime in API Response
In the API response, we usually use the ISO 8601 format time, as you can see, after enabling multi-timezone support, the API returns time to the front end as UTC time.
`2025-03-01T09:30:00Z` and `2025-03-01T12:30:00+00:00` are ISO 8601 format time.
```json
[
{
"subject": "ABP Developer Guide",
"startTime": "2025-03-01T09:30:00Z",
"endTime": "2025-03-01T10:30:00Z",
"actualStartTime": "2025-03-01T11:30:00Z",
"canceledTime": null,
"reminderTime": "2025-03-01T12:30:00+00:00",
"followUpTime": "2025-03-01T13:30:00+00:00",
"description": "We will discuss the ABP developer guide.",
"id": "2af0abd3-be06-ecff-5d4c-3a1895ac7950"
},
{
"subject": "ABP Training",
"startTime": "2025-03-01T09:30:00Z",
"endTime": "2025-03-01T10:30:00Z",
"actualStartTime": "2025-03-01T11:30:00Z",
"canceledTime": "2025-03-01T12:00:00Z",
"reminderTime": "2025-03-01T12:30:00+00:00",
"followUpTime": "2025-03-01T13:30:00+00:00",
"description": "ABP training for the new developers.",
"id": "290b0cb6-3e50-6324-1e79-3a1895ac795f"
}
]
```
### Handling Timezone in MVC/Razor Pages
In the `AuthServer` project, we handle time conversion in a simple way:
1. First, we get the `Meeting` entities from the database using `IRepository<Meeting, Guid>`. At this point, all `DateTime` values are in UTC.
2. Then, when displaying the times in the view, we use `Clock.ConvertToUserTime` to show them in the user's timezone.
> Note: The `ConvertToUserTime` method will only convert times if multi-timezone support is enabled in the application.
```csharp
public class IndexModel : AbpPageModel
{
public List<Meeting>? Meetings { get; set; }
protected IRepository<Meeting, Guid> MeetingRepository { get; }
public IndexModel(IRepository<Meeting, Guid> meetingRepository)
{
MeetingRepository = meetingRepository;
}
public async Task OnGetAsync()
{
Meetings = await MeetingRepository.GetListAsync();
}
}
```
```html
<div class="container">
<abp-row>
<div class="table-responsive">
<table class="table table-striped table-hover mt-3">
<thead>
<tr>
<th>@L["Subject"]</th>
<th>@L["StartTime"] / @L["EndTime"]</th>
<th>@L["ActualStartTime"]</th>
<th>@L["CanceledTime"]</th>
<th>@L["ReminderTime"]</th>
<th>@L["FollowUpTime"]</th>
<th>@L["Description"]</th>
</tr>
</thead>
<tbody>
@foreach (var meeting in Model.Meetings)
{
<tr>
<td>@meeting.Subject</td>
<td>@Clock.ConvertToUserTime(meeting.StartTime) ➡️ @Clock.ConvertToUserTime(meeting.EndTime)</td>
<td>@Clock.ConvertToUserTime(meeting.ActualStartTime)</td>
<td>@(meeting.CanceledTime.HasValue ? Clock.ConvertToUserTime(meeting.CanceledTime.Value) : "N/A")</td>
<td>@Clock.ConvertToUserTime(meeting.ReminderTime).DateTime</td>
<td>@(meeting.FollowUpTime.HasValue ? Clock.ConvertToUserTime(meeting.FollowUpTime.Value).DateTime : "N/A")</td>
<td>@meeting.Description</td>
</tr>
}
</tbody>
</table>
</div>
</abp-row>
</div>
```
![](auth-list.png)
### Handling Timezone in JavaScript
In the `Web` project, we use JavaScript to handle timezone.
#### Displaying Time in UI
* `timeZoneApp.meetings.meeting.getList` gets all `Meeting` entities and displays them in `DataTables`
* `abp.clock.normalizeToLocaleString()` is the ABP JavaScript API, it converts `UTC` time to the current user's timezone, and then calls its `toLocaleString` method to format time
* `dataFormat: "datetime"` is the ABP DataTable extension method, it calls the `abp.clock.normalizeToLocaleString` method to convert and format time
> If the current application is not enabled for multi-timezone support, then the `abp.clock.normalizeToLocaleString` method will not convert the time, it will just call the `Date` object's `toLocaleString` method.
```js
var dataTable = $('#MeetingsTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(timeZoneApp.meetings.meeting.getList),
columnDefs: [
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
visible: abp.auth.isGranted('TimeZoneApp.Meetings.Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
},
},
{
text: l('Delete'),
visible: abp.auth.isGranted('TimeZoneApp.Meetings.Delete'),
confirmMessage: function (data) {
return l('MeetingDeletionConfirmationMessage', data.record.subject);
},
action: function (data) {
timeZoneApp.meetings.meeting
.delete(data.record.id)
.then(function() {
abp.notify.info(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
]
}
},
{
title: l('Subject'),
data: "subject"
},
{
title: l('StartTime') + ' / ' + l('StartTime'),
data: "startTime",
render: function (data, type, row) {
return abp.clock.normalizeToLocaleString(row.startTime) + ' ➡️ ' + abp.clock.normalizeToLocaleString(row.endTime);
}
},
{
title: l('ActualStartTime'),
data: "actualStartTime",
dataFormat: "datetime"
},
{
title: l('CanceledTime'),
data: "canceledTime",
render: function (data, type, row) {
return data ? abp.clock.normalizeToLocaleString(data) : 'N/A';
}
},
{
title: l('ReminderTime'),
data: "reminderTime",
dataFormat: "datetime"
},
{
title: l('FollowUpTime'),
data: "followUpTime",
render: function (data, type, row) {
return data ? abp.clock.normalizeToLocaleString(data) : 'N/A';
}
},
{
title: l('Description'),
data: "description"
}
]
})
);
```
Below is the screenshot of `DataTables`:
![](mvc-list.png)
#### Creating and Editing Meeting
We use `JavaScript` to create and edit `Meeting`.
ABP's [TagHelper](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/tag-helpers) can automatically create forms based on the model, it will generate corresponding HTML tags based on the attributes in the model. For `DateTime` and `DateTimeOffset` attributes, it will generate and initialize a [DateTimePicker](https://www.daterangepicker.com/) component.
**CreateModal** and **EditModal** :
```html
<abp-dynamic-form abp-model="Meeting" asp-page="/Meetings/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewMeeting"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
```
```html
<abp-dynamic-form abp-model="Meeting" asp-page="/Meetings/EditModal">
<abp-modal>
<abp-modal-header title="@L["Update"].Value"></abp-modal-header>
<abp-modal-body>
<abp-input asp-for="Id" />
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
```
You can see that the time in the control has been converted to the current user's timezone.
![](mvc-create.png)
![](mvc-edit.png)
When we submit the form, we need to convert the time to `UTC`. In the `JavaScript` of the `Create` and `Edit` pages, we use the `handleDatepicker` this `jQuery` extension method to handle time in the form, it internally gets the user's local time from the selector `input[type="hidden"][data-hidden-datepicker]`, and then uses the `abp.clock.normalizeToString` method to convert the date field in the form to the `ISO 8601` format `UTC` time string.
> If the current application is not enabled for multi-timezone support, then the `abp.clock.normalizeToString` method will not convert the time, it will just convert to the ISO 8601 format time string without timezone.
```js
var abp = abp || {};
$(function () {
abp.modals.meetingCreate = function () {
var initModal = function (publicApi, args) {
var $form = publicApi.getForm();
$form.find('button[type="submit"]').on('click', function (e) {
$form.handleDatepicker('input[type="hidden"][data-hidden-datepicker]');
});
};
return {
initModal: initModal
}
};
});
```
The requested data is as follows:
```csharp
Request URL: Meetings/EditModal
Request Method: POST
Payload:
Id: 0803780b-3762-2af8-1c75-3a1895d59c89
Meeting.Subject: ABP Developer Guide
Meeting.StartTime: 2025-03-01T09:30:00.000Z
Meeting.EndTime: 2025-03-01T10:30:00.000Z
Meeting.ActualStartTime: 2025-03-01T11:30:00.000Z
Meeting.CanceledTime:
Meeting.ReminderTime: 2025-03-01T12:30:00.000Z
Meeting.FollowUpTime: 2025-03-01T13:30:00.000Z
Meeting.Description: We will discuss the ABP developer guide.
```
![](mvc-post.png)
In short, we use the `abp.clock.normalizeToLocaleString` method to display time, and use the `abp.clock.normalizeToString` method to modify the time to be submitted. If you submit data via `ajax`, please remember to use the `abp.clock.normalizeToString` method to convert time.
### Handling Timezone in Blazor
We cannot automatically complete some work in `Blazor UI`, we need to inject `IClock` and use the `ConvertToUserTime` and `ConvertToUtc` methods to display and create/update entities.
Below is a complete `Meeting` page, please refer to the usage of `Clock` in it.
```csharp
@page "/meetings"
@using Volo.Abp.Application.Dtos
@using Microsoft.Extensions.Localization
@using TimeZoneApp.Meetings
@using TimeZoneApp.Localization
@using TimeZoneApp.Permissions
@using Volo.Abp.AspNetCore.Components.Web
@inject IStringLocalizer<TimeZoneAppResource> L
@inject AbpBlazorMessageLocalizerHelper<TimeZoneAppResource> LH
@inherits AbpCrudPageBase<IMeetingAppService, MeetingDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateMeetingDto>
<Card>
<CardHeader>
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Meetings"]</h2>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
@if (HasCreatePermission)
{
<Button Color="Color.Primary" Clicked="OpenCreateModalAsync">@L["NewMeeting"]</Button>
}
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="MeetingDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridEntityActionsColumn TItem="MeetingDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="MeetingDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="MeetingDto"
Text="@L["Edit"]"
Visible=HasUpdatePermission
Clicked="() => OpenEditModalAsync(context)" />
<EntityAction TItem="MeetingDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
Visible=HasDeletePermission
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.Subject)"
Caption="@L["Subject"]"></DataGridColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.StartTime)"
Caption="@(L["StartTime"] + "/" + L["EndTime"])">
<DisplayTemplate>
@Clock.ConvertToUserTime(context.StartTime).ToString("yyyy-MM-dd HH:mm:ss") ➡️ @Clock.ConvertToUserTime(context.EndTime).ToString("yyyy-MM-dd HH:mm:ss")
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.ActualStartTime)"
Caption="@L["ActualStartTime"]">
<DisplayTemplate>
@Clock.ConvertToUserTime(context.ActualStartTime).ToString("yyyy-MM-dd HH:mm:ss")
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.CanceledTime)"
Caption="@L["CanceledTime"]">
<DisplayTemplate>
@(context.CanceledTime.HasValue ? Clock.ConvertToUserTime(context.CanceledTime.Value).ToString("yyyy-MM-dd HH:mm:ss") : "N/A")
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.ReminderTime)"
Caption="@L["ReminderTime"]">
<DisplayTemplate>
@(Clock.ConvertToUserTime(context.ReminderTime).ToString("yyyy-MM-dd HH:mm:ss") )
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.FollowUpTime)"
Caption="@L["FollowUpTime"]">
<DisplayTemplate>
@(context.FollowUpTime.HasValue ? Clock.ConvertToUserTime(context.FollowUpTime.Value).ToString("yyyy-MM-dd HH:mm:ss") : "N/A")
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="MeetingDto"
Field="@nameof(MeetingDto.Description)"
Caption="@L["Description"]">
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
<Modal @ref="@CreateModal">
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@L["NewMeeting"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Subject"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Subject">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["StartTime"] / @L["EndTime"]</FieldLabel>
<DatePicker TValue="DateTime?" @bind-Dates="SelectedDates" InputMode="DateInputMode.DateTime" SelectionMode="DateInputSelectionMode.Range" />
</Field>
<Field>
<FieldLabel>@L["ActualStartTime"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.ActualStartTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Field>
<FieldLabel>@L["CanceledTime"]</FieldLabel>
<DateEdit TValue="DateTime?" @bind-Date="NewEntity.CanceledTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Field>
<FieldLabel>@L["ReminderTime"]</FieldLabel>
<DateEdit TValue="DateTimeOffset" @bind-Date="NewEntity.ReminderTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Field>
<FieldLabel>@L["FollowUpTime"]</FieldLabel>
<DateEdit TValue="DateTimeOffset?" @bind-Date="NewEntity.FollowUpTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Description"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Description">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
<Modal @ref="@EditModal">
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Subject</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@EditingEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Subject"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Subject">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["StartTime"] / @L["EndTime"]</FieldLabel>
<DatePicker TValue="DateTime?" @bind-Dates="SelectedDates" InputMode="DateInputMode.DateTime" SelectionMode="DateInputSelectionMode.Range" />
</Field>
<Field>
<FieldLabel>@L["ActualStartTime"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.ActualStartTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Field>
<FieldLabel>@L["CanceledTime"]</FieldLabel>
<DateEdit TValue="DateTime?" @bind-Date="EditingEntity.CanceledTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Field>
<FieldLabel>@L["ReminderTime"]</FieldLabel>
<DateEdit TValue="DateTimeOffset" @bind-Date="EditingEntity.ReminderTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Field>
<FieldLabel>@L["FollowUpTime"]</FieldLabel>
<DateEdit TValue="DateTimeOffset?" @bind-Date="EditingEntity.FollowUpTime" InputMode="DateInputMode.DateTime"/>
</Field>
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Description"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Description">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
@code {
IReadOnlyList<DateTime?> SelectedDates;
public Meeting()
{
CreatePolicyName = TimeZoneAppPermissions.Meetings.Create;
UpdatePolicyName = TimeZoneAppPermissions.Meetings.Edit;
DeletePolicyName = TimeZoneAppPermissions.Meetings.Delete;
}
protected override async Task OpenCreateModalAsync()
{
await base.OpenCreateModalAsync();
var now = DateTime.Now;
SelectedDates = new List<DateTime?> { now.Date.AddHours(10),now.Date.AddDays(7).AddHours(10) };
NewEntity.ActualStartTime = now.Date.AddHours(11);
NewEntity.CanceledTime = now.Date.AddHours(12);
NewEntity.ReminderTime = now.Date.AddHours(13);
NewEntity.FollowUpTime = now.Date.AddHours(14);
}
protected override Task OnCreatingEntityAsync()
{
if (SelectedDates.Count == 2 && SelectedDates[0].HasValue && SelectedDates[1].HasValue)
{
NewEntity.StartTime = Clock.ConvertToUtc(SelectedDates[0]!.Value);
NewEntity.EndTime = Clock.ConvertToUtc(SelectedDates[1]!.Value);
}
NewEntity.ActualStartTime = Clock.ConvertToUtc(NewEntity.ActualStartTime);
NewEntity.CanceledTime = NewEntity.CanceledTime.HasValue ? Clock.ConvertToUtc(NewEntity.CanceledTime.Value) : null;
NewEntity.ReminderTime = Clock.ConvertToUtc(NewEntity.ReminderTime.DateTime);
NewEntity.FollowUpTime = NewEntity.FollowUpTime.HasValue ? Clock.ConvertToUtc(NewEntity.FollowUpTime.Value.DateTime) : null;
return Task.CompletedTask;
}
protected override async Task OpenEditModalAsync(MeetingDto entity)
{
await base.OpenEditModalAsync(entity);
SelectedDates = new List<DateTime?> { Clock.ConvertToUserTime(EditingEntity.StartTime), Clock.ConvertToUserTime(EditingEntity.EndTime) };
EditingEntity.ActualStartTime = Clock.ConvertToUserTime(EditingEntity.ActualStartTime);
EditingEntity.CanceledTime = EditingEntity.CanceledTime.HasValue ? Clock.ConvertToUserTime(EditingEntity.CanceledTime.Value) : null;
EditingEntity.ReminderTime = Clock.ConvertToUserTime(EditingEntity.ReminderTime);
EditingEntity.FollowUpTime = EditingEntity.FollowUpTime.HasValue ? Clock.ConvertToUserTime(EditingEntity.FollowUpTime.Value) : null;
}
protected override Task OnUpdatingEntityAsync()
{
if (SelectedDates.Count == 2 && SelectedDates[0].HasValue && SelectedDates[1].HasValue)
{
EditingEntity.StartTime = Clock.ConvertToUtc(SelectedDates[0]!.Value);
EditingEntity.EndTime = Clock.ConvertToUtc(SelectedDates[1]!.Value);
}
EditingEntity.ActualStartTime = Clock.ConvertToUtc(EditingEntity.ActualStartTime);
EditingEntity.CanceledTime = EditingEntity.CanceledTime.HasValue ? Clock.ConvertToUtc(EditingEntity.CanceledTime.Value) : null;
return Task.CompletedTask;
}
}
```
![](blazor-list.png)
![](blazor-create.png)
![](blazor-edit.png)
## Timezone Settings Change
If the timezone settings change, then all times will be converted to the new timezone. For example, if the current timezone changes from `Europe/Istanbul` to `Europe/Berlin`, then all times will be converted to the `Europe/Berlin` timezone.
![](berlin.png)
`Europe/Istanbul`:
![](auth-list-utc3.png)
![](mvc-list-utc3.png)
`Europe/Berlin`:
![](auth-list-utc1.png)
![](mvc-list-utc1.png)
## Browser Timezone Detection
When no timezone setting is configured, ABP's MVC, Blazor, and Angular applications will automatically detect the browser's timezone during initialization. The detected timezone is then stored in either the request's Cookie or Header.
This functionality is implemented by the `UseAbpTimeZone` middleware, which follows a specific order to determine the appropriate timezone:
1. First, it attempts to retrieve the timezone from the application/tenant/user settings
2. If no setting is found, it tries to get the timezone from the request information, including Cookie, Header, QueryString, and Form
3. Finally, if no timezone information is found, it falls back to using the server's timezone as the default
> The timezone information is stored using the key `__timezone`
## TimeZoneApp Source Code
You can download and view the [TimeZoneApp source code](https://github.com/maliming/TimeZone) for detailed implementation.
## Summary
Through this article, we learned how to handle timezone in different types of UIs. I hope this article is helpful to you. If you have any questions, please contact me at any time.

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

@ -75,7 +75,7 @@ Let's create a simple Vue component to display the TODO list.
```html
<div id="vue-app">
<message-component :message="'Welcome, @CurrentUser.UserName !'"></todo-component>
<message-component :message="'Welcome, @CurrentUser.UserName !'"></message-component>
</div>
```

115
docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md

@ -0,0 +1,115 @@
# How to Change the CurrentUser in ABP?
[ABP Framework](https://abp.io/) provides a powerful service for accessing information about the currently authenticated user in your application. Understanding how to use and modify this service (`ICurrentUser`) is essential for both basic and certain advanced scenarios.
In this article, we'll explore the [`CurrentUser` service](https://abp.io/docs/latest/framework/infrastructure/current-user), its use cases, and how to change it when necessary.
## Understanding the ICurrentUser Service
The `ICurrentUser` interface is the primary service in ABP Framework for obtaining information about the logged-in user. It provides some key properties, such as `Id`, `UserName`, `TenantId`, `Roles` (roleNames), and more...
`ICurrentUser` is implemented on the `ICurrentPrincipalAccessor` service and works with claims as well. So, all of these properties are actually retrieved from the claims. ICurrentUser has some methods to directly work with the claims, such as:
* FindClaim (finds a single claim by name)
* FindClaims (gets all claims with the given name)
* IsInRole (checks if the user has a specific role)
* GetAllClaims (gets all claims of the user)
## Where the CurrentUser Service is Used?
The CurrentUser service is used extensively throughout ABP applications whenever there's a need to access information about the logged-in user. Common scenarios include: authorization checks, logging, setting common properties like `CreatorId`, `LastModifierId`, `DeleterId`, and more...
## When to Change the CurrentUser Service?
While the CurrentUser service works automatically in the context of HTTP requests (it gets the `User` property of the current `HttpContext`), there are advanced scenarios where you might need to manually set or change the current user:
1. **Background workers:** When executing code outside the context of a user request
2. **Event handlers:** When processing events that may run in a different context
3. **Unit & integration tests:** When simulating a user for testing purposes
## How to Change the CurrentUser Service?
If you need to change the CurrentUser service, you can inject the `ICurrentPrincipalAccessor` service, use its `Change` method to change the current user, and then use the `CurrentUser` service as usual.
Here's how to change the current user for a specific scope:
```csharp
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
namespace MyProject.Products;
public class ProductEventHandler : IDistributedEventHandler<OrderPlacedEto>, ITransientDependency
{
private readonly IProductRepository _productRepository;
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
private readonly IdentityUserManager _userManager;
public ProductEventHandler(
IProductRepository productRepository,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IdentityUserManager userManager
)
{
_productRepository = productRepository;
_currentPrincipalAccessor = currentPrincipalAccessor;
_userManager = userManager;
}
public async Task HandleEventAsync(OrderPlacedEto eventData)
{
var product = await _productRepository.FindAsync(eventData.ProductId);
if (product == null)
{
return;
}
//Get the admin user
var adminUser = await _userManager.FindByNameAsync("admin");
if (adminUser == null)
{
return;
}
var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(
new Claim[] {
new Claim(AbpClaimTypes.UserId, adminUser.Id.ToString()),
new Claim(AbpClaimTypes.UserName, "admin"),
}));
//IMPORTANT: It will set the CreatorId, LastModifierId, etc. with the admin user
using (_currentPrincipalAccessor.Change(newPrincipal))
{
product.StockCount -= eventData.Quantity;
// Update the product
await _productRepository.UpdateAsync(product);
}
}
}
```
In this example, we have a distributed event handler that processes an `OrderPlacedEto` event. When an order is placed, we need to update the product's stock count. However, we want this operation to be performed under an admin user's context for auditing purposes.
Here's what the code does step by step:
1. First, it retrieves the product using the product ID from the event data.
2. Then, it finds the admin user by username using the `_userManager.FindByNameAsync("admin")`.
3. A new `ClaimsPrincipal` is created with the admin user's claims (`UserId` and `UserName`).
4. Using the `_currentPrincipalAccessor.Change()` method within a `using` statement, it temporarily changes the current user context to the admin user.
5. Inside this scope, it updates the product's stock count by subtracting the ordered quantity.
6. Finally, it saves the changes to the database using the repository.
**The important part here is that any audit properties (like `CreatorId`, `LastModifierId`, etc.) will be set to the admin user's ID because we changed the current principal. Once the using block ends, the original user context is automatically restored.**
This pattern is particularly useful in background jobs, event handlers, or any scenario where you need to perform operations under a specific user's context, regardless of the actual authenticated user.
## Conclusion
The `CurrentUser` service in ABP Framework provides a simple way to access information about the authenticated user. While it works automatically in most scenarios, there are cases where you need to explicitly change the current user identity, particularly in background processing scenarios.
By using the ICurrentPrincipalAccessor.Change() method within a using statement, you can temporarily change the current user for a specific scope of execution, enabling your background processes, event handlers, or tests to execute with the identity of a specific user.

38
docs/en/Community-Articles/2025-03-27-Announcing-the-ABP-Contributor-Program/post.md

@ -0,0 +1,38 @@
We are excited to introduce the ABP Contributor Program, which is an amazing opportunity for community enthusiasts to contribute to the ABP ecosystem while getting valuable benefits\!
**Create, Contribute and Be Part of Something Bigger**
This is your opportunity to not only share your knowledge but also to grow alongside a passionate community of developers and creators. When you create and contribute to the ABP ecosystem, you’re not just building content, you’re also helping shape the future of ABP.
## **Why Become an ABP Contributor?**
By sharing your knowledge, creating valuable content, and engaging with the ABP Community, you’ll enjoy a range of benefits, including:
* Free ABP Personal License – If you meet all the requirements, you’ll receive an ABP Personal License at no cost
* Community Badge & Title – Stand out in the ABP Community with a special contributor badge and label next to your name.
* Exclusive Discord Role – Get a unique Contributor role on the ABP Community Discord server.
* Increased Visibility – Your contributions will be recognized by hundreds of thousands of developers worldwide.
Completely Free – No fees, just log in, contribute, and start collecting benefits\!
## **How to Apply?**
If you’re passionate about ABP and think you meet the required criteria, follow these simple steps:
Create content related to ABP: blog posts, tutorials, videos, or documentation. You can publish it on the ABP Community website or any external platform (just make sure to submit it to the ABP Community site).
Apply by email: Send your application to marketing@volosoft.com, and the ABP Team will review your eligibility.
Periodic Assessments: The team reviews submissions every three months to determine and renew contributor status. Keep contributing to continue enjoying the perks\!
## **Join Us & Grow\!**
This is your chance to give back to the community, grow your presence, and get rewarded for your contributions. Whether you love writing documentation, sharing tutorials, or building open-source projects, your efforts matter.
Apply today and be part of something impactful\!
[image1]: images/img_1.png

1
docs/en/cli/index.md

@ -888,6 +888,7 @@ Some features of the CLI requires to be logged in to ABP Platform. The login com
```bash
abp login # Opens a default browser to log in to ABP Platform via abp.io
abp login --device # Use device login flow
abp login username -p ****** --password # Use user password login
```
A new login with an already active session overwrites the previous session.

4
docs/en/docs-nav.json

@ -613,6 +613,10 @@
"text": "Google Cloud Storage Provider",
"path": "framework/infrastructure/blob-storing/google.md"
},
{
"text": "Bunny.Net Provider",
"path": "framework/infrastructure/blob-storing/bunny.md"
},
{
"text": "Create a Custom Provider",
"path": "framework/infrastructure/blob-storing/custom-provider.md"

6
docs/en/framework/architecture/microservices/index.md

@ -25,6 +25,8 @@ However, developing such a well-modular application can be a problem since it is
ABP can help you in that point by offering a **microservice-compatible, strict module architecture** where your module is split into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](../best-practices) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration.
## Microservice Demo Solution: eShopOnAbp
## Microservice Solution Template
The [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) demonstrates a complete microservice solution based on the ABP.
ABP provides a pre-architected and production-ready microservice solution template that includes multiple services, API gateways and applications well integrated with each other. This template helps you quickly start building distributed systems with common microservice patterns.
See the [Microservice Solution Template](../../../solution-templates/microservice/index.md) documentation for details.

6
docs/en/framework/infrastructure/event-bus/distributed/index.md

@ -297,10 +297,14 @@ Configure<AbpDistributedEntityEventOptions>(options =>
options.AutoEventSelectors.Add(
type => type.Namespace.StartsWith("MyProject.")
);
//Ignore for a single entity
options.IgnoredEventSelectors.Add<IgnoredProductEntity>();
});
````
* The last one provides flexibility to decide if the events should be published for the given entity type. Returns `true` to accept a `Type`.
* The `type.Namespace.StartsWith("MyProject.")` provides flexibility to decide if the events should be published for the given entity type. Returns `true` to accept a `Type`.
* The `IgnoredEventSelectors` is used to ignore the events for the specified entity types. It is useful if you enabled for all entities and want to ignore for some entities.
You can add more than one selector. If one of the selectors match for an entity type, then it is selected.

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

@ -24,6 +24,8 @@ public class EmailSettingProvider : SettingDefinitionProvider
}
````
> If you're developing a [DDD module](../architecture/domain-driven-design), you usually create this class in the `Domain` layer, of course, that's not mandatory.
ABP automatically discovers this class and registers the setting definitions.
### SettingDefinition

100
docs/en/framework/infrastructure/timing.md

@ -8,11 +8,11 @@ ABP provides a basic infrastructure to make it easy and handle automatically whe
## IClock
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**.
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. Things especially get complicated when you save this value to a database and read it later, or send it to a client in a **different time zone**.
One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this way, you can convert it to the time zone of the target client when needed.
One solution to this problem is always using `DateTime.UtcNow` and assuming all `DateTime` objects as UTC time. In this way, you can convert it to the time zone of the target client when needed.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the datetime (UTC or local) in a single point in your application.
**Example: Getting the current time**
@ -40,14 +40,14 @@ namespace AbpDemo
}
````
* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already injects it and provides as a base property - so, you can directly use as `Clock`.
* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already inject it and provide it as a base property - so, you can directly use it as `Clock`.
* Use the `Now` property to get the current time.
> Most of the times, `IClock` is the only service you need to know and use in your application.
> Most of the time, `IClock` is the only service you need to know and use in your application.
### Clock Options
`AbpClockOptions` is the [options](../fundamentals/options.md) class that used to set the clock kind.
`AbpClockOptions` is the [options](../fundamentals/options.md) class that is used to set the clock kind.
**Example: Use UTC Clock**
@ -58,13 +58,12 @@ Configure<AbpClockOptions>(options =>
});
````
Write this inside the `ConfigureServices` method of your [module](../architecture/modularity/basics.md).
> Default `Kind` is `Unspecified`, that actually make the Clock as it doesn't exists at all. Either make it `Utc` or `Local` if you want to get benefit of the Clock system.
Write this inside of the `ConfigureServices` method of your [module](../architecture/modularity/basics.md).
> The default `Kind` is `Unspecified`, which effectively disables the **Clock** functionality. Either make it `Utc` or `Local` if you want to get the benefit of the Clock system.
### DateTime Normalization
Other important function of the `IClock` is to normalize `DateTime` objects.
Another important function of the `IClock` is to normalize `DateTime` objects.
**Example usage:**
@ -75,11 +74,11 @@ var normalizedDateTime = Clock.Normalize(dateTime)
`Normalize` method works as described below:
* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if current Clock is UTC and given `DateTime` is local.
* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if current Clock is local and given `DateTime` is UTC.
* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if the current Clock is UTC and the given `DateTime` is local.
* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if the current Clock is local and the given `DateTime` is UTC.
* Sets `Kind` of the given `DateTime` (using the `DateTime.SpecifyKind(...)` method) to the `Kind` of the current Clock if given `DateTime`'s `Kind` is `Unspecified`.
`Normalize` method is used by the ABP when the it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples;
`Normalize` method is used by the ABP when it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples;
* `DateTime` type binding in the ASP.NET Core MVC model binding.
* Saving data to and reading data from database via [Entity Framework Core](../data/entity-framework-core).
@ -89,12 +88,67 @@ var normalizedDateTime = Clock.Normalize(dateTime)
`DisableDateTimeNormalization` attribute can be used to disable the normalization operation for desired classes or properties.
### DateTime Converter Between UTC and User's Time Zone
#### Convert given UTC to user's time zone.
`DateTime ConvertToUserTime(DateTime utcDateTime)` and `DateTimeOffset ConvertToUserTime(DateTimeOffset dateTimeOffset)` methods convert given UTC `DateTime` or `DateTimeOffset` to the user's time zone.
> If `SupportsMultipleTimezone` is `false` or `dateTime.Kind` is not `Utc` or these is no timezone setting, it returns the given `DateTime` or `DateTimeOffset` without any changes.
**Example:**
If user's `TimeZone Setting` is `Europe/Istanbul`
````csharp
// 2025-03-01T05:30:00Z
var utcTime = new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Utc);
var userTime = Clock.ConvertToUserTime(utcTime);
// Europe/Istanbul has 3 hours difference with UTC. So, the result will be 3 hours later.
userTime.Kind.ShouldBe(DateTimeKind.Unspecified);
userTime.ToString("O").ShouldBe("2025-03-01T08:30:00");
````
````csharp
// 2025-03-01T05:30:00Z
var utcTime = new DateTimeOffset(new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Utc), TimeSpan.Zero);
var userTime = Clock.ConvertToUserTime(utcTime);
// Europe/Istanbul has 3 hours difference with UTC. So, the result will be 3 hours later.
userTime.Offset.ShouldBe(TimeSpan.FromHours(3));
userTime.ToString("O").ShouldBe("2025-03-01T08:30:00.0000000+03:00");
````
#### Converts given user's DateTime to UTC
`DateTime ConvertToUtc(DateTime dateTime)` method convert given user's `DateTime` to UTC.
> If `SupportsMultipleTimezone` is `false` or `dateTime.Kind` is `Utc` or these is no timezone setting, it returns the given `DateTime` without any changes.
**Example:**
If user's `TimeZone Setting` is `Europe/Istanbul`
````csharp
// 2025-03-01T05:30:00
var userTime = new DateTime(2025, 3, 1, 5, 30, 0, DateTimeKind.Unspecified); //Same as Local
var utcTime = Clock.ConvertToUtc(userTime);
// Europe/Istanbul has 3 hours difference with UTC. So, the result will be 3 hours earlier.
utcTime.Kind.ShouldBe(DateTimeKind.Utc);
utcTime.ToString("O").ShouldBe("2025-03-01T02:30:00.0000000Z");
````
### Other IClock Properties
In addition to the `Now`, `IClock` service has the following properties:
* `Kind`: Returns a `DateTimeKind` for the currently used clock type (`DateTimeKind.Utc`, `DateTimeKind.Local` or `DateTimeKind.Unspecified`).
* `SupportsMultipleTimezone`: Returns `true` if currently used clock is UTC.
* `SupportsMultipleTimezone`: Returns `true` if the currently used clock is UTC.
## Time Zones
@ -102,12 +156,26 @@ This section covers the ABP infrastructure related to managing time zones.
### TimeZone Setting
ABP defines **a setting**, named `Abp.Timing.TimeZone`, that can be used to set and get the time zone for a user, [tenant](../architecture/multi-tenancy) or globally for the application. The default value is `UTC`.
ABP defines **a setting**, named `Abp.Timing.TimeZone`, that can be used to set and get the time zone for a user, [tenant](../architecture/multi-tenancy) or globally for the application. The default value is empty, which means the application will use the server's time zone.
You can change your host/tenant global time zone in the [Settings Management UI](../../modules/setting-management#setting-management-ui)
The [Account Pro Module](../../modules/account-pro#Time-Zone-Setting) supports user to set their own time zone in the account settings page.
See the [setting documentation](../infrastructure/settings.md) to learn more about the setting system.
### UseAbpTimeZone Middleware
The `app.UseAbpTimeZone()` middleware is used to set the time zone for the current request.
* It will get timezone from settings, the order is `User` -> `Tenant` -> `Application/Global`.
* If current request is anonymous, it will get timezone from the request header/cookie/form/query string. the key is `__timezone`.
> If you want to get current timezone, you can inject `ICurrentTimezoneProvider` service.
> Please add this middleware after authentication.
### ITimezoneProvider
`ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name.
`ITimezoneProvider` is a service to simply convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get the list of these time zones and get a `TimeZoneInfo` with a given name.
It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library.

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 KiB

After

Width:  |  Height:  |  Size: 258 KiB

BIN
docs/en/images/account-pro-time-zone-setting.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/en/images/create-aspnet-core-application.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 125 KiB

BIN
docs/en/images/create-new-aspnet-core-application-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 181 KiB

BIN
docs/en/images/pen-test-alert-list-9.1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/images/select-empty-web-application-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 114 KiB

BIN
docs/en/images/setting-management-email-ui.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/en/images/setting-management-feature-management-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/images/setting-management-time-zone-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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

@ -273,6 +273,12 @@ Users who register via both local registration and external/social login using t
![require-local-password-on-social-account-linking](../images/require-local-password-on-social-account-linking.png)
### Time Zone Setting
Users can to set their own time zone in the account settings page if application is [supports multiple timezones](../framework/infrastructure/timing.md#clock-options).
![account-pro-time-zone-setting](../images/account-pro-time-zone-setting.png)
## Internals
### Settings

6
docs/en/modules/setting-management.md

@ -118,12 +118,16 @@ The order of the providers are important. Providers are executed in the reverse
## Setting Management UI
Setting Mangement module provided the email setting UI by default.
Setting Mangement module provided the Email setting, Feature management and Timezone setting UI by default.
![EmailSettingUi](../images/setting-management-email-ui.png)
> You can click the Send test email button to send a test email to check your email settings.
![FeatureManagementUi](../images/setting-management-feature-management-ui.png)
![TimeZoneSettingUi](../images/setting-management-time-zone-ui.png)
Setting it is extensible; You can add your tabs to this page for your application settings.
### MVC UI

67
docs/en/others/penetration-test-report.md

@ -1,6 +1,6 @@
# ABP Penetration Test Report
The ABP Commercial MVC `v9.0.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document.
The ABP Commercial MVC `v9.1.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document.
Many of these alerts are **false-positive**, meaning the vulnerability scanner detected these issues, but they are not exploitable. It's clearly explained for each false-positive alert why this alert is a false-positive.
@ -10,53 +10,23 @@ In the next sections, you will find the affected URLs, attack parameters (reques
There are high _(red flag)_, medium _(orange flag)_, low _(yellow flag)_, and informational _(blue flag)_ alerts.
![penetration-test-9.0.0](../images/pen-test-alert-list-9.0.png)
![penetration-test-9.1.0](../images/pen-test-alert-list-9.1.png)
> The informational alerts are not mentioned in this document. These alerts are not raising any risks on your application and they are optional.
### Path Traversal [Risk: High] - False Positive
### Spring4Shell [Risk: High] - False Positive
- *[POST] - https://localhost:44349/Account/Login* (attack: **\Login**)
- *[POST] - https://localhost:44349/Account/LinkLogin* (attack: **\LinkLogin**)
- *[POST] - https://localhost:44349/Account/Register* (attack: **\Register**)
- *[POST] - https://localhost:44349/Account/SecurityLogs* (attack: **\SecurityLogs**)
- *[POST] - https://localhost:44349/Identity/SecurityLogs* (attack: **\SecurityLogs**)
- *[POST] - https://localhost:44349/Account/ForgotPassword* (attack: **class.module.classLoader.DefaultAssertionStatus=nonsense**)
- *[POST] - https://localhost:44349/Account/Login* (attack: **class.module.classLoader.DefaultAssertionStatus=nonsense**)
- *[POST] - https://localhost:44349/Account/Login?ReturnUrl=%2FSettingManagement* (attack: **class.module.classLoader.DefaultAssertionStatus=nonsense**)
**Description**:
The Path Traversal attack technique allows an attacker access to files, directories, and commands that potentially reside outside the web document root directory. An attacker may manipulate a URL in such a way that the website will execute or reveal the contents of arbitrary files anywhere on the web server. Any device that exposes an HTTP-based interface is potentially vulnerable to Path Traversal.
**Solution**:
This is a **false-positive** alert since ABP does all related checks for this kind of attacks on the backend side for these endpoints.
### SQL Injection [Risk: High] - False Positive
* *[POST] — https://localhost:44349/Account/Login* (attack: **1q2w3E* AND 1=1 --**)
* *[POST] — https://localhost:44349/Account/ImpersonateUser* (attack: **CfDJ8Pyqeg0vtHtJpnK-9eLaft7-JxLJfJ6WHKPOdBZVxz14BDo061qpJ2NLplgAn2Hw16ec0IR38_wWAUkJGxP8hL6PcLfH0bh-ATNTspWyWYTGGbiH-zeKWiS5vWX-br2BA1hE7Dc45eWGUZNcVc_vm2s AND 1=1 --**)
* *[POST] — https://localhost:44349/Abp/MultiTenancy/TenantSwitchModal* (attack: **CfDJ8Pyqeg0vtHtJpnK-9eLaft7-JxLJfJ6WHKPOdBZVxz14BDo061qpJ2NLplgAn2Hw16ec0IR38_wWAUkJGxP8hL6PcLfH0bh-ATNTspWyWYTGGbiH-zeKWiS5vWX-br2BA1hE7Dc45eWGUZNcVc_vm2s AND 1=1 --**)
* *[POST] — https://localhost:44349/Identity/OrganizationUnits/\** (attack: **6f4cd0ab-f4eb-7ce0-8b26-3a138af1840d" AND '1'='1**) (also, several other URLs...)
* *[POST] — https://localhost:44349/Identity/ClaimTypes/CreateModal* (attack: **aaaad AND '1'='1**)
**Description**:
SQL injection may be possible. SQL injection is a web security vulnerability that allows an attacker to interfere with the queries that an application makes to its database. It allows an attacker to view data that they are not normally able to retrieve and perform unauthorized actions.
The application appears to be vulnerable to CVE-2022-22965 (otherwise known as Spring4Shell) - remote code execution (RCE) via data binding.
**Explanation**:
ABP uses Entity Framework Core and LINQ. **It's safe against SQL Injection because it passes all data to the database via SQL parameters.** LINQ queries are not composed by using string manipulation or concatenation, that's why they are not susceptible to traditional SQL injection attacks. Therefore, this is a **false-positive** alert.
### SQL Injection - Authentication Bypass [Risk: High] - False Positive
* *[POST] — https://localhost:44349/Account/Login* (attack: **false AND 1=1 --**)
**Description**:
SQL injection may be possible on a login page, potentially allowing the application's authentication mechanism to be bypassed.
**Solution**:
This alert indicates that we must not trust client side input (even if there is client side validation in place) and check all data on the server side. ABP Framework already does that and makes server-side validations while authenticating a user. Therefore this is a **false-positive** alert.
ABP Framework is built on top of ASP.NET Core and does not use the Spring Framework. This application does not rely on Java-based technologies, making it immune to vulnerabilities like Spring4Shell. The detection is a false positive as there are no Spring dependencies in the project.
### Absence of Anti-CSRF Tokens [Risk: Medium] — False Positive
@ -130,6 +100,10 @@ Configure<AbpSecurityHeadersOptions>(options =>
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A&returnUrl=%2F&uiCulture=ar* (with combination of different parameters)
- *[GET] — https://localhost:44349/Abp/ApplicationLocalizationScript?cultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A* (with combination of different parameters)
- *[GET] — https://localhost:44349/api/language-management/language-texts?filter=aa&resourceName=&baseCultureName=es&targetCultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A&getOnlyEmptyValues=false&sorting=name+asc&skipCount=0&maxResultCount=10* (with combination of different parameters)
- *[GET] — https://localhost:44349/LanguageManagement/Texts/Edit?name=IncorrectCaptchaAnswer&targetCultureName=sv&resourceName=AbpAccount&baseCultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A* (with combination of different parameters)
- *[POST] — https://localhost:44349/Account/Login?ReturnUrl=%2FSettingManagement*
- *[POST] — https://localhost:44349/Account/Manage* (with combination of different parameters)
**Description:**
@ -149,14 +123,9 @@ The second URL is also a **false-positive** alert because there is no bad charac
### XSLT Injection [Risk: Medium] - False Positive
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=%3Cxsl%3Avalue-of+select%3D%22system-property%28%27xsl%3Avendor%27%29%22%2F%3E&returnUrl=%2F&uiCulture=ar*
- *[POST] — https://localhost:44349/Account/Login _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/ImpersonateUser _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/Register _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/Manage _(same URL with different parameters...)_*
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=%3Cxsl%3Avalue-of+select%3D%22system-property%28%27xsl%3Avendor%27%29%22%2F%3E&returnUrl=%2F&uiCulture=tr _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/ForgotPassword _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/SaasWidgets/LatestTenants _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/AuditLogs*
- *[GET] — https://localhost:44349/SaasWidgets/LatestTenants _(same URL with different parameters...)_*
**Description**:
@ -168,10 +137,12 @@ This is a **false-positive** alert. v9.0 uses .NET 9 and the XSLT transformation
### Application Error Disclosure [Risk: Low] — False Positive
- *[GET] — https://localhost:44349/Abp/Languages/Switch*
- *[POST] — https://localhost:44349/Account/ImpersonateUser*
- *[GET] — https://localhost:44349/Account/ExternalLogins*
- *[GET] — https://localhost:44349/Account/Logout*
- *[GET] — https://localhost:44349/OrganizationUnits*
- *[GET] — https://localhost:44349/HostDashboard*
- *[GET] — https://localhost:44349/Saas/Host/Editions*
- *[GET] — https://localhost:44349/Saas/Host/Tenants*
**Description:**
@ -326,7 +297,7 @@ This vulnerability was reported as a positive alert, because ABP uses the [zxcvb
- *[GET] — https://localhost:44349/client-proxies/account-proxy.js?_v=638550091940000000 (and other client-proxies related URLs...)*
- *[GET] — https://localhost:44349/favicon.svg*
- *[GET] — https://localhost:44349/images/getting-started/bg-01.png* (and other image URLs...)
- *[GET] — https://localhost:44349/images/getting-started/bg-01.png* (and other image URLs...)
- *[GET] — https://localhost:44349/global-styles.css?_v=638556076064360335*
- *[GET] — https://localhost:44349/libs/@fortawesome/fontawesome-free/css/all.css?_v=%5CWEB-INF%5Cweb.xml (other several URLs...)*
- other URLs...

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

@ -0,0 +1,64 @@
# ABP Version 9.2 Migration Guide
This document is a guide for upgrading ABP v9.x solutions to ABP v9.2. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
## Open-Source (Framework)
### Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
When multiple applications share the same storage for [ABP's Background Jobs Module](../../modules/background-jobs.md), jobs from one application might be visible to another application. This can lead to the following issues:
1. Applications may attempt to process jobs that don't belong to them
2. These attempts fail with "Undefined background job for the job name" error
3. Failed jobs are marked as `IsAbandoned = true`
4. The original application can no longer process these abandoned jobs
To fix this, we added the `ApplicationName` property to the `AbpBackgroundJobWorkerOptions` class. This property allows you to specify the application name, which helps isolate jobs between different applications. (See the [PR](https://github.com/abpframework/abp/pull/22169) for more details.)
**By default there is no breaking change. However, you need to create a migration for the database to add the `ApplicationName` column to the relevant table and apply it to your database.**
### Upgraded `MongoDB.Driver` to `3.1.0`
In this version, we upgraded `MongoDB.Driver` to `3.1.0`. To migrate your application, please refer to our [MongoDB Driver 2 to 3 Migration Guide](./MongoDB-Driver-2-to-3.md) document.
### Replaced Toastr with Custom Implementation (without depending on any 3rd party library)
In this version, we replaced Toastr with a custom implementation that does not depend on any 3rd party library. This is a breaking change if you are using the `Toastr` library.
Here are the migration steps:
1. Remove any direct Toastr dependencies from your application
2. Update your notification calls to use the new API
3. Migrate any custom styles or configurations
For detailed implementation guidelines and API documentation, see:
- [Notify Documentation](../../framework/ui/mvc-razor-pages/javascript-api/notify.md)
- [PR #21940 for more information](https://github.com/abpframework/abp/pull/21940)
## PRO
> Please check the **Open-Source (Framework)** section before reading this section. The listed topics might affect your application and you might need to take care of them.
If you are a paid-license owner and using the ABP's paid version, then please follow the following sections to get informed about the breaking changes and apply the necessary ones:
### Identity Pro Module: Require Email Verification to Register
In this version, we added a new setting to the Identity Pro module to require email verification to register. This is a security measure to prevent spam registrations:
![](./images/require-email-verification-for-register.png)
Typically, no changes are required. However, if you have inherited from the `AccountAppService` class and implemented your own logic, you'll need to update your constructor to match the new signature since two new services are now injected:
```diff
//code omitted for brevity...
+ protected IDistributedCache<EmailConfirmationCodeCacheItem> EmailConfirmationCodeCache { get; }
+ protected IdentityErrorDescriber IdentityErrorDescriber { get; }
public AccountAppService(
- IdentityUserTwoFactorChecker identityUserTwoFactorChecker)
+ IdentityUserTwoFactorChecker identityUserTwoFactorChecker,
+ IdentityErrorDescriber identityErrorDescriber)
```

BIN
docs/en/release-info/migration-guides/images/require-email-verification-for-register.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

1
docs/en/release-info/migration-guides/index.md

@ -2,6 +2,7 @@
The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../upgrading.md).
- [9.x to 9.2](abp-9-2.md)
- [9.0 to 9.1](abp-9-1.md)
- [8.x to 9.0](abp-9-0.md)
- [8.x to 8.3](abp-8-3.md)

13
docs/en/release-info/release-notes.md

@ -4,9 +4,20 @@ This document contains **brief release notes** for each release. Release notes o
> If you want to read the release notes for each ABP Studio release, check it out from [here](../studio/release-notes.md).
## 9.2 (2025-03-25)
This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/community/articles/abp-platform-9.2-rc-has-been-released-jpq072nh)** for the v9.2 release.
* Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
* Docs Module: Added "Alternative Words" to Filter Items
* Introducing the [Bunny BLOB Storage Provider](../framework/infrastructure/blob-storing/bunny.md)
* Upgraded `MongoDB.Driver` to **v3.1.0**
* Identity Pro Module: Require Email Verification to Register
* Switching users during OAuth login
## 9.1 (2025-01-16)
This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/blog/abp-9-1-release-candidate)** for the v9.1 release.
See the detailed **[blog post / announcement](https://abp.io/community/articles/abp.io-platform-9.1-final-has-been-released-h96a56qa)** for the v9.1 release.
* Upgraded to Angular 19
* Upgraded to OpenIddict 6.0

20
docs/en/release-info/road-map.md

@ -4,9 +4,9 @@ This document provides a road map, release schedule, and planned features for th
## Next Versions
### v9.2
### v9.3
The next version will be 9.2 and planned to release the stable 9.2 version in June 2025. We will be mostly working on the following topics:
The next version will be 9.3 and planned to release the stable 9.3 version in July 2025. We will be mostly working on the following topics:
* Framework
* Upgrading 3rd-party dependencies
@ -15,22 +15,20 @@ The next version will be 9.2 and planned to release the stable 9.2 version in Ju
* ABP Suite
* Define navigation properties without target string property dependency
* Improvements one-to-many scenarios
* Access to default code generation templates for customized templates
* File Upload Modal enhancements
* Master/Detail DataGrid Toggle Detail Row Enhancements for Blazor UI
* ABP Studio
* Allow to directly create new solutions with ABP's RC (Release Candidate) versions
* Automate more details on new service creation for a microservice solution
* Support multiple concurrent Kubernetes deployment/integration scenarios
* Improve the module installation experience / installation guides
* Auto-install 3rd-party dependencies
* Better handle long log files
* Allow to directly create new solutions with ABP's RC (Release Candidate) versions
* Support Intel processors for Mac computers, support ARM chipset for Windows and support Linux OS
* Improve client proxy generation experience
* Modular Monolith Application Startup Template
* Application modules
* Account module: Support mixed social/local login scenarios & enforcing email verification in wide aspect
* Account module: Support mixed social/local login scenarios & adding security related features
* UI/UX improvements on existing application modules
* Updating existing tutorials & documents (with other UI & DB options)
@ -55,6 +53,8 @@ The ABP framework is [open source](https://github.com/abpframework/abp) and free
* [#15932](https://github.com/abpframework/abp/issues/15932) / Introduce ABP Diagnostics Module
* [#16744](https://github.com/abpframework/abp/issues/16744) / State Management API
* [#17815](https://github.com/abpframework/abp/issues/17815) / Operation Rate Limiting
* [#119](https://github.com/abpframework/abp/issues/119) / REST API Versioning Improvements
* [#2087](https://github.com/abpframework/abp/issues/2087) / RavenDB Database Support
### Application Modules / UI Themes
@ -100,7 +100,7 @@ Here, are some of the important planned features for next ABP Studio versions:
Here, are some of the important planned features for the next ABP Suite versions:
* Handle image properties for entities
* Handle image properties for entities (in addition to file properties, which is already supported)
* Allow to define extra properties for DTOs those are not a part of the entity
* Allow to create pages instead of modals for CRUD page generation
* View-only (detail view) modal/page for an entity
@ -111,7 +111,7 @@ Here, are some of the important planned features for the next ABP Suite versions
## Feature Requests
Vote for your favorite feature on the related GitHub issues (and write your thoughts). You can create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but first search in the existing issues please. You can also contact info@abp.io for your feature requests and other suggestions.
Vote for your favorite feature on the related GitHub issues (and write your thoughts). You can create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but please first search the existing issues. You can also contact [info@abp.io](mailto:info@abp.io) for your feature requests and other suggestions.
## See Also

30
docs/en/studio/installation.md

@ -5,25 +5,25 @@
## Pre-requirements
Before you begin the installation process for ABP Studio, ensure that your system meets the following pre-requirements:
ABP Studio now features automatic installation of most required dependencies. When you first launch the application, it will check for and install the following components:
### Node
Make sure [Node.js](https://nodejs.org/en) is installed on your system. If you have not installed Node.js, you can download the `v22+` version from the official [Node.js website](https://nodejs.org/en/download/prebuilt-installer).
* .NET SDK
* Node.js
* ABP CLI
* mkcert (for HTTPS development)
* WireGuard (for Kubernetes operations)
### WireGuard (Optional)
ABP Studio needs [WireGuard](https://www.wireguard.com/) for Kubernetes operations. You can find the installation instructions for your specific operating system below:
The only manual installation required is:
**For Windows:**
Installation instructions for your Windows operating system are on the official [WireGuard website](https://www.wireguard.com/install/#windows-7-81-10-11-2008r2-2012r2-2016-2019-2022).
**For macOS:**
Installation instructions for your macOS operating system are on the official [WireGuard website](https://www.wireguard.com/install/#macos-homebrew-and-macports-basic-cli-homebrew-userspace-go-homebrew-tools-macports-userspace-go-macports-tools).
### Docker
### Docker (Required for Kubernetes Operations)
ABP Studio needs [Docker](https://www.docker.com/) for [Kubernetes](https://kubernetes.io/) operations. Install Docker by following the guidelines on the official [Docker website](https://docs.docker.com/get-docker/).
### Package Manager Prerequisites
* **Windows:** The automatic installation process uses `winget`. If not already installed, ABP Studio will attempt to install it.
* **macOS:** The automatic installation process uses `brew`. If not already installed, you'll need to install it manually from [brew.sh](https://brew.sh/).
## Installation
Now you have met the pre-requirements, follow the steps below to install ABP Studio:
Follow these steps to install ABP Studio:
1. **Download ABP Studio:** Visit [abp.io](https://abp.io/studio) to download the latest version of ABP Studio.
@ -31,8 +31,10 @@ Now you have met the pre-requirements, follow the steps below to install ABP Stu
2. **Run the Installer:** Execute the installer and follow the on-screen instructions to install ABP Studio on your computer.
3. **First Launch:** When you first launch ABP Studio, it will automatically check for and install required dependencies. This process may take several minutes, and you'll see progress indicators for each component being installed.
## Login
After you install ABP Studio, you can log in to access all the features. To log in, follow the below steps:
After installation is complete, you can log in to access all features:
1. **Launch ABP Studio:** Open ABP Studio on your desktop.

15
docs/en/tutorials/microservice/part-06.md

@ -304,3 +304,18 @@ Now, the Ordering service displays the product name instead of the product ID. W
> **Design Tip**
>
> It is suggested that you keep that type of communication to a minimum and not couple your services with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering service frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Catalog service.
### Updating the Kubernetes Configuration
ABP microservice startup template provides [pre-configured Helm charts](../../solution-templates/microservice/helm-charts-and-kubernetes.md) to deploy your solution to Kubernetes. When you develop your solution, you should also care about configurations of these charts if you want to keep them up to date and working.
In the section *Generating Proxy Classes for the Integration Service* above, we added a new configuration to the `appsettings.json` file of the Ordering microservice. We should configure the corresponding Helm chart configuration to keep it synchronized.
Open the `etc\helm\cloudcrm\charts\ordering\templates\ordering.yaml` file in a text editor, and add the following lines under the `env` section, just like the other values present (be careful on indents since it is critical in YAML files):
````yaml
- name: "RemoteServices__CatalogService__BaseUrl"
value: "http://{%{{{ .Release.Name }}}%}-catalog"
````
With this simple configuration, now the Ordering module can discover the catalog microservice's URL inside your Kubernetes cluster.

7
framework/Volo.Abp.sln

@ -489,6 +489,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny.Tests", "test\Volo.Abp.BlobStoring.Bunny.Tests\Volo.Abp.BlobStoring.Bunny.Tests.csproj", "{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Timing.Tests", "test\Volo.Abp.Timing.Tests\Volo.Abp.Timing.Tests.csproj", "{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1459,6 +1461,10 @@ Global
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Release|Any CPU.Build.0 = Release|Any CPU
{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1705,6 +1711,7 @@ Global
{70720321-DED4-464F-B913-BDA5BBDD7982} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{1BBCBA72-CDB6-4882-96EE-D4CD149433A2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

3
framework/src/Volo.Abp.AspNetCore.Bundling/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
framework/src/Volo.Abp.AspNetCore.Bundling/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

32
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj

@ -1,25 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
@ -31,11 +26,4 @@
<ProjectReference Include="..\Volo.Abp.AspNetCore.Bundling\Volo.Abp.AspNetCore.Bundling.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Components.MauiBlazor\Volo.Abp.AspNetCore.Components.MauiBlazor.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Platforms\Android\" />
<Folder Include="Platforms\iOS\" />
<Folder Include="Platforms\MacCatalyst\" />
<Folder Include="Platforms\Windows\" />
</ItemGroup>
</Project>

4
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/AbpAspNetCoreComponentsMauiBlazorBundlingModule.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/AbpAspNetCoreComponentsMauiBlazorBundlingModule.cs

@ -1,4 +1,8 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

2
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/AbpBlazorWebView.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/AbpBlazorWebView.cs

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components.WebView.Maui;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.VirtualFileSystem;
using Microsoft.Maui;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;

9
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/BundleManager.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/BundleManager.cs

@ -1,6 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Maui.Storage;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Scripts;
using Volo.Abp.AspNetCore.Bundling.Styles;
@ -114,7 +119,7 @@ public class BundleManager : BundleManagerBase, ITransientDependency
#if DEBUG
return true;
#else
retur false;
return false;
#endif
}
@ -122,4 +127,4 @@ public class BundleManager : BundleManagerBase, ITransientDependency
{
return MauiBlazorContentFileProvider;
}
}
}

0
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/IMauiBlazorContentFileProvide.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/IMauiBlazorContentFileProvide.cs

0
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/MauiBlazorBundlerBase.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/MauiBlazorBundlerBase.cs

3
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/MauiBlazorContentFileProvider.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/MauiBlazorContentFileProvider.cs

@ -1,6 +1,9 @@
using System;
using System.IO;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Storage;
using Volo.Abp.DependencyInjection;
using Volo.Abp.VirtualFileSystem;

1
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Scripts/ScriptBundler.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/Scripts/ScriptBundler.cs

@ -1,3 +1,4 @@
using System;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Scripts;

2
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Styles/StyleBundler.cs → framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo/Abp/AspNetCore/Components/MauiBlazor/Bundling/Styles/StyleBundler.cs

@ -1,3 +1,5 @@
using System;
using System.IO;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Styles;

20
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/AbpMauiBlazorClientHttpMessageHandler.cs

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Components.Progression;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor;
@ -13,12 +14,15 @@ public class AbpMauiBlazorClientHttpMessageHandler : DelegatingHandler, ITransie
{
private readonly IUiPageProgressService _uiPageProgressService;
private readonly IMauiBlazorSelectedLanguageProvider _mauiBlazorSelectedLanguageProvider;
private readonly ICurrentTimezoneProvider _currentTimezoneProvider;
public AbpMauiBlazorClientHttpMessageHandler(
IClientScopeServiceProviderAccessor clientScopeServiceProviderAccessor,
IMauiBlazorSelectedLanguageProvider mauiBlazorSelectedLanguageProvider)
IMauiBlazorSelectedLanguageProvider mauiBlazorSelectedLanguageProvider,
ICurrentTimezoneProvider currentTimezoneProvider)
{
_mauiBlazorSelectedLanguageProvider = mauiBlazorSelectedLanguageProvider;
_currentTimezoneProvider = currentTimezoneProvider;
_uiPageProgressService = clientScopeServiceProviderAccessor.ServiceProvider.GetRequiredService<IUiPageProgressService>();
}
@ -32,6 +36,7 @@ public class AbpMauiBlazorClientHttpMessageHandler : DelegatingHandler, ITransie
});
await SetLanguageAsync(request);
await SetTimeZoneAsync(request);
return await base.SendAsync(request, cancellationToken);
}
@ -51,4 +56,15 @@ public class AbpMauiBlazorClientHttpMessageHandler : DelegatingHandler, ITransie
request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(selectedLanguage!));
}
}
}
private Task SetTimeZoneAsync(HttpRequestMessage request)
{
if (!_currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace())
{
request.Headers.Remove(TimeZoneConsts.DefaultTimeZoneKey);
request.Headers.Add(TimeZoneConsts.DefaultTimeZoneKey, _currentTimezoneProvider.TimeZone);
}
return Task.CompletedTask;
}
}

30
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs

@ -1,10 +1,13 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
using Volo.Abp.AspNetCore.Mvc.Client;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor
{
@ -18,17 +21,29 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor
protected ICurrentTenantAccessor CurrentTenantAccessor { get; }
protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; }
protected IJSRuntime JSRuntime { get; }
protected IClock Clock { get; }
public MauiBlazorCachedApplicationConfigurationClient(
AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy,
ApplicationConfigurationCache cache,
ICurrentTenantAccessor currentTenantAccessor,
ICurrentTimezoneProvider currentTimezoneProvider,
AuthenticationStateProvider authenticationStateProvider,
AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy)
AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy,
IJSRuntime jsRuntime,
IClock clock)
{
ApplicationConfigurationClientProxy = applicationConfigurationClientProxy;
Cache = cache;
CurrentTenantAccessor = currentTenantAccessor;
CurrentTimezoneProvider = currentTimezoneProvider;
ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
JSRuntime = jsRuntime;
Clock = clock;
authenticationStateProvider.AuthenticationStateChanged += async _ => { await InitializeAsync(); };
}
@ -57,6 +72,15 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor
CurrentTenantAccessor.Current = new BasicTenantInfo(
configurationDto.CurrentTenant.Id,
configurationDto.CurrentTenant.Name);
if (Clock.SupportsMultipleTimezone)
{
CurrentTimezoneProvider.TimeZone = !configurationDto.Timing.TimeZone.Iana.TimeZoneName.IsNullOrWhiteSpace()
? configurationDto.Timing.TimeZone.Iana.TimeZoneName
: await JSRuntime.InvokeAsync<string>("abp.clock.getBrowserTimeZone");
await JSRuntime.InvokeAsync<string>("abp.clock.setBrowserTimeZoneToCookie");
}
}
public virtual Task<ApplicationConfigurationDto> GetAsync()
@ -81,4 +105,4 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor
return configuration;
}
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs

@ -0,0 +1,10 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor;
[Dependency(ReplaceServices = true)]
public class MauiBlazorCurrentTimezoneProvider : ICurrentTimezoneProvider, ISingletonDependency
{
public string? TimeZone { get; set; }
}

10
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor

@ -5,7 +5,12 @@
{
foreach (var file in ScriptFiles)
{
<script src="@file"></script>
var src = file;
if (!AppBasePath.IsNullOrWhiteSpace())
{
src = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/");
}
<script src="@src"></script>
}
}
@ -19,6 +24,9 @@
[Parameter]
public string? BundleName { get; set; }
[Parameter]
public string? AppBasePath { get; set; }
private List<string>? ScriptFiles { get; set; }
private PersistingComponentStateSubscription _persistingSubscription;

10
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor

@ -5,7 +5,12 @@
{
foreach (var file in StyleFiles)
{
<link rel="stylesheet" href="@file" />
var href = file;
if (!AppBasePath.IsNullOrWhiteSpace())
{
href = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/");
}
<link rel="stylesheet" href="@href" />
}
}
@ -19,6 +24,9 @@
[Parameter]
public string? BundleName { get; set; }
[Parameter]
public string? AppBasePath { get; set; }
private List<string>? StyleFiles { get; set; }
private PersistingComponentStateSubscription _persistingSubscription;

19
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpBlazorClientHttpMessageHandler.cs

@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Volo.Abp.AspNetCore.Components.Progression;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Components.Web;
@ -21,6 +22,8 @@ public class AbpBlazorClientHttpMessageHandler : DelegatingHandler, ITransientDe
private readonly IUiPageProgressService _uiPageProgressService;
private readonly ICurrentTimezoneProvider _currentTimezoneProvider;
private const string AntiForgeryCookieName = "XSRF-TOKEN";
private const string AntiForgeryHeaderName = "RequestVerificationToken";
@ -29,11 +32,13 @@ public class AbpBlazorClientHttpMessageHandler : DelegatingHandler, ITransientDe
IJSRuntime jsRuntime,
ICookieService cookieService,
NavigationManager navigationManager,
IClientScopeServiceProviderAccessor clientScopeServiceProviderAccessor)
IClientScopeServiceProviderAccessor clientScopeServiceProviderAccessor,
ICurrentTimezoneProvider currentTimezoneProvider)
{
_jsRuntime = jsRuntime;
_cookieService = cookieService;
_navigationManager = navigationManager;
_currentTimezoneProvider = currentTimezoneProvider;
_uiPageProgressService = clientScopeServiceProviderAccessor.ServiceProvider.GetRequiredService<IUiPageProgressService>();
}
@ -48,6 +53,7 @@ public class AbpBlazorClientHttpMessageHandler : DelegatingHandler, ITransientDe
await SetLanguageAsync(request, cancellationToken);
await SetAntiForgeryTokenAsync(request);
await SetTimeZoneAsync(request);
return await base.SendAsync(request, cancellationToken);
}
@ -94,4 +100,15 @@ public class AbpBlazorClientHttpMessageHandler : DelegatingHandler, ITransientDe
request.Headers.Add(AntiForgeryHeaderName, token);
}
}
private Task SetTimeZoneAsync(HttpRequestMessage request)
{
if (!_currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace())
{
request.Headers.Remove(TimeZoneConsts.DefaultTimeZoneKey);
request.Headers.Add(TimeZoneConsts.DefaultTimeZoneKey, _currentTimezoneProvider.TimeZone);
}
return Task.CompletedTask;
}
}

15
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/UserExceptionInformer.cs

@ -8,6 +8,7 @@ using Volo.Abp.AspNetCore.Components.Messages;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.Http.Client;
namespace Volo.Abp.AspNetCore.Components.Web.ExceptionHandling;
@ -35,6 +36,8 @@ public class UserExceptionInformer : IUserExceptionInformer, IScopedDependency
{
//TODO: Create sync versions of the MessageService APIs.
LogException(context);
var errorInfo = GetErrorInfo(context);
if (errorInfo.Details.IsNullOrEmpty())
@ -49,6 +52,8 @@ public class UserExceptionInformer : IUserExceptionInformer, IScopedDependency
public async Task InformAsync(UserExceptionInformerContext context)
{
LogException(context);
var errorInfo = GetErrorInfo(context);
if (errorInfo.Details.IsNullOrEmpty())
@ -70,4 +75,14 @@ public class UserExceptionInformer : IUserExceptionInformer, IScopedDependency
options.SendExceptionDataToClientTypes = Options.SendExceptionDataToClientTypes;
});
}
protected virtual void LogException(UserExceptionInformerContext context)
{
if (context.Exception is AbpRemoteCallException && OperatingSystem.IsBrowser())
{
return;
}
Logger.LogException(context.Exception);
}
}

10
framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/abp.js

@ -245,4 +245,14 @@ var abp = abp || {};
}
}
}
abp.clock = abp.clock || {};
abp.clock.setBrowserTimeZoneToCookie = function () {
abp.utils.setCookieValue('__timezone', Intl.DateTimeFormat().resolvedOptions().timeZone, new Date(new Date().setFullYear(new Date().getFullYear() + 1)), '/');
}
abp.clock.getBrowserTimeZone = function () {
return Intl.DateTimeFormat().resolvedOptions().timeZone
}
})();

23
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
using Volo.Abp.AspNetCore.Components.Web.Security;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
@ -6,6 +7,7 @@ using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
using Volo.Abp.AspNetCore.Mvc.Client;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Components.WebAssembly;
@ -19,24 +21,32 @@ public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicatio
protected ICurrentTenantAccessor CurrentTenantAccessor { get; }
protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; }
protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; }
protected IJSRuntime JSRuntime { get; }
protected IClock Clock { get; }
public WebAssemblyCachedApplicationConfigurationClient(
AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy,
ApplicationConfigurationCache cache,
ICurrentTenantAccessor currentTenantAccessor,
ICurrentTimezoneProvider currentTimezoneProvider,
AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy,
ApplicationConfigurationChangedService applicationConfigurationChangedService,
IJSRuntime jsRuntime)
IJSRuntime jsRuntime,
IClock clock)
{
ApplicationConfigurationClientProxy = applicationConfigurationClientProxy;
Cache = cache;
CurrentTenantAccessor = currentTenantAccessor;
CurrentTimezoneProvider = currentTimezoneProvider;
ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
ApplicationConfigurationChangedService = applicationConfigurationChangedService;
JSRuntime = jsRuntime;
Clock = clock;
}
public virtual async Task InitializeAsync()
@ -69,6 +79,15 @@ public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicatio
configurationDto.CurrentTenant.Id,
configurationDto.CurrentTenant.Name
);
if (Clock.SupportsMultipleTimezone)
{
CurrentTimezoneProvider.TimeZone = !configurationDto.Timing.TimeZone.Iana.TimeZoneName.IsNullOrWhiteSpace()
? configurationDto.Timing.TimeZone.Iana.TimeZoneName
: await JSRuntime.InvokeAsync<string>("abp.clock.getBrowserTimeZone");
await JSRuntime.InvokeAsync<string>("abp.clock.setBrowserTimeZoneToCookie");
}
}
public virtual Task<ApplicationConfigurationDto> GetAsync()

10
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs

@ -0,0 +1,10 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Components.WebAssembly;
[Dependency(ReplaceServices = true)]
public class WebAssemblyCurrentTimezoneProvider : ICurrentTimezoneProvider, ISingletonDependency
{
public string? TimeZone { get; set; }
}

3
framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpComponentBase.cs

@ -172,8 +172,7 @@ public abstract class AbpComponentBase : OwningComponentBase
{
return;
}
Logger.LogException(exception);
await InvokeAsync(async () =>
{
await UserExceptionInformer.InformAsync(new UserExceptionInformerContext(exception));

4
framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs

@ -16,7 +16,9 @@ public class AbpAspNetCoreMvcNewtonsoftModule : AbpModule
context.Services.AddOptions<MvcNewtonsoftJsonOptions>()
.Configure<IServiceProvider>((options, rootServiceProvider) =>
{
options.SerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver(rootServiceProvider.GetRequiredService<AbpDateTimeConverter>());
options.SerializerSettings.ContractResolver =
new AbpCamelCasePropertyNamesContractResolver(rootServiceProvider
.GetRequiredService<AbpDateTimeConverter>());
});
}
}

111
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs

@ -16,63 +16,28 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper>
where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper>
{
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes = new()
{
{
typeof(string), o =>
{
if(o is string s && DateTime.TryParse(s, out var dt))
{
return dt.ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime), o =>
{
if(o is DateTime dt && dt != default)
{
return dt.ToString("O");
}
return string.Empty;
}
},
{typeof(DateTime?), o => ((DateTime?) o)?.ToString("O")!},
{
typeof(DateTimeOffset), o =>
{
if(o is DateTimeOffset dto && dto != default)
{
return dto.ToString("O");
}
return string.Empty;
}
},
{typeof(DateTimeOffset?), o => ((DateTimeOffset?) o)?.ToString("O")!}
};
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes;
protected readonly IJsonSerializer JsonSerializer;
protected readonly IHtmlGenerator Generator;
protected readonly HtmlEncoder Encoder;
protected readonly IServiceProvider ServiceProvider;
protected readonly IAbpTagHelperLocalizer TagHelperLocalizer;
protected readonly IClock Clock;
protected virtual string TagName { get; set; } = "abp-date-picker";
protected IStringLocalizer<AbpUiResource> L { get; }
protected abstract TagHelperOutput TagHelperOutput { get; set; }
protected AbpDatePickerBaseTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator,
HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer)
IAbpTagHelperLocalizer tagHelperLocalizer, IClock clock)
{
JsonSerializer = jsonSerializer;
Generator = generator;
@ -80,6 +45,65 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
ServiceProvider = serviceProvider;
L = l;
TagHelperLocalizer = tagHelperLocalizer;
Clock = clock;
SupportedInputTypes = new Dictionary<Type, Func<object, string>>
{
{
typeof(string), x =>
{
if(x is string s && DateTime.TryParse(s, out var dt))
{
return Clock.ConvertToUserTime(dt).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime), x =>
{
if(x is DateTime dt && dt != default)
{
return Clock.ConvertToUserTime(dt).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime?), x =>
{
if(x is DateTime dt && dt != default)
{
return Clock.ConvertToUserTime(dt).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTimeOffset), x =>
{
if(x is DateTimeOffset dto && dto != default)
{
return Clock.ConvertToUserTime(dto).DateTime.ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTimeOffset?), x =>
{
if(x is DateTimeOffset dto && dto != default)
{
return Clock.ConvertToUserTime(dto).DateTime.ToString("O");
}
return string.Empty;
}
}
};
}
protected virtual T? GetAttribute<T>() where T : Attribute
@ -233,6 +257,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
}
protected abstract int GetOrder();
protected abstract void AddBaseTagAttributes(TagHelperAttributeList attributes);
protected virtual string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output)
@ -375,7 +400,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{
attrList.Add("data-visible-date-format", options.VisibleDateFormat);
}
if(!options.InputDateFormat.IsNullOrEmpty())
{
attrList.Add("data-input-date-format", options.InputDateFormat);
@ -754,7 +779,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{
return Task.FromResult(string.Empty);
}
return GetValidationAsHtmlByInputAsync(context, output, @for);
}
@ -766,7 +791,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext };
var attributeList = new TagHelperAttributeList { { "class", "text-danger" } };
if(!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null)
{
if (nameAttribute != null)
@ -776,7 +801,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
nameAttribute = new TagHelperAttribute("name", "date_" + Guid.NewGuid().ToString("N"));
output.Attributes.Add(nameAttribute);
}
attributeList.Add("data-valmsg-for", nameAttribute.Value);
return await validationMessageTagHelper.RenderAsync(attributeList, context, Encoder, "span",

36
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs

@ -8,14 +8,22 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<AbpDatePickerTagHelper>
{
public AbpDatePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer<AbpUiResource> l, IAbpTagHelperLocalizer tagHelperLocalizer) : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer)
public AbpDatePickerTagHelperService(
IJsonSerializer jsonSerializer,
IHtmlGenerator generator,
HtmlEncoder encoder,
IServiceProvider serviceProvider,
IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer,
IClock clock)
: base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock)
{
}
protected override TagHelperOutput TagHelperOutput { get; set; } = default!;
@ -42,10 +50,28 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<A
{
InputTypeName = "hidden",
ViewContext = TagHelper.ViewContext,
For = TagHelper.AspFor,
For = TagHelper.AspFor
};
var attributes = new TagHelperAttributeList { { "data-date", "true" }, { "type", "hidden" } };
var attributes = new TagHelperAttributeList { { "data-hidden-datepicker", "true" }, { "data-date", "true" }, { "type", "hidden" } };
if (Clock.SupportsMultipleTimezone)
{
if (TagHelper.AspFor.Model is DateTime dateTime)
{
DateTagHelper.Format = "{0:O}";
DateTagHelper.Value = Clock.ConvertToUserTime(dateTime).ToString("O");
attributes.Add("value", DateTagHelper.Value);
}
if (TagHelper.AspFor.Model is DateTimeOffset dateTimeOffset)
{
DateTagHelper.Format = "{0:O}";
DateTagHelper.Value = Clock.ConvertToUserTime(dateTimeOffset).UtcDateTime.ToString("O");
attributes.Add("value", DateTagHelper.Value);
}
}
DateTagHelperOutput = await DateTagHelper.ProcessAndGetOutputAsync(attributes, context, "input");
}
@ -80,4 +106,4 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<A
{
return DateTagHelperOutput?.Render(Encoder)!;
}
}
}

58
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs

@ -9,16 +9,20 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperService<AbpDateRangePickerTagHelper>
{
public AbpDateRangePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator,
HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer) :
base(jsonSerializer, generator, encoder, serviceProvider, l,
tagHelperLocalizer)
public AbpDateRangePickerTagHelperService(
IJsonSerializer jsonSerializer, IHtmlGenerator generator,
HtmlEncoder encoder,
IServiceProvider serviceProvider,
IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer,
IClock clock) :
base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock)
{
}
@ -34,7 +38,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
{
if (TagHelper.AspForStart != null)
{
var startDateAttributes = new TagHelperAttributeList { { "data-start-date", "true" }, { "type", "hidden" } };
var startDateAttributes = new TagHelperAttributeList { { "data-hidden-datepicker", "true" }, { "data-start-date", "true" }, { "type", "hidden" } };
StartDateTagHelper = new InputTagHelper(Generator)
{
ViewContext = TagHelper.ViewContext,
@ -42,12 +46,29 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
InputTypeName = "hidden"
};
if (Clock.SupportsMultipleTimezone)
{
if (TagHelper.AspForStart.Model is DateTime dateTime)
{
StartDateTagHelper.Format = "{0:O}";
StartDateTagHelper.Value = Clock.ConvertToUserTime(dateTime).ToString("O");
startDateAttributes.Add("value", StartDateTagHelper.Value);
}
if (TagHelper.AspForStart.Model is DateTimeOffset dateTimeOffset)
{
StartDateTagHelper.Format = "{0:O}";
StartDateTagHelper.Value = Clock.ConvertToUserTime(dateTimeOffset).UtcDateTime.ToString("O");
startDateAttributes.Add("value", StartDateTagHelper.Value);
}
}
StartDateTagHelperOutput = await StartDateTagHelper.ProcessAndGetOutputAsync(startDateAttributes, context, "input");
}
if (TagHelper.AspForEnd != null)
{
var endDateAttributes = new TagHelperAttributeList { { "data-end-date", "true" }, { "type", "hidden" } };
var endDateAttributes = new TagHelperAttributeList { { "data-hidden-datepicker", "true" }, { "data-end-date", "true" }, { "type", "hidden" } };
EndDateTagHelper = new InputTagHelper(Generator)
{
ViewContext = TagHelper.ViewContext,
@ -55,6 +76,23 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
InputTypeName = "hidden"
};
if (Clock.SupportsMultipleTimezone)
{
if (TagHelper.AspForEnd.Model is DateTime dateTime)
{
EndDateTagHelper.Format = "{0:O}";
EndDateTagHelper.Value = Clock.ConvertToUserTime(dateTime).ToString("O");
endDateAttributes.Add("value", EndDateTagHelper.Value);
}
if (TagHelper.AspForEnd.Model is DateTimeOffset dateTimeOffset)
{
EndDateTagHelper.Format = "{0:O}";
EndDateTagHelper.Value = Clock.ConvertToUserTime(dateTimeOffset).UtcDateTime.ToString("O");
endDateAttributes.Add("value", EndDateTagHelper.Value);
}
}
EndDateTagHelperOutput = await EndDateTagHelper.ProcessAndGetOutputAsync(endDateAttributes, context, "input");
}
@ -78,7 +116,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
protected override int GetOrder()
{
return TagHelper.Order;
return TagHelper.AspForStart?.Metadata.Order ?? 0;
}
protected override void AddBaseTagAttributes(TagHelperAttributeList attributes)
@ -92,7 +130,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
attributes.Add("data-start-date", convert);
}
}
if (TagHelper.AspForEnd?.Model != null &&
SupportedInputTypes.TryGetValue(TagHelper.AspForEnd.Metadata.ModelType, out var convertFuncEnd))
{
@ -113,4 +151,4 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
{
return TagHelper.AspForStart ?? TagHelper.AspForEnd;
}
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js

@ -110,7 +110,7 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f
_onOpenCallbacks.triggerAll(_publicApi);
if ($firstVisibleInput.data("datepicker")) {
if ($firstVisibleInput.data("datepicker") || $firstVisibleInput.data("daterangepicker")) {
return; //don't pop-up date pickers...
}

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

Loading…
Cancel
Save