Browse Source

Merge branch 'abpframework:dev' into dev

pull/22561/head
David Simões 2 years ago
committed by GitHub
parent
commit
2c1eacba05
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      .github/workflows/angular.yml
  2. 6
      Directory.Packages.props
  3. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  5. 2
      docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md
  6. 2
      docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md
  7. 2
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md
  8. 2
      docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md
  9. 2
      docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md
  10. 2
      docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md
  11. 2
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md
  12. 2
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md
  13. 2
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md
  14. 46
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md
  15. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/after.png
  16. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/before.png
  17. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/configure-timezone.png
  18. 2
      docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md
  19. 2
      docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md
  20. 2
      docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md
  21. 2
      docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md
  22. 2
      docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md
  23. 2
      docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md
  24. 2
      docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md
  25. 2
      docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md
  26. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list-utc1.png
  27. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list-utc3.png
  28. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/auth-list.png
  29. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/berlin.png
  30. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-create.png
  31. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-edit.png
  32. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/blazor-list.png
  33. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-create.png
  34. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-edit.png
  35. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list-utc1.png
  36. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list-utc3.png
  37. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-list.png
  38. BIN
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/mvc-post.png
  39. 781
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md
  40. 1
      docs/en/cli/index.md
  41. 6
      docs/en/framework/infrastructure/event-bus/distributed/index.md
  42. 71
      docs/en/framework/infrastructure/timing.md
  43. BIN
      docs/en/images/account-pro-time-zone-setting.png
  44. BIN
      docs/en/images/setting-management-email-ui.png
  45. BIN
      docs/en/images/setting-management-feature-management-ui.png
  46. BIN
      docs/en/images/setting-management-time-zone-ui.png
  47. 6
      docs/en/modules/account-pro.md
  48. 6
      docs/en/modules/setting-management.md
  49. 10
      docs/en/release-info/release-notes.md
  50. 15
      docs/en/tutorials/microservice/part-06.md
  51. 7
      framework/Volo.Abp.sln
  52. 20
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/AbpMauiBlazorClientHttpMessageHandler.cs
  53. 30
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs
  54. 10
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs
  55. 19
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpBlazorClientHttpMessageHandler.cs
  56. 15
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/UserExceptionInformer.cs
  57. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/abp.js
  58. 5
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/Extensions/DependencyInjection/AbpBlazorWebAppServiceCollectionExtensions.cs
  59. 15
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebApp/RemoteAuthenticationStateProvider.cs
  60. 8
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebApp/RemoteAuthenticationStateProviderCompatible.cs
  61. 27
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
  62. 10
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs
  63. 3
      framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpComponentBase.cs
  64. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs
  65. 111
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  66. 36
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs
  67. 58
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs
  68. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js
  69. 21
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  70. 39
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js
  71. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  72. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  73. 20
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs
  74. 27
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  75. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs
  76. 13
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs
  77. 82
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs
  78. 2
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpClaimsMapMiddleware.cs
  79. 63
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  80. 7
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs
  81. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs
  82. 22
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
  83. 43
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  84. 47
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs
  85. 13
      framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs
  86. 3
      framework/src/Volo.Abp.Ddd.Domain.Shared/Volo/Abp/Domain/Entities/Events/Distributed/AbpDistributedEntityEventOptions.cs
  87. 10
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs
  88. 7
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreModule.cs
  89. 6
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpRabbitMqEventBusOptions.cs
  90. 44
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  91. 8
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs
  92. 28
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs
  93. 21
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs
  94. 22
      framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs
  95. 3
      framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs
  96. 3
      framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs
  97. 13
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs
  98. 22
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs
  99. 22
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs
  100. 13
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpGuidCustomBsonTypeMapper.cs

4
.github/workflows/angular.yml

@ -27,12 +27,12 @@ jobs:
with:
fetch-depth: 0
- uses: actions/cache@v2
- uses: actions/cache@v4
with:
path: 'npm/ng-packs/node_modules'
key: ${{ runner.os }}-${{ hashFiles('npm/ng-packs/yarn.lock') }}
- uses: actions/cache@v2
- uses: actions/cache@v4
with:
path: 'templates/app/angular/node_modules'
key: ${{ runner.os }}-${{ hashFiles('templates/app/angular/yarn.lock') }}

6
Directory.Packages.props

@ -119,7 +119,7 @@
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.6.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.6.0" />
<PackageVersion Include="Minio" Version="6.0.3" />
<PackageVersion Include="MongoDB.Driver" Version="3.1.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.3.0" />
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Nito.AsyncEx.Context" Version="5.1.2" />
@ -142,7 +142,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" />
@ -177,7 +177,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" />

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

@ -690,6 +690,13 @@
"Permission:Campaigns": "Campaigns",
"Permission:Licenses": "License Settings",
"BlockUserPolicy": "User Block Policies",
"Permission:BlockUserPolicy": "User Block Policy"
"Permission:BlockUserPolicy": "User Block Policy",
"ManageImages" : "Manage Image",
"ModuleMainImage": "Module Main Image",
"ModuleImageInfo1": "Accepted file types : <strong> JPEG, JPG, PNG </strong>",
"ModuleImageInfo2": "Max file size : <strong> 1 MB </strong>",
"ModuleImageInfo3": "Image Proportion : <strong> 1:1 </strong>",
"ModuleImageInfo4": "<a href='/images/example-module-img.png' download> Download a sample cover image </a>",
"AreYouSureToDeleteImage": "Are you sure you want to delete this image?"
}
}

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

@ -1887,6 +1887,8 @@
"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>.",
"BiographyContainsUrlValidationMessage": "Biography cannot contain URL."
"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"
}
}

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

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

@ -25,6 +25,7 @@ Here is a brief list of titles explained in the next sections:
* Docs Module: Added "Alternative Words" to Filter Items
* Introducing the Bunny BLOB Storage Provider
* Upgraded `MongoDB.Driver` to v3.1.0
* Using Timezone Settings to Display Datetime
* Identity Pro Module: Require Email Verification to Register
* Switching users during OAuth login
@ -114,6 +115,49 @@ The upgrade provides:
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.
### Using Timezone Settings to Display Datetime
A significant enhancement in ABP v9.2 is the ability to use timezone settings to display `DateTime` values according to the user's or application's configured timezone. Introduced in `v9.2.0-rc.2`, this feature addresses the common challenge of ensuring users see accurate time information, regardless of their geographical location.
Previously, `DateTime` values were often shown in the server's timezone or in UTC, which could cause confusion for users in different timezones. With this new feature, ABP applications can now respect the configured timezone and automatically convert and display datetime values accordingly.
**Before setting the timezone:**
Consider a scenario where you have a list of books, and the `CreationTime` property is displayed without any timezone consideration. It may appear in the server's default timezone:
![before.png](before.png)
*(Screenshot of a Books page showing the CreationTime in a default timezone.)*
**Setting the timezone:**
To set the timezone, start by configuring the `AbpClockOptions` in your module's `ConfigureServices` method:
```csharp
Configure<AbpClockOptions>(options =>
{
options.Kind = DateTimeKind.Utc;
});
```
> By setting the `Kind` property of `AbpClockOptions` to `DateTimeKind.Utc`, ABP will normalize all datetime values. Times stored in the database and returned to the frontend will be in UTC. Additionally, the `SupportsMultipleTimezone` property of the `IClock` service will be **true**, and you’ll be able to configure the timezone from the UI under the _Settings_ page.
After setting the `Kind` property, you can run your application and configure the timezone from the UI under the _Settings_ page. For example, set the timezone to "Asia/Tokyo (+09:00)":
![configure-timezone.png](configure-timezone.png)
*(Screenshot showing the configuration setting for Abp.Timing.TimeZone, set to "Asia/Tokyo (+09:00)".)*
> ABP provides the `Abp.Timing.TimeZone` setting, which allows you to configure the desired timezone at the application, tenant, or user level and this setting can be configured in the UI under the _Settings_ page, inside of the _Time Zone_ tab.
**After setting the timezone:**
Once the timezone is configured, ABP automatically handles the conversion and display of `DateTime` values. The `CreationTime` on the _Books_ page will now be shown in the **"Asia/Tokyo"** timezone.
![after.png](after.png)
*(Screenshot of the _Books_ page after setting the timezone, showing the `CreationTime` adjusted to the "Asia/Tokyo" timezone.)*
This feature utilizes the `IClock` service, which provides methods like `ConvertToUserTime` and `ConvertToUtc` to facilitate timezone conversions. By configuring the `Abp.Timing.TimeZone` setting, developers can ensure a consistent and user-friendly experience across applications with a global user base.
> For a more detailed guide on implementing and using this feature, refer to the article: [Developing a Multi-Timezone Application Using the ABP Framework](https://abp.io/community/articles/developing-a-multitimezone-application-using-the-abp-framework-zk7fnrdq). It offers step-by-step instructions and examples for handling multi-timezone scenarios in ABP applications.
### 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.
@ -142,7 +186,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [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/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/Blog-Posts/2025-03-27 v9_2_Preview/after.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/en/Blog-Posts/2025-03-27 v9_2_Preview/configure-timezone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 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.

1
docs/en/cli/index.md

@ -342,6 +342,7 @@ Note that this command can upgrade your solution from a previous version, and al
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
* `--check-all`: Check the new version of each package separately. Default is `false`.
* `--version` or `-v`: Specifies the version to use for update. If not specified, latest version is used.
* * `--leptonx-version` or `-lv`: Specifies the LeptonX version to use for update. If not specified, latest version or the version that is compatible with `--version` argument is used.
### clean

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.

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

@ -88,6 +88,61 @@ 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:
@ -101,10 +156,24 @@ 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 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.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 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

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

@ -1,8 +1,12 @@
# Release Notes
This document contains **brief release notes** for each release. Release notes only include **major features** and **visible enhancements**. They don't include all the development done in the related version. To see raw and detailed change logs for every release, please check <a href="https://github.com/abpframework/abp/milestones?state=closed" target="_blank">the related milestone</a> and [the change logs page](https://abp.io/pro-releases) (only for paid license holders).
This document contains **brief release notes** for each release. Release notes only include **major features** and **visible enhancements**. They don't include all the development done in the related version.
Also see the following notes about ABP releases:
* [ABP Studio release notes](../studio/release-notes.md)
* [Change logs for ABP pro packages](https://abp.io/pro-releases)
> 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)
@ -15,7 +19,7 @@ This is currently a RC (release-candidate) and you can see the detailed **[blog
* Identity Pro Module: Require Email Verification to Register
* Switching users during OAuth login
## 9.1 (2025-01-16)
## 9.1 (2025-03-05)
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.

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}

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; }
}

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
}
})();

5
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/Extensions/DependencyInjection/AbpBlazorWebAppServiceCollectionExtensions.cs

@ -1,3 +1,4 @@
using System;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection.Extensions;
@ -19,10 +20,14 @@ public static class AbpBlazorWebAppServiceCollectionExtensions
return services;
}
[Obsolete("Use AddBlazorWebAppServices instead. See https://github.com/abpframework/abp/issues/22622")]
public static IServiceCollection AddBlazorWebAppTieredServices([NotNull] this IServiceCollection services)
{
Check.NotNull(services, nameof(services));
// Compatibility with old template code
services.AddTransient<AddBlazorWebAppTieredServicesHasBeenCalled>();
services.AddScoped<AuthenticationStateProvider, RemoteAuthenticationStateProvider>();
services.Replace(ServiceDescriptor.Singleton<IAbpAccessTokenProvider, PersistentComponentStateAbpAccessTokenProvider>());

15
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebApp/RemoteAuthenticationStateProvider.cs

@ -1,5 +1,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.WebApp;
@ -8,21 +10,26 @@ public class RemoteAuthenticationStateProvider : AuthenticationStateProvider
{
protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; }
protected WebAssemblyCachedApplicationConfigurationClient WebAssemblyCachedApplicationConfigurationClient { get; }
protected IServiceProvider ServiceProvider { get; }
public RemoteAuthenticationStateProvider(
ICurrentPrincipalAccessor currentPrincipalAccessor,
WebAssemblyCachedApplicationConfigurationClient webAssemblyCachedApplicationConfigurationClient)
WebAssemblyCachedApplicationConfigurationClient webAssemblyCachedApplicationConfigurationClient,
IServiceProvider serviceProvider)
{
CurrentPrincipalAccessor = currentPrincipalAccessor;
WebAssemblyCachedApplicationConfigurationClient = webAssemblyCachedApplicationConfigurationClient;
ServiceProvider = serviceProvider;
}
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (CurrentPrincipalAccessor.Principal.Identity == null ||
!CurrentPrincipalAccessor.Principal.Identity.IsAuthenticated)
if (ServiceProvider.GetService<AddBlazorWebAppTieredServicesHasBeenCalled>() != null)
{
await WebAssemblyCachedApplicationConfigurationClient.InitializeAsync();
if (CurrentPrincipalAccessor.Principal.Identity == null || !CurrentPrincipalAccessor.Principal.Identity.IsAuthenticated)
{
await WebAssemblyCachedApplicationConfigurationClient.InitializeAsync();
}
}
return new AuthenticationState(CurrentPrincipalAccessor.Principal);

8
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebApp/RemoteAuthenticationStateProviderCompatible.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.AspNetCore.Components.WebAssembly.WebApp;
/// <summary>
/// This class is used to indicate that the AddBlazorWebAppTieredServices method has been called for compatibility with the old template code
/// </summary>
internal sealed class AddBlazorWebAppTieredServicesHasBeenCalled
{
}

27
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()
@ -63,12 +73,21 @@ public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicatio
await JSRuntime.InvokeVoidAsync("abp.utils.removeOidcUser");
}
ApplicationConfigurationChangedService.NotifyChanged();
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");
}
ApplicationConfigurationChangedService.NotifyChanged();
}
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...
}

21
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js

@ -511,22 +511,19 @@ var abp = abp || {};
}
};
var ISOStringToDateTimeLocaleString = function (format) {
return function (data) {
var date = luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
});
return format ? date.toLocaleString(format) : date.toLocaleString();
};
datatables.defaultRenderers['date'] = function (value) {
if (!value) {
return value;
} else {
return abp.clock.normalizeToLocaleString(value, { year: 'numeric', month: '2-digit', day: '2-digit' });
}
};
datatables.defaultRenderers['date'] = function (value) {
datatables.defaultRenderers['time'] = function (value) {
if (!value) {
return value;
} else {
return (ISOStringToDateTimeLocaleString())(value);
return abp.clock.normalizeToLocaleString(value, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
};
@ -534,7 +531,7 @@ var abp = abp || {};
if (!value) {
return value;
} else {
return (ISOStringToDateTimeLocaleString(luxon.DateTime.DATETIME_SHORT))(value);
return abp.clock.normalizeToLocaleString(value);
}
};

39
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js

@ -752,4 +752,41 @@
$(function () {
abp.dom.initializers.initializeDateRangePickers($('body'));
});
})(jQuery);
$.fn.handleDatepicker = function (datepickerSelector) {
var $this = $(this);
var datepickers = $this.find(datepickerSelector);
$this.find('input[class~="hidden-datepicker"]').remove();
datepickers.each(function () {
var $this = $(this);
var datepicker = $this.data('daterangepicker');
if (!datepicker) {
return;
}
if ($this.val() === '') {
return;
}
var name = $this.attr('name') || $this.data('name');
$this.data('name', name).removeAttr('name');
if (datepicker.singleDatePicker) {
var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate());
var startDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker');
$this.after(startDateInput);
} else {
if ($this.data('start-date')) {
var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate());
var startDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker');
$this.after(startDateInput);
}
if ($this.data('end-date')) {
var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate());
var endDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker');
$this.after(endDateInput);
}
}
});
return this;
};
})(jQuery);

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs

@ -16,6 +16,8 @@ public class AbpAspNetCoreMvcOptions
public bool ExposeIntegrationServices { get; set; } = false;
public bool ExposeClientProxyServices { get; set; } = false;
public bool AutoModelValidation { get; set; }
public bool EnableRazorRuntimeCompilationOnDevelopment { get; set; }

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -311,7 +311,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
protected virtual async Task<TimingDto> GetTimingConfigAsync()
{
var windowsTimeZoneId = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone);
var timeZone = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone);
return new TimingDto
{
@ -319,13 +319,11 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
{
Windows = new WindowsTimeZone
{
TimeZoneId = windowsTimeZoneId
TimeZoneId = timeZone.IsNullOrWhiteSpace() ? null : _timezoneProvider.IanaToWindows(timeZone)
},
Iana = new IanaTimeZone
{
TimeZoneName = windowsTimeZoneId.IsNullOrWhiteSpace()
? null
: _timezoneProvider.WindowsToIana(windowsTimeZoneId!)
TimeZoneName = timeZone
}
}
};

20
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -99,6 +100,7 @@ public class AbpRemoteStreamContentModelBinder<TRemoteStreamContent> : IModelBin
{
var form = await request.ReadFormAsync();
var useMemoryStream = form.Files.Count > 1;
foreach (var file in form.Files)
{
// If there is an <input type="file" ... /> in the form and is left blank.
@ -109,13 +111,27 @@ public class AbpRemoteStreamContentModelBinder<TRemoteStreamContent> : IModelBin
if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase))
{
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length).As<TRemoteStreamContent>());
if (useMemoryStream)
{
var memoryStream = new MemoryStream();
await file.OpenReadStream().CopyToAsync(memoryStream);
memoryStream.Position = 0;
postedFiles.Add(new RemoteStreamContent(memoryStream, file.FileName, file.ContentType, file.Length, disposeStream: false).As<TRemoteStreamContent>());
bindingContext.HttpContext.Response.OnCompleted(async () =>
{
await memoryStream.DisposeAsync();
});
}
else
{
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length, disposeStream: false).As<TRemoteStreamContent>());
}
}
}
}
else if (bindingContext.IsTopLevelObject)
{
postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength).As<TRemoteStreamContent>());
postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength, disposeStream: false).As<TRemoteStreamContent>());
}
}

27
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -74,16 +74,29 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
protected virtual void RemoveIntegrationControllersIfNotExposed(ApplicationModel application)
{
if (Options.ExposeIntegrationServices)
if (!Options.ExposeIntegrationServices)
{
return;
var integrationControllers = GetControllers(application)
.Where(c => IntegrationServiceAttribute.IsDefinedOrInherited(c.ControllerType))
.ToArray();
application.Controllers.RemoveAll(integrationControllers);
}
if (!Options.ExposeClientProxyServices)
{
var clientProxyServiceControllers = GetControllers(application)
.Where(c => IsClientProxyService(c.ControllerType))
.ToArray();
application.Controllers.RemoveAll(clientProxyServiceControllers);
}
var integrationControllers = GetControllers(application)
.Where(c => IntegrationServiceAttribute.IsDefinedOrInherited(c.ControllerType))
.ToArray();
}
application.Controllers.RemoveAll(integrationControllers);
protected virtual bool IsClientProxyService(Type controllerType)
{
return typeof(IApplicationService).IsAssignableFrom(controllerType) &&
controllerType.GetBaseClasses().Any(x => x.IsGenericType && x.GetGenericTypeDefinition().FullName!.StartsWith("Volo.Abp.Http.Client.ClientProxying.ClientProxyBase"));
}
protected virtual IList<ControllerModel> GetControllers(ApplicationModel application)

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs

@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Json.SystemTextJson.JsonConverters;
using Volo.Abp.Json.SystemTextJson.Modifiers;
namespace Volo.Abp.AspNetCore.Mvc.Json;
@ -26,6 +27,13 @@ public static class MvcCoreBuilderExtensions
options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(rootServiceProvider
.GetRequiredService<IOptions<AbpSystemTextJsonSerializerModifiersOptions>>());
var dateTimeConverter = rootServiceProvider.GetRequiredService<AbpDateTimeConverter>();
var nullableDateTimeConverter = rootServiceProvider.GetRequiredService<AbpNullableDateTimeConverter>();
options.JsonSerializerOptions.TypeInfoResolver.As<AbpDefaultJsonTypeInfoResolver>().Modifiers.Add(
new AbpDateTimeConverterModifier(dateTimeConverter, nullableDateTimeConverter)
.CreateModifyAction());
});
return builder;

13
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.RequestLocalization;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticAssets;
using Microsoft.AspNetCore.Timing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
@ -114,7 +115,7 @@ public static class AbpApplicationBuilderExtensions
return app.UseMiddleware<AbpExceptionHandlingMiddleware>();
}
[Obsolete("Replace with AbpClaimsTransformation")]
[Obsolete("Use the TransformAbpClaims extension method from IServiceCollection instead.")]
public static IApplicationBuilder UseAbpClaimsMap(this IApplicationBuilder app)
{
return app.UseMiddleware<AbpClaimsMapMiddleware>();
@ -269,4 +270,14 @@ public static class AbpApplicationBuilderExtensions
return app;
}
/// <summary>
/// Use this middleware after <see cref="UseMultiTenancy" /> middleware.
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseAbpTimeZone(this IApplicationBuilder app)
{
return app.UseMiddleware<AbpTimeZoneMiddleware>();
}
}

82
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs

@ -0,0 +1,82 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Middleware;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
using Volo.Abp.Users;
namespace Microsoft.AspNetCore.Timing;
public class AbpTimeZoneMiddleware : AbpMiddlewareBase, ITransientDependency
{
public async override Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (!context.RequestServices.GetRequiredService<IClock>().SupportsMultipleTimezone)
{
await next(context);
return;
}
// Try to get the timezone from the setting system first
var settingProvider = context.RequestServices.GetRequiredService<ISettingProvider>();
var timezone = await settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone);
if (timezone.IsNullOrWhiteSpace())
{
// Try to get the timezone from the HTTP request if the setting is not available
timezone = await GetTimezoneFromRequestAsync(context);
}
if (timezone.IsNullOrWhiteSpace())
{
// Try to get the timezone from the current running server if the setting and request are not available
timezone = await GetLocalTimeZoneAsync();
}
var currentTimezoneProvider = context.RequestServices.GetRequiredService<ICurrentTimezoneProvider>();
using (currentTimezoneProvider.Change(timezone))
{
await next(context);
}
}
protected virtual async Task<string? > GetTimezoneFromRequestAsync(HttpContext context)
{
var timeZoneSources = new Func<HttpContext, Task<string?>>[]
{
ctx => Task.FromResult(ctx.Request.Headers[TimeZoneConsts.DefaultTimeZoneKey].FirstOrDefault()),
ctx => Task.FromResult<string?>(ctx.Request.Query[TimeZoneConsts.DefaultTimeZoneKey].ToString()),
async ctx => ctx.Request.HasFormContentType
? (await ctx.Request.ReadFormAsync())[TimeZoneConsts.DefaultTimeZoneKey].ToString()
: null,
ctx => Task.FromResult(ctx.Request.Cookies[TimeZoneConsts.DefaultTimeZoneKey]?.ToString()),
};
foreach (var source in timeZoneSources)
{
var timezone = await source(context);
if (!string.IsNullOrEmpty(timezone))
{
return timezone;
}
}
return null;
}
protected virtual Task<string?> GetLocalTimeZoneAsync()
{
if (TimeZoneInfo.Local.HasIanaId)
{
return Task.FromResult<string?>(TimeZoneInfo.Local.Id);
}
return TimeZoneInfo.TryConvertWindowsIdToIanaId(TimeZoneInfo.Local.Id, out var ianaName)
? Task.FromResult<string?>(ianaName)
: Task.FromResult<string?>(null);
}
}

2
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpClaimsMapMiddleware.cs

@ -11,7 +11,7 @@ using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Security.Claims;
[Obsolete("Replace with AbpClaimsTransformation")]
[Obsolete("Use the TransformAbpClaims extension method from IServiceCollection instead.")]
public class AbpClaimsMapMiddleware : AbpMiddlewareBase, ITransientDependency
{
public async override Task InvokeAsync(HttpContext context, RequestDelegate next)

63
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@ -120,46 +121,44 @@ public class JobQueue<TArgs> : IJobQueue<TArgs>
ChannelAccessor?.Dispose();
}
protected virtual Task EnsureInitializedAsync()
protected virtual async Task EnsureInitializedAsync()
{
if (ChannelAccessor != null && ChannelAccessor.Channel.IsOpen)
{
return Task.CompletedTask;
return;
}
ChannelAccessor = ChannelPool.Acquire(
ChannelAccessor = await ChannelPool.AcquireAsync(
ChannelPrefix + QueueConfiguration.QueueName,
QueueConfiguration.ConnectionName
);
var result = QueueConfiguration.Declare(ChannelAccessor.Channel);
var result = await QueueConfiguration.DeclareAsync(ChannelAccessor.Channel);
Logger.LogDebug($"RabbitMQ Queue '{QueueConfiguration.QueueName}' has {result.MessageCount} messages and {result.ConsumerCount} consumers.");
// Declare delayed queue
QueueConfiguration.DeclareDelayed(ChannelAccessor.Channel);
await QueueConfiguration.DeclareDelayedAsync(ChannelAccessor.Channel);
if (AbpBackgroundJobOptions.IsJobExecutionEnabled)
{
if (QueueConfiguration.PrefetchCount.HasValue)
{
ChannelAccessor.Channel.BasicQos(0, QueueConfiguration.PrefetchCount.Value, false);
await ChannelAccessor.Channel.BasicQosAsync(0, QueueConfiguration.PrefetchCount.Value, false);
}
Consumer = new AsyncEventingBasicConsumer(ChannelAccessor.Channel);
Consumer.Received += MessageReceived;
Consumer.ReceivedAsync += MessageReceived;
//TODO: What BasicConsume returns?
ChannelAccessor.Channel.BasicConsume(
await ChannelAccessor.Channel.BasicConsumeAsync(
queue: QueueConfiguration.QueueName,
autoAck: false,
consumer: Consumer
);
}
return Task.CompletedTask;
}
protected virtual Task PublishAsync(
protected virtual async Task PublishAsync(
TArgs args,
BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
@ -167,29 +166,27 @@ public class JobQueue<TArgs> : IJobQueue<TArgs>
//TODO: How to handle priority
var routingKey = QueueConfiguration.QueueName;
var basicProperties = CreateBasicPropertiesToPublish();
var basicProperties = new BasicProperties
{
Persistent = true
};
if (delay.HasValue)
{
routingKey = QueueConfiguration.DelayedQueueName;
basicProperties.Expiration = delay.Value.TotalMilliseconds.ToString();
basicProperties.Expiration = delay.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
}
ChannelAccessor!.Channel.BasicPublish(
exchange: "",
routingKey: routingKey,
basicProperties: basicProperties,
body: Serializer.Serialize(args!)
);
return Task.CompletedTask;
}
protected virtual IBasicProperties CreateBasicPropertiesToPublish()
{
var properties = ChannelAccessor!.Channel.CreateBasicProperties();
properties.Persistent = true;
return properties;
if (ChannelAccessor != null)
{
await ChannelAccessor.Channel.BasicPublishAsync(
exchange: "",
routingKey: routingKey,
mandatory: false,
basicProperties: basicProperties,
body: Serializer.Serialize(args!)
);
}
}
protected virtual async Task MessageReceived(object sender, BasicDeliverEventArgs ea)
@ -205,17 +202,17 @@ public class JobQueue<TArgs> : IJobQueue<TArgs>
try
{
await JobExecuter.ExecuteAsync(context);
ChannelAccessor!.Channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
await ChannelAccessor!.Channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (BackgroundJobExecutionException)
{
//TODO: Reject like that?
ChannelAccessor!.Channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: true);
await ChannelAccessor!.Channel.BasicRejectAsync(deliveryTag: ea.DeliveryTag, requeue: true);
}
catch (Exception)
{
//TODO: Reject like that?
ChannelAccessor!.Channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: false);
await ChannelAccessor!.Channel.BasicRejectAsync(deliveryTag: ea.DeliveryTag, requeue: false);
}
}
}

7
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using RabbitMQ.Client;
using Volo.Abp.RabbitMQ;
@ -34,15 +35,15 @@ public class JobQueueConfiguration : QueueDeclareConfiguration
DelayedQueueName = delayedQueueName;
}
public virtual QueueDeclareOk DeclareDelayed(IModel channel)
public virtual async Task<QueueDeclareOk> DeclareDelayedAsync(IChannel channel)
{
var delayedArguments = new Dictionary<string, object>(Arguments)
var delayedArguments = new Dictionary<string, object?>(Arguments)
{
["x-dead-letter-routing-key"] = QueueName,
["x-dead-letter-exchange"] = string.Empty
};
return channel.QueueDeclare(
return await channel.QueueDeclareAsync(
queue: DelayedQueueName,
durable: Durable,
exclusive: Exclusive,

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs

@ -68,7 +68,7 @@ public class HelpCommand : IConsoleCommand, ITransientDependency
sb.AppendLine("Command List:");
sb.AppendLine("");
foreach (var command in AbpCliOptions.Commands.ToArray().Where(NotHiddenFromCommandList))
foreach (var command in AbpCliOptions.Commands.ToArray().Where(NotHiddenFromCommandList).OrderBy(x => x.Key))
{
var method = command.Value.GetMethod("GetShortDescription", BindingFlags.Static | BindingFlags.Public);
if (method == null)

22
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs

@ -38,24 +38,25 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency
var directory = commandLineArgs.Options.GetOrNull(Options.SolutionPath.Short, Options.SolutionPath.Long) ??
Directory.GetCurrentDirectory();
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long);
var leptonXVersion = commandLineArgs.Options.GetOrNull(Options.LeptonXVersion.Short, Options.LeptonXVersion.Long);
if (updateNuget || !updateNpm)
{
await UpdateNugetPackages(commandLineArgs, directory, version);
await UpdateNugetPackages(commandLineArgs, directory, version, leptonXVersion);
}
if (updateNpm || !updateNuget)
{
await UpdateNpmPackages(directory, version);
await UpdateNpmPackages(directory, version, leptonXVersion);
}
}
private async Task UpdateNpmPackages(string directory, string version)
private async Task UpdateNpmPackages(string directory, string version, string leptonXVersion)
{
await _npmPackagesUpdater.Update(directory, version: version);
await _npmPackagesUpdater.Update(directory, version: version, leptonXVersion: leptonXVersion);
}
private async Task UpdateNugetPackages(CommandLineArgs commandLineArgs, string directory, string version)
private async Task UpdateNugetPackages(CommandLineArgs commandLineArgs, string directory, string version, string leptonXVersion)
{
var solutions = new List<string>();
var givenSolution = commandLineArgs.Options.GetOrNull(Options.SolutionName.Short, Options.SolutionName.Long);
@ -77,7 +78,7 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency
{
var solutionName = Path.GetFileName(solution).RemovePostFix(".sln");
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(solution, checkAll: checkAll, version: version);
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(solution, checkAll: checkAll, version: version, leptonXVersion: leptonXVersion);
Logger.LogInformation("Volo packages are updated in {SolutionName} solution", solutionName);
}
@ -90,7 +91,7 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency
{
var projectName = Path.GetFileName(project).RemovePostFix(".csproj");
await _nugetPackagesVersionUpdater.UpdateProjectAsync(project, checkAll: checkAll, version: version);
await _nugetPackagesVersionUpdater.UpdateProjectAsync(project, checkAll: checkAll, version: version, leptonXVersion: leptonXVersion);
Logger.LogInformation("Volo packages are updated in {ProjectName} project", projectName);
return;
@ -120,6 +121,7 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency
sb.AppendLine("-sn|--solution-name (Specify the solution name)");
sb.AppendLine("--check-all (Check the new version of each package separately)");
sb.AppendLine("-v|--version <version> (default: latest version)");
sb.AppendLine("-lv|--leptonx-version <version> (default: latest LeptonX version)");
sb.AppendLine("");
sb.AppendLine("Some examples:");
sb.AppendLine("");
@ -167,5 +169,11 @@ public class UpdateCommand : IConsoleCommand, ITransientDependency
public const string Short = "v";
public const string Long = "version";
}
public static class LeptonXVersion
{
public const string Short = "lv";
public const string Long = "leptonx-version";
}
}
}

43
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs

@ -50,7 +50,7 @@ public class NpmPackagesUpdater : ITransientDependency
public async Task Update(string rootDirectory, bool includePreviews = false,
bool includeReleaseCandidates = false,
bool switchToStable = false, string version = null, bool includePreRc = false)
bool switchToStable = false, string version = null, string leptonXVersion = null, bool includePreRc = false)
{
var fileList = _packageJsonFileFinder.Find(rootDirectory);
@ -80,6 +80,7 @@ public class NpmPackagesUpdater : ITransientDependency
var updated = await UpdatePackagesInFile(file, includePreviews, includeReleaseCandidates,
switchToStable,
version,
leptonXVersion,
includePreRc);
packagesUpdated.TryAdd(file, updated);
@ -162,6 +163,7 @@ public class NpmPackagesUpdater : ITransientDependency
bool includeReleaseCandidates = false,
bool switchToStable = false,
string specifiedVersion = null,
string specifiedLeptonXVersion = null,
bool includePreRc = false)
{
var packagesUpdated = false;
@ -177,7 +179,7 @@ public class NpmPackagesUpdater : ITransientDependency
foreach (var abpPackage in abpPackages)
{
var updated = await TryUpdatingPackage(filePath, abpPackage, includePreviews, includeReleaseCandidates,
switchToStable, specifiedVersion, includePreRc);
switchToStable, specifiedVersion, specifiedLeptonXVersion, includePreRc);
if (updated)
{
@ -188,7 +190,7 @@ public class NpmPackagesUpdater : ITransientDependency
var updatedContent = packageJson.ToString(Formatting.Indented);
File.WriteAllText(filePath, updatedContent);
return packagesUpdated;
}
@ -199,6 +201,7 @@ public class NpmPackagesUpdater : ITransientDependency
bool includeReleaseCandidates = false,
bool switchToStable = false,
string specifiedVersion = null,
string specifiedLeptonXVersion = null,
bool includePreRc = false)
{
var currentVersion = (string)package.Value;
@ -207,18 +210,36 @@ public class NpmPackagesUpdater : ITransientDependency
if (!specifiedVersion.IsNullOrWhiteSpace())
{
if (!SpecifiedVersionExists(specifiedVersion, package))
if (package.Name.IndexOf("leptonx", StringComparison.InvariantCultureIgnoreCase) > 0 && !specifiedLeptonXVersion.IsNullOrWhiteSpace())
{
return false;
}
if (!SpecifiedVersionExists(specifiedLeptonXVersion, package))
{
return false;
}
if (SemanticVersion.Parse(specifiedVersion) <=
SemanticVersion.Parse(currentVersion.RemovePreFix("~", "^")))
{
return false;
if (SemanticVersion.Parse(specifiedLeptonXVersion) <=
SemanticVersion.Parse(currentVersion.RemovePreFix("~", "^")))
{
return false;
}
version = specifiedLeptonXVersion.EnsureStartsWith('^');
}
else
{
if (!SpecifiedVersionExists(specifiedVersion, package))
{
return false;
}
version = specifiedVersion.EnsureStartsWith('^');
if (SemanticVersion.Parse(specifiedVersion) <=
SemanticVersion.Parse(currentVersion.RemovePreFix("~", "^")))
{
return false;
}
version = specifiedVersion.EnsureStartsWith('^');
}
}
else
{

47
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs

@ -27,7 +27,14 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
Logger = NullLogger<VoloNugetPackagesVersionUpdater>.Instance;
}
public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false, bool checkAll = false, string version = null)
public async Task UpdateSolutionAsync(
string solutionPath,
bool includePreviews = false,
bool includeReleaseCandidates = false,
bool switchToStable = false,
bool checkAll = false,
string version = null,
string leptonXVersion = null)
{
var projectPaths = ProjectFinder.GetProjectFiles(solutionPath);
@ -58,6 +65,7 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
latestReleaseCandidateVersionInfo.Version,
latestVersionFromMyGet,
version,
leptonXVersion,
latestStableVersions: latestStableVersions);
fs.Seek(0, SeekOrigin.Begin);
@ -75,7 +83,14 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
}
}
public async Task UpdateProjectAsync(string projectPath, bool includeNightlyPreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false, bool checkAll = false, string version = null)
public async Task UpdateProjectAsync(
string projectPath,
bool includeNightlyPreviews = false,
bool includeReleaseCandidates = false,
bool switchToStable = false,
bool checkAll = false,
string version = null,
string leptonXVersion = null)
{
if (checkAll && version.IsNullOrWhiteSpace())
{
@ -102,6 +117,7 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
latestReleaseCandidateVersionInfo.Version,
latestVersionFromMyGet,
version,
leptonXVersion,
latestStableVersions: latestStableVersions);
fs.Seek(0, SeekOrigin.Begin);
@ -166,6 +182,7 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
SemanticVersion latestNugetReleaseCandidateVersion = null,
string latestMyGetVersion = null,
string specifiedVersion = null,
string specifiedLeptonXVersion = null,
List<PackageVersionCheckerService.LatestStableVersionResult> latestStableVersions = null)
{
string packageId = null;
@ -222,21 +239,35 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
var leptonXPackageVersion = latestStableVersions?
.FirstOrDefault(v => v.Version.Equals(specifiedVersion, StringComparison.InvariantCultureIgnoreCase))?.LeptonX?.Version;
if ((isLeptonXPackage && string.IsNullOrWhiteSpace(leptonXPackageVersion)) || isStudioPackage)
if ((isLeptonXPackage && string.IsNullOrWhiteSpace(leptonXPackageVersion) && specifiedLeptonXVersion.IsNullOrWhiteSpace()) || isStudioPackage)
{
Logger.LogWarning("Package: {PackageId} could not be updated. Please manually update the package version yourself to prevent version mismatches!", packageId);
continue;
}
var isLeptonXPackageWithVersion = isLeptonXPackage && !string.IsNullOrWhiteSpace(leptonXPackageVersion);
if (isLeptonXPackageWithVersion || await SpecifiedVersionExists(specifiedVersion, packageId))
if (isLeptonXPackage)
{
TryUpdatingPackage(isLeptonXPackageWithVersion ? leptonXPackageVersion : specifiedVersion);
var isLeptonXPackageWithVersion = isLeptonXPackage && !string.IsNullOrWhiteSpace(leptonXPackageVersion);
if (isLeptonXPackageWithVersion || await SpecifiedVersionExists(specifiedLeptonXVersion, packageId))
{
TryUpdatingPackage(specifiedLeptonXVersion ?? leptonXPackageVersion);
}
else
{
Logger.LogWarning($"Package \"{packageId}\" specified version v{specifiedLeptonXVersion} does not exist!");
}
}
else
{
Logger.LogWarning("Package \"{PackageId}\" specified version v{SpecifiedVersion} does not exist!", packageId, specifiedVersion);
if (await SpecifiedVersionExists(specifiedVersion, packageId))
{
TryUpdatingPackage(specifiedVersion);
}
else
{
Logger.LogWarning($"Package \"{packageId}\" specified version v{specifiedVersion} does not exist!");
}
}
void TryUpdatingPackage(string versionToUpdate)

13
framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs

@ -17,7 +17,6 @@ public class RemoteStreamContent : IRemoteStreamContent
public RemoteStreamContent(Stream stream, string? fileName = null, string? contentType = null, long? readOnlyLength = null, bool disposeStream = true)
{
_stream = stream;
FileName = fileName;
if (contentType != null)
{
@ -34,10 +33,16 @@ public class RemoteStreamContent : IRemoteStreamContent
public virtual void Dispose()
{
if (!_disposed && _disposeStream)
if (_disposed)
{
_disposed = true;
_stream?.Dispose();
return;
}
if (_disposeStream)
{
_stream.Dispose();
}
_disposed = true;
}
}

3
framework/src/Volo.Abp.Ddd.Domain.Shared/Volo/Abp/Domain/Entities/Events/Distributed/AbpDistributedEntityEventOptions.cs

@ -4,11 +4,14 @@ public class AbpDistributedEntityEventOptions
{
public IAutoEntityDistributedEventSelectorList AutoEventSelectors { get; }
public IAutoEntityDistributedEventSelectorList IgnoredEventSelectors { get; }
public EtoMappingDictionary EtoMappings { get; set; }
public AbpDistributedEntityEventOptions()
{
AutoEventSelectors = new AutoEntityDistributedEventSelectorList();
IgnoredEventSelectors = new AutoEntityDistributedEventSelectorList();
EtoMappings = new EtoMappingDictionary();
}
}

10
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs

@ -68,13 +68,9 @@ public class EntityChangeEventHelper : IEntityChangeEventHelper, ITransientDepen
private bool ShouldPublishDistributedEventForEntity(object entity)
{
return DistributedEntityEventOptions
.AutoEventSelectors
.IsMatch(
ProxyHelper
.UnProxy(entity)
.GetType()
);
var entityType = ProxyHelper.UnProxy(entity).GetType();
return !DistributedEntityEventOptions.IgnoredEventSelectors.IsMatch(entityType) &&
DistributedEntityEventOptions.AutoEventSelectors.IsMatch(entityType);
}
public virtual void PublishEntityUpdatedEvent(object entity)

7
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreModule.cs

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EntityFrameworkCore.DistributedEvents;
using Volo.Abp.Modularity;
using Volo.Abp.Uow.EntityFrameworkCore;
@ -28,5 +29,11 @@ public class AbpEntityFrameworkCoreModule : AbpModule
context.Services.TryAddTransient(typeof(IDbContextProvider<>), typeof(UnitOfWorkDbContextProvider<>));
context.Services.AddTransient(typeof(IDbContextEventOutbox<>), typeof(DbContextEventOutbox<>));
context.Services.AddTransient(typeof(IDbContextEventInbox<>), typeof(DbContextEventInbox<>));
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.IgnoredEventSelectors.Add<OutgoingEventRecord>();
options.IgnoredEventSelectors.Add<IncomingEventRecord>();
});
}
}

6
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpRabbitMqEventBusOptions.cs

@ -17,10 +17,10 @@ public class AbpRabbitMqEventBusOptions
public ushort? PrefetchCount { get; set; }
public IDictionary<string, object> QueueArguments { get; set; } = new Dictionary<string, object>();
public IDictionary<string, object?> QueueArguments { get; set; } = new Dictionary<string, object?>();
public IDictionary<string, object?> ExchangeArguments { get; set; } = new Dictionary<string, object?>();
public IDictionary<string, object> ExchangeArguments { get; set; } = new Dictionary<string, object>();
public string GetExchangeTypeOrDefault()
{
return string.IsNullOrEmpty(ExchangeType)

44
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs

@ -97,7 +97,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
SubscribeHandlers(AbpDistributedEventBusOptions.Handlers);
}
private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea)
private async Task ProcessEventAsync(IChannel channel, BasicDeliverEventArgs ea)
{
var eventName = ea.RoutingKey;
var eventType = EventTypes.GetOrDefault(eventName);
@ -224,10 +224,10 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
IEnumerable<OutgoingEventInfo> outgoingEvents,
OutboxConfig outboxConfig)
{
using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
using (var channel = await (await ConnectionPool.GetAsync(AbpRabbitMqEventBusOptions.ConnectionName))
.CreateChannelAsync(new CreateChannelOptions(publisherConfirmationsEnabled: true, publisherConfirmationTrackingEnabled: true, new ThrottlingRateLimiter(256))))
{
var outgoingEventArray = outgoingEvents.ToArray();
channel.ConfirmSelect();
foreach (var outgoingEvent in outgoingEventArray)
{
@ -248,8 +248,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
});
}
}
channel.WaitForConfirmsOrDie();
}
}
@ -293,31 +291,33 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
return PublishAsync(eventName, body, headersArguments, eventId, correlationId);
}
protected virtual Task PublishAsync(
protected virtual async Task PublishAsync(
string eventName,
byte[] body,
Dictionary<string, object>? headersArguments = null,
Guid? eventId = null,
string? correlationId = null)
{
using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
using (var channel = await (await ConnectionPool.GetAsync(AbpRabbitMqEventBusOptions.ConnectionName)).CreateChannelAsync())
{
return PublishAsync(channel, eventName, body, headersArguments, eventId, correlationId);
await PublishAsync(channel, eventName, body, headersArguments, eventId, correlationId);
}
}
protected virtual Task PublishAsync(
IModel channel,
protected virtual async Task PublishAsync(
IChannel channel,
string eventName,
byte[] body,
Dictionary<string, object>? headersArguments = null,
Guid? eventId = null,
string? correlationId = null)
{
EnsureExchangeExists(channel);
await EnsureExchangeExistsAsync(channel);
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
var properties = new BasicProperties
{
DeliveryMode = DeliveryModes.Persistent
};
if (properties.MessageId.IsNullOrEmpty())
{
@ -331,18 +331,16 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish(
await channel.BasicPublishAsync(
exchange: AbpRabbitMqEventBusOptions.ExchangeName,
routingKey: eventName,
mandatory: true,
mandatory: false,
basicProperties: properties,
body: body
);
return Task.CompletedTask;
}
private void EnsureExchangeExists(IModel channel)
protected virtual async Task EnsureExchangeExistsAsync(IChannel channel)
{
if (_exchangeCreated)
{
@ -351,14 +349,14 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
try
{
using (var temporaryChannel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
using (var temporaryChannel = await (await ConnectionPool.GetAsync(AbpRabbitMqEventBusOptions.ConnectionName)).CreateChannelAsync())
{
temporaryChannel.ExchangeDeclarePassive(AbpRabbitMqEventBusOptions.ExchangeName);
await temporaryChannel.ExchangeDeclarePassiveAsync(AbpRabbitMqEventBusOptions.ExchangeName);
}
}
catch (Exception)
{
channel.ExchangeDeclare(
await channel.ExchangeDeclareAsync(
AbpRabbitMqEventBusOptions.ExchangeName,
AbpRabbitMqEventBusOptions.GetExchangeTypeOrDefault(),
durable: true
@ -367,14 +365,14 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
_exchangeCreated = true;
}
private void SetEventMessageHeaders(IBasicProperties properties, Dictionary<string, object>? headersArguments)
protected virtual void SetEventMessageHeaders(IBasicProperties properties, Dictionary<string, object>? headersArguments)
{
if (headersArguments == null)
{
return;
}
properties.Headers ??= new Dictionary<string, object>();
properties.Headers ??= new Dictionary<string, object?>();
foreach (var header in headersArguments)
{

8
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs

@ -20,6 +20,7 @@ using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
namespace Volo.Abp.Http.Client.ClientProxying;
@ -33,6 +34,7 @@ public class ClientProxyBase<TService> : ITransientDependency
protected ICorrelationIdProvider CorrelationIdProvider => LazyServiceProvider.LazyGetRequiredService<ICorrelationIdProvider>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IOptions<AbpCorrelationIdOptions> AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpCorrelationIdOptions>>();
protected ICurrentTimezoneProvider CurrentTimezoneProvider => LazyServiceProvider.LazyGetRequiredService<ICurrentTimezoneProvider>();
protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService<IProxyHttpClientFactory>();
protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceConfigurationProvider>();
protected IOptions<AbpHttpClientOptions> ClientOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpHttpClientOptions>>();
@ -337,6 +339,12 @@ public class ClientProxyBase<TService> : ITransientDependency
//X-Requested-With
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest");
//Timezone
if (!CurrentTimezoneProvider.TimeZone.IsNullOrWhiteSpace())
{
requestMessage.Headers.Add(TimeZoneConsts.DefaultTimeZoneKey, CurrentTimezoneProvider.TimeZone);
}
}
protected virtual StringSegment RemoveQuotes(StringSegment input)

28
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs

@ -15,6 +15,7 @@ using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.Http.Client.ClientProxying;
@ -33,9 +34,15 @@ public class ClientProxyRequestPayloadBuilder : ITransientDependency
protected AbpHttpClientProxyingOptions HttpClientProxyingOptions { get; }
public ClientProxyRequestPayloadBuilder(IServiceScopeFactory serviceScopeFactory, IOptions<AbpHttpClientProxyingOptions> httpClientProxyingOptions)
protected IClock Clock { get; }
public ClientProxyRequestPayloadBuilder(
IServiceScopeFactory serviceScopeFactory,
IOptions<AbpHttpClientProxyingOptions> httpClientProxyingOptions,
IClock clock)
{
ServiceScopeFactory = serviceScopeFactory;
Clock = clock;
HttpClientProxyingOptions = httpClientProxyingOptions.Value;
}
@ -156,12 +163,12 @@ public class ClientProxyRequestPayloadBuilder : ITransientDependency
{
foreach (var item in (IEnumerable) value)
{
formData.Add(new StringContent(item.ToString()!, Encoding.UTF8), parameter.Name);
formData.Add(new StringContent(await ConvertValueToStringAsync(item), Encoding.UTF8), parameter.Name);
}
}
else
{
formData.Add(new StringContent(value.ToString()!, Encoding.UTF8), parameter.Name);
formData.Add(new StringContent(await ConvertValueToStringAsync(value), Encoding.UTF8), parameter.Name);
}
}
@ -172,4 +179,19 @@ public class ClientProxyRequestPayloadBuilder : ITransientDependency
{
return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value);
}
protected virtual Task<string> ConvertValueToStringAsync(object value)
{
if (value is DateTime dateTimeValue)
{
if (Clock.SupportsMultipleTimezone || dateTimeValue.Kind == DateTimeKind.Utc)
{
return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O"));
}
return Task.FromResult(dateTimeValue.ToString("yyyy-MM-ddTHH:mm:ss.fffffff").TrimEnd('0').TrimEnd('.'));
}
return Task.FromResult(value.ToString()!);
}
}

21
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs

@ -14,6 +14,7 @@ using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Localization;
using Volo.Abp.Timing;
namespace Volo.Abp.Http.Client.ClientProxying;
@ -36,11 +37,16 @@ public class ClientProxyUrlBuilder : ITransientDependency
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AbpHttpClientProxyingOptions HttpClientProxyingOptions { get; }
protected IClock Clock { get; }
public ClientProxyUrlBuilder(IServiceScopeFactory serviceScopeFactory, IOptions<AbpHttpClientProxyingOptions> httpClientProxyingOptions)
public ClientProxyUrlBuilder(
IServiceScopeFactory serviceScopeFactory,
IOptions<AbpHttpClientProxyingOptions> httpClientProxyingOptions,
IClock clock)
{
ServiceScopeFactory = serviceScopeFactory;
HttpClientProxyingOptions = httpClientProxyingOptions.Value;
Clock = clock;
}
public async Task<string> GenerateUrlWithParametersAsync(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
@ -218,13 +224,18 @@ public class ClientProxyUrlBuilder : ITransientDependency
return true;
}
protected virtual Task<string?> ConvertValueToStringAsync(object? value)
protected virtual Task<string> ConvertValueToStringAsync(object value)
{
if (value is DateTime dateTimeValue)
{
return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O"))!;
if (Clock.SupportsMultipleTimezone || dateTimeValue.Kind == DateTimeKind.Utc)
{
return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O"));
}
return Task.FromResult(dateTimeValue.ToString("yyyy-MM-ddTHH:mm:ss.fffffff").TrimEnd('0').TrimEnd('.'));
}
return Task.FromResult(value?.ToString());
return Task.FromResult(value.ToString()!);
}
}
}

22
framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs

@ -19,6 +19,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
private readonly CultureInfo _culture = CultureInfo.InvariantCulture;
private readonly IClock _clock;
private readonly AbpJsonOptions _options;
private bool _skipDateTimeNormalization;
public AbpDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> options)
{
@ -26,6 +27,12 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
_options = options.Value;
}
public virtual AbpDateTimeConverter SkipDateTimeNormalization()
{
_skipDateTimeNormalization = true;
return this;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
@ -46,7 +53,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
if (reader.TokenType == JsonToken.Date)
{
return _clock.Normalize(reader.Value!.To<DateTime>());
return Normalize(reader.Value!.To<DateTime>());
}
if (reader.TokenType != JsonToken.String)
@ -67,20 +74,20 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
{
if (DateTime.TryParseExact(dateText, format, _culture, _dateTimeStyles, out var d1))
{
return _clock.Normalize(d1);
return Normalize(d1);
}
}
}
var date = DateTime.Parse(dateText!, _culture, _dateTimeStyles);
return _clock.Normalize(date);
return Normalize(date);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
{
value = _clock.Normalize(value.To<DateTime>());
value = Normalize(value.To<DateTime>());
}
if (value is DateTime dateTime)
@ -111,4 +118,11 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableDateTimeNormalizationAttribute>(member) == null;
}
protected virtual DateTime Normalize(DateTime dateTime)
{
return _skipDateTimeNormalization
? dateTime
: _clock.Normalize(dateTime);
}
}

3
framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs

@ -13,7 +13,8 @@ public class AbpJsonNewtonsoftModule : AbpModule
context.Services.AddOptions<AbpNewtonsoftJsonSerializerOptions>()
.Configure<IServiceProvider>((options, rootServiceProvider) =>
{
options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver(rootServiceProvider.GetRequiredService<AbpDateTimeConverter>());
options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver(
rootServiceProvider.GetRequiredService<AbpDateTimeConverter>().SkipDateTimeNormalization());
});
}
}

3
framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs

@ -86,7 +86,8 @@ public class AbpNewtonsoftJsonSerializer : IJsonSerializer, ITransientDependency
if (!camelCase)
{
//Default contract resolver is AbpCamelCasePropertyNamesContractResolver}
settings.ContractResolver = new AbpDefaultContractResolver(RootServiceProvider.GetRequiredService<AbpDateTimeConverter>());
settings.ContractResolver = new AbpDefaultContractResolver(RootServiceProvider
.GetRequiredService<AbpDateTimeConverter>().SkipDateTimeNormalization());
}
if (indented)

13
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs

@ -29,14 +29,13 @@ public class AbpJsonSystemTextJsonModule : AbpModule
options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(rootServiceProvider
.GetRequiredService<IOptions<AbpSystemTextJsonSerializerModifiersOptions>>());
});
context.Services.AddOptions<AbpSystemTextJsonSerializerModifiersOptions>()
.Configure<IServiceProvider>((options, rootServiceProvider) =>
{
options.Modifiers.Add(new AbpDateTimeConverterModifier(
rootServiceProvider.GetRequiredService<AbpDateTimeConverter>(),
rootServiceProvider.GetRequiredService<AbpNullableDateTimeConverter>()).CreateModifyAction());
var dateTimeConverter = rootServiceProvider.GetRequiredService<AbpDateTimeConverter>().SkipDateTimeNormalization();
var nullableDateTimeConverter = rootServiceProvider.GetRequiredService<AbpNullableDateTimeConverter>().SkipDateTimeNormalization();
options.JsonSerializerOptions.TypeInfoResolver.As<AbpDefaultJsonTypeInfoResolver>().Modifiers.Add(
new AbpDateTimeConverterModifier(dateTimeConverter, nullableDateTimeConverter)
.CreateModifyAction());
});
}
}

22
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs

@ -13,6 +13,7 @@ public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependenc
{
private readonly IClock _clock;
private readonly AbpJsonOptions _options;
private bool _skipDateTimeNormalization;
public AbpDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> abpJsonOptions)
{
@ -20,6 +21,12 @@ public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependenc
_options = abpJsonOptions.Value;
}
public virtual AbpDateTimeConverter SkipDateTimeNormalization()
{
_skipDateTimeNormalization = true;
return this;
}
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (_options.InputDateTimeFormats.Any())
@ -31,7 +38,7 @@ public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependenc
var s = reader.GetString();
if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1))
{
return _clock.Normalize(d1);
return Normalize(d1);
}
}
}
@ -43,7 +50,7 @@ public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependenc
if (reader.TryGetDateTime(out var d3))
{
return _clock.Normalize(d3);
return Normalize(d3);
}
var dateText = reader.GetString();
@ -51,7 +58,7 @@ public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependenc
{
if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d4))
{
return _clock.Normalize(d4);
return Normalize(d4);
}
}
@ -62,11 +69,16 @@ public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependenc
{
if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace())
{
writer.WriteStringValue(_clock.Normalize(value));
writer.WriteStringValue(Normalize(value));
}
else
{
writer.WriteStringValue(_clock.Normalize(value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture));
writer.WriteStringValue(Normalize(value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture));
}
}
protected virtual DateTime Normalize(DateTime dateTime)
{
return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime);
}
}

22
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs

@ -13,6 +13,7 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
{
private readonly IClock _clock;
private readonly AbpJsonOptions _options;
private bool _skipDateTimeNormalization;
public AbpNullableDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> abpJsonOptions)
{
@ -20,6 +21,12 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
_options = abpJsonOptions.Value;
}
public virtual AbpNullableDateTimeConverter SkipDateTimeNormalization()
{
_skipDateTimeNormalization = true;
return this;
}
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (_options.InputDateTimeFormats.Any())
@ -31,7 +38,7 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
var s = reader.GetString();
if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1))
{
return _clock.Normalize(d1);
return Normalize(d1);
}
}
}
@ -43,7 +50,7 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
if (reader.TryGetDateTime(out var d2))
{
return _clock.Normalize(d2);
return Normalize(d2);
}
var dateText = reader.GetString();
@ -51,7 +58,7 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
{
if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3))
{
return _clock.Normalize(d3);
return Normalize(d3);
}
}
@ -68,12 +75,17 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
{
if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace())
{
writer.WriteStringValue(_clock.Normalize(value.Value));
writer.WriteStringValue(Normalize(value.Value));
}
else
{
writer.WriteStringValue(_clock.Normalize(value.Value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture));
writer.WriteStringValue(Normalize(value.Value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture));
}
}
}
protected virtual DateTime Normalize(DateTime dateTime)
{
return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime);
}
}

13
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpGuidCustomBsonTypeMapper.cs

@ -0,0 +1,13 @@
using System;
using MongoDB.Bson;
namespace Volo.Abp.MongoDB;
public class AbpGuidCustomBsonTypeMapper : ICustomBsonTypeMapper
{
public bool TryMapToBsonValue(object value, out BsonValue bsonValue)
{
bsonValue = new BsonBinaryData((Guid)value, GuidRepresentation.Standard);
return true;
}
}

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

Loading…
Cancel
Save