Browse Source

Merge branch 'dev' into pr/21965

pull/21965/head
maliming 2 years ago
parent
commit
937a3b351e
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 14
      Directory.Packages.props
  2. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. 8
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  4. 6
      build/common.ps1
  5. 147
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md
  6. BIN
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/cover-image.png
  7. BIN
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/idle-session-settings.png
  8. BIN
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/lazy-expandable.png
  9. BIN
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/session-expiration-warning.png
  10. BIN
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/studio-switch-to-preview.png
  11. 46
      docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/POST.md
  12. BIN
      docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/abp-studio-macos-intel.png
  13. BIN
      docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/cover-image.png
  14. BIN
      docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/Load-User-Profile.jpg
  15. 77
      docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/POST.md
  16. 11
      docs/en/cli/index.md
  17. 4
      docs/en/docs-nav.json
  18. 8
      docs/en/framework/architecture/best-practices/mongodb-integration.md
  19. 13
      docs/en/framework/architecture/domain-driven-design/repositories.md
  20. 8
      docs/en/guides/microservice-mongodb.md
  21. 2
      docs/en/modules/account-pro.md
  22. 59
      docs/en/release-info/migration-guides/MongoDB-Driver-2-to-3.md
  23. 7
      docs/en/release-info/migration-guides/abp-9-1.md
  24. 1
      docs/en/release-info/migration-guides/index.md
  25. 106
      docs/en/samples/index.md
  26. 217
      docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md
  27. 4
      docs/en/solution-templates/layered-web-application/solution-structure.md
  28. 2
      docs/en/studio/monitoring-applications.md
  29. 14
      docs/en/studio/release-notes.md
  30. 8
      docs/en/studio/running-applications.md
  31. 3
      docs/en/studio/version-mapping.md
  32. 7
      docs/en/tutorials/book-store/part-07.md
  33. BIN
      docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png
  34. BIN
      docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png
  35. BIN
      docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png
  36. BIN
      docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png
  37. BIN
      docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png
  38. 4
      docs/en/tutorials/modular-crm/index.md
  39. 7
      docs/en/tutorials/modular-crm/part-03.md
  40. 16
      docs/en/tutorials/modular-crm/part-05.md
  41. 15
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  42. 24
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  43. 64
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
  44. 47
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  45. 11
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs
  46. 8
      framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs
  47. 38
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs
  48. 18
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  49. 20
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs
  50. 6
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs
  51. 26
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs
  52. 3
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs
  53. 11
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs
  54. 53
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  55. 15
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs
  56. 2
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs
  57. 5
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs
  58. 1
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs
  59. 1
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs
  60. 141
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs
  61. 72
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  62. 22
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/DistributedEventHandles.cs
  63. 63
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs
  64. 62
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/UnitTestLocalEventBus.cs
  65. 8
      framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj
  66. 5
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs
  67. 12
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs
  68. 1
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/CityRepository.cs
  69. 9
      latest-versions.json
  70. 101
      modules/account/src/Volo.Abp.Account.HttpApi/Volo.Abp.Account.HttpApi.abppkg.analyze.json
  71. 41
      modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg.analyze.json
  72. 2
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json
  73. 39
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter.cs
  74. 18
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs
  75. 9
      modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo.Abp.AuditLogging.EntityFrameworkCore.abppkg.analyze.json
  76. 24
      modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs
  77. 6
      modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AuditLogEntityTypeFullNameConverter_Tests.cs
  78. 8
      modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.csproj
  79. 9
      modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogEntityTypeFullNameConverter_Tests.cs
  80. 2
      modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs
  81. 46
      modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter_Tests.cs
  82. 9
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo.Abp.BackgroundJobs.EntityFrameworkCore.abppkg.analyze.json
  83. 5
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs
  84. 8
      modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj
  85. 2
      modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs
  86. 4
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml
  87. 24
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css
  88. 17
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/logo.svg
  89. 15
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.abppkg.analyze.json
  90. 4
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs
  91. 2
      modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs
  92. 8
      modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj
  93. 2
      modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.abppkg.analyze.json
  94. 84
      modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo.Blogging.Admin.HttpApi.abppkg.analyze.json
  95. 77
      modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.abppkg.analyze.json
  96. 39
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo.Blogging.EntityFrameworkCore.abppkg.analyze.json
  97. 294
      modules/blogging/src/Volo.Blogging.HttpApi/Volo.Blogging.HttpApi.abppkg.analyze.json
  98. 2
      modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs
  99. 9
      modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs
  100. 13
      modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs

14
Directory.Packages.props

@ -17,10 +17,10 @@
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.401.16" /> <PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.401.16" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.1" /> <PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.22.1" /> <PackageVersion Include="Azure.Storage.Blobs" Version="12.22.1" />
<PackageVersion Include="Blazorise" Version="1.7.2" /> <PackageVersion Include="Blazorise" Version="1.7.3" />
<PackageVersion Include="Blazorise.Components" Version="1.7.2" /> <PackageVersion Include="Blazorise.Components" Version="1.7.3" />
<PackageVersion Include="Blazorise.DataGrid" Version="1.7.2" /> <PackageVersion Include="Blazorise.DataGrid" Version="1.7.3" />
<PackageVersion Include="Blazorise.Snackbar" Version="1.7.2" /> <PackageVersion Include="Blazorise.Snackbar" Version="1.7.3" />
<PackageVersion Include="Castle.Core" Version="5.1.1" /> <PackageVersion Include="Castle.Core" Version="5.1.1" />
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" /> <PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" /> <PackageVersion Include="CommonMark.NET" Version="0.15.1" />
@ -37,6 +37,10 @@
<PackageVersion Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" /> <PackageVersion Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" /> <PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" /> <PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<PackageVersion Include="MongoSandbox.Core" Version="1.0.1" />
<PackageVersion Include="MongoSandbox6.runtime.linux-x64" Version="1.0.1" />
<PackageVersion Include="MongoSandbox6.runtime.osx-x64" Version="1.0.1" />
<PackageVersion Include="MongoSandbox6.runtime.win-x64" Version="1.0.1" />
<PackageVersion Include="FluentValidation" Version="11.10.0" /> <PackageVersion Include="FluentValidation" Version="11.10.0" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" /> <PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" /> <PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" />
@ -111,7 +115,7 @@
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" /> <PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
<PackageVersion Include="Minio" Version="6.0.3" /> <PackageVersion Include="Minio" Version="6.0.3" />
<PackageVersion Include="MongoDB.Driver" Version="2.29.0" /> <PackageVersion Include="MongoDB.Driver" Version="3.1.0" />
<PackageVersion Include="NEST" Version="7.17.5" /> <PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Nito.AsyncEx.Context" Version="5.1.2" /> <PackageVersion Include="Nito.AsyncEx.Context" Version="5.1.2" />

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

@ -683,6 +683,8 @@
"DiscountRate": "Discount Rate", "DiscountRate": "Discount Rate",
"Menu:RedisManagement": "Redis Management", "Menu:RedisManagement": "Redis Management",
"RedisManagement": "Redis Management", "RedisManagement": "Redis Management",
"Permission:RedisManagement": "Redis Management" "Permission:RedisManagement": "Redis Management",
"UserCleanUp": "User Clean Up",
"Permission:UserCleanUp": "User Clean Up"
} }
} }

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

@ -535,7 +535,7 @@
"WhenShouldIRenewMyLicenseExplanation3": "{0} for Business and Enterprise Licenses;", "WhenShouldIRenewMyLicenseExplanation3": "{0} for Business and Enterprise Licenses;",
"WhenShouldIRenewMyLicenseExplanation4": "However, if you renew your license more than {0} days after the expiry date, the renewal price will be the same as the initial purchase price of the license, with no discounts applied to your renewal.", "WhenShouldIRenewMyLicenseExplanation4": "However, if you renew your license more than {0} days after the expiry date, the renewal price will be the same as the initial purchase price of the license, with no discounts applied to your renewal.",
"DoesTheSubscriptionRenewAutomaticallyExplanationAutoRenewal": "ABP Platform allows you to auto-renew your license. This is an optional free service. You can toggle this feature when you purchase a new license or later enable it from your <a href=\"{0}\">organization management page</a>. If you want to turn on or off the auto-renewal, visit the <a href=\"{0}\">organization management page</a>, go to the 'Payments Method' section and either check or uncheck the 'Automatic Renewal' checkbox. When you turn off the auto-renewal feature, it will be your responsibility to renew your license manually.", "DoesTheSubscriptionRenewAutomaticallyExplanationAutoRenewal": "ABP Platform allows you to auto-renew your license. This is an optional free service. You can toggle this feature when you purchase a new license or later enable it from your <a href=\"{0}\">organization management page</a>. If you want to turn on or off the auto-renewal, visit the <a href=\"{0}\">organization management page</a>, go to the 'Payments Method' section and either check or uncheck the 'Automatic Renewal' checkbox. When you turn off the auto-renewal feature, it will be your responsibility to renew your license manually.",
"TrialPlanExplanation": "Yes, to start your free trial, please contact <a href=\"mailto:marketing@volosoft.com?subject=Trial License Request\">marketing@volosoft.com</a>. We also offer a 30-day money-back guarantee for the Team license, with no questions asked! You can request a full refund within the first 30 days of purchasing the license. For Business and Enterprise licenses, we provide a 60% refund if requested within 30 days of purchase. This policy is due to the inclusion of the full source code for all modules and themes in the Business and Enterprise licenses.", "TrialPlanExplanation": "Yes, to start your free trial, please contact <a href=\"mailto:sales@volosoft.com?subject=Trial License Request\">sales@volosoft.com</a>. We also offer a 30-day money-back guarantee for the Team license, with no questions asked! You can request a full refund within the first 30 days of purchasing the license. For Business and Enterprise licenses, we provide a 60% refund if requested within 30 days of purchase. This policy is due to the inclusion of the full source code for all modules and themes in the Business and Enterprise licenses.",
"BlazoriseLicenseExplanation": "We have an agreement between Volosoft and Megabit, according to which the Blazorise license is bundled with the ABP Platform’s commercial licenses. Therefore, our paid users do not need to purchase an additional Blazorise license.", "BlazoriseLicenseExplanation": "We have an agreement between Volosoft and Megabit, according to which the Blazorise license is bundled with the ABP Platform’s commercial licenses. Therefore, our paid users do not need to purchase an additional Blazorise license.",
"HowToUpgradeExplanation1": "When you create a new application using the ABP startup templates, all the modules and themes are used as NuGet and NPM packages. This setup allows for easy upgrades to newer versions of the packages.", "HowToUpgradeExplanation1": "When you create a new application using the ABP startup templates, all the modules and themes are used as NuGet and NPM packages. This setup allows for easy upgrades to newer versions of the packages.",
"HowToUpgradeExplanation2": "In addition to the standard NuGet/NPM upgrades, <a href=\"{0}\">ABP CLI</a> provides an <strong>update</strong> command that automatically finds and upgrades all ABP-related packages in your solution.", "HowToUpgradeExplanation2": "In addition to the standard NuGet/NPM upgrades, <a href=\"{0}\">ABP CLI</a> provides an <strong>update</strong> command that automatically finds and upgrades all ABP-related packages in your solution.",
@ -1868,6 +1868,10 @@
"GenerateQuote" : "Generate Quote" , "GenerateQuote" : "Generate Quote" ,
"GeneratePriceQuote": "Generate a Price Quote", "GeneratePriceQuote": "Generate a Price Quote",
"Qa:QuestionPageTitle": "Support", "Qa:QuestionPageTitle": "Support",
"SelectedTrainingName" : "Trainings" "SelectedTrainingName" : "Trainings",
"RcStableDifference": "What is the difference between the RC version and the stable version of ABP?",
"RcStableDifferenceExplanation1": "The RC (Release Candidate) version is a pre-release version that allows early access to upcoming features and updates in the ABP project. It is primarily intended for testing purposes and for developers who want to prepare for the upcoming stable release. While it undergoes internal testing, it may still contain unresolved issues and it is not recommended for use in the production environment.",
"RcStableDifferenceExplanation2": "The Stable version is tested and officially supported for production use. It ensures reliability and compatibility.",
"RcStableDifferenceExplanation3": "Use the RC version for testing and early adoption but use the Stable version for production deployment."
} }
} }

6
build/common.ps1

@ -34,11 +34,13 @@ if ($full -eq "-f")
"../templates/module/aspnet-core", "../templates/module/aspnet-core",
"../templates/app/aspnet-core", "../templates/app/aspnet-core",
"../templates/console", "../templates/console",
"../templates/wpf",
"../templates/app-nolayers/aspnet-core", "../templates/app-nolayers/aspnet-core",
"../abp_io/AbpIoLocalization", "../abp_io/AbpIoLocalization",
"../source-code" "../source-code"
) )
if ($env:OS -eq "Windows_NT") {
$solutionPaths += "../templates/wpf"
}
}else{ }else{
Write-host "" Write-host ""
Write-host ":::::::::::::: !!! You are in development mode !!! ::::::::::::::" -ForegroundColor red -BackgroundColor yellow Write-host ":::::::::::::: !!! You are in development mode !!! ::::::::::::::" -ForegroundColor red -BackgroundColor yellow

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

@ -0,0 +1,147 @@
# ABP Platform 9.1 RC Has Been Released
We are happy to release [ABP](https://abp.io) version **9.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
Try this version and provide feedback for a more stable version of ABP v9.1! Thanks to you in advance.
## Get Started with the 9.1 RC
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli).
By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI:
![studio-switch-to-preview.png](studio-switch-to-preview.png)
## Migration Guide
There are no breaking changes in this version that would affect your application. Only you might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the [OpenIddict 6.0 migration guide](https://abp.io/docs/9.1/release-info/migration-guides/openiddict5-to-6).
## What's New with ABP v9.1?
In this section, I will introduce some major features released in this version.
Here is a brief list of titles explained in the next sections:
* Upgraded to Angular 19
* Upgraded to OpenIddict 6.0
* New Blazor WASM Bundling System
* Idle Session Warning
* Lazy Expandable Feature for Documentation
### Upgraded to Angular 19
We've upgraded the Angular templates and packages to **Angular 19**. This upgrade brings the latest features and improvements from the Angular ecosystem to ABP-based applications, including better performance and development experience.
### Upgraded to OpenIddict 6.0
OpenIddict 6.0 has been released and we've upgraded the OpenIddict packages to version 6.0 in ABP 9.1. This brings enhanced security features and improved authentication capabilities. The migration is straightforward and mainly involves updating some constant names:
- `OpenIddictConstants.Permissions.Endpoints.Logout` is now `OpenIddictConstants.Permissions.Endpoints.EndSession`
- `OpenIddictConstants.Permissions.Endpoints.Device` is now `OpenIddictConstants.Permissions.Endpoints.DeviceAuthorization`
If you're using IdentityModel packages directly, you'll need to upgrade them to the latest stable version (8.3.0). This update ensures your applications stay current with the latest security standards and best practices.
> Please refer to the [OpenIddict 6.0 migration guide](https://abp.io/docs/9.1/release-info/migration-guides/openiddict5-to-6) for more information.
### New Blazor WASM Bundling System
We've implemented a new bundling system for Blazor WebAssembly applications that eliminates the need to manually run the `abp bundle` command. This system automatically handles JavaScript and CSS file bundling at runtime, significantly improving both development experience and application loading performance.
**Key improvements include:**
- Automatic bundling of JavaScript and CSS files without manual intervention
- Dynamic file generation through the host application
- Better integration with the ABP module system
- Improved asset management through virtual file system
The new system is particularly beneficial for modular applications, as it allows modules to contribute their assets automatically to the global bundles. This results in a more maintainable and efficient asset management system for Blazor WebAssembly applications.
> Please refer to [this documentation](https://abp.io/docs/9.1/framework/ui/blazor/global-scripts-styles) for more information.
### Idle Session Warning
We've introduced a new idle session warning feature for the [Account (Pro) Module](https://abp.io/docs/latest/modules/account-pro) that helps manage user sessions more effectively. This security enhancement automatically monitors user activity and manages session timeouts in a user-friendly way.
![idle-session-settings.png](idle-session-settings.png)
The feature can be easily configured through the administration interface, where administrators can:
- Enable/disable the idle session timeout
- Set custom timeout duration in minutes
- Configure when users should be signed out
When a user becomes inactive for the configured duration, they'll receive a warning dialog:
![session-expiration-warning.png](session-expiration-warning.png)
**Key features and behaviors:**
- Tracks real user activity (mouse movements, keyboard presses) across all tabs
- Works on a per-browser session basis - affects all tabs of the same session
- Maintains session if user is active in any tab of the application
- Provides a countdown timer before automatic sign-out
- Offers options to "Stay signed in" or "Sign out now"
This feature significantly improves application security while maintaining a smooth user experience by preventing unexpected session expirations and data loss.
### Lazy Expandable Feature for Documentation
We've introduced a new lazy expandable feature to the documentation system that significantly improves navigation through large documentation sections. This enhancement addresses common challenges when dealing with extensive documentation hierarchies by introducing smart menu management.
**Key benefits and features:**
- **Cleaner Navigation:** The menu stays concise by hiding sub-items until they're needed, reducing visual clutter
- **Better Performance:** Reduces the initial load of the navigation tree by loading sub-items on demand
- **Improved Search Experience:** Makes filtering documentation items more efficient by showing only relevant top-level items
- **Context-Aware Expansion:** Automatically expands relevant sections when viewing specific documentation pages
The feature works by marking certain documentation sections as "lazy expandable" in the navigation configuration. When users navigate to a document within a lazy expandable section, the system automatically expands the relevant menu items while keeping other sections collapsed.
This improvement is particularly valuable for complex documentation areas like tutorials, solution templates, and extensive module documentation, where having all navigation items visible at once could be overwhelming.
An example of lazy expandable feature from the [ABP's BookStore Tutorial](https://abp.io/docs/latest/tutorials/book-store/part-01):
```json
{
"text": "Book Store Application",
"isLazyExpandable": true,
"path": "tutorials/book-store",
"items": [
{
"text": "Overview",
"path": "tutorials/book-store",
"isIndex": true
},
//other items...
]
}
```
![lazy-expandable.png](lazy-expandable.png)
### Others
Some other highlights from this release:
* Updated Iyzico NuGet packages to the latest version, which is used in the [ABP's Payment Module](https://abp.io/docs/latest/modules/payment#payment-module-pro).
* Removed optional _secondaryIds_ from path. See: [#21307](https://github.com/abpframework/abp/pull/21307)
* [CMS Kit Pro](https://abp.io/docs/latest/modules/cms-kit-pro): Added automatic deletion of comments when a blog post is deleted - comments are now automatically removed when their associated blog post is deleted.
* Avoiding global blocking in distributed event handlers (See [#21716](https://github.com/abpframework/abp/pull/21716)).
## Community News
### New ABP Community Articles
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
* [Integrating ABP Modules in Your ASP.NET Core Web API Project. A Step-by-Step Guide](https://abp.io/community/articles/integrating-abp-modules-in-your-asp.net-core-web-api-project.-a-stepbystep-guide-jtbyosnr) by [Sajankumar Vijayan](https://abp.io/community/members/connect)
* [ABP Framework: Background Jobs vs Background Workers](https://abp.io/community/articles/abp-framework-background-jobs-vs-background-workers-when-to-use-which-t98pzjv6) — When to Use Which? by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu)
* [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.
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/9.1/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.1 RC and provide feedback to help us release a more stable version.
Thanks for being a part of this community!

BIN
docs/en/Blog-Posts/2025-01-21 v9_1_Preview/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

BIN
docs/en/Blog-Posts/2025-01-21 v9_1_Preview/idle-session-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/en/Blog-Posts/2025-01-21 v9_1_Preview/lazy-expandable.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/en/Blog-Posts/2025-01-21 v9_1_Preview/session-expiration-warning.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/en/Blog-Posts/2025-01-21 v9_1_Preview/studio-switch-to-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

46
docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/POST.md

@ -0,0 +1,46 @@
# ABP Studio Now Supports MacOS Intel 🚀
We are excited to announce that [ABP Studio, our cross-platform desktop application for ABP developers](https://abp.io/studio), now supports Intel-based Mac computers!
This addition expands our platform compatibility, ensuring that developers using Intel-powered Macs can also benefit from the powerful features of ABP Studio.
## What is ABP Studio?
For those who aren't familiar, [ABP Studio](https://abp.io/studio) is a powerful desktop application that makes ABP development faster and easier. It offers:
* Easy creation of new solutions (from simple applications to microservices)
* Visual architecture management for modular-monolith and microservice solutions
* Solution exploration tools for entities, services, and HTTP APIs
* Simplified running, debugging and monitoring of multi-application or microservice solutions
* Kubernetes cluster integration capabilities
* and more...
## Extended Platform Support
ABP Studio has been proudly supporting multiple platforms, and we're excited to add MacOS Intel to the our list of supported architectures. You can now use ABP Studio on:
* Windows x64
* Windows ARM
* MacOS Apple Silicon (M1/M2/M3)
* MacOS Intel **(New!)**
## Why This Matters
This update is particularly important for developers who are using Intel-based Mac computers. Previously, ABP Studio was only available for Apple Silicon Macs (for MacOS), but we understand that many developers are still using Intel-based Macs. With this release, we're ensuring that all Mac users can access our development tools, regardless of their processor architecture.
## Getting Started
Installing ABP Studio on your Intel-based Mac is straightforward:
1. Go to [abp.io/studio](https://abp.io/studio)
2. Click on the download button and select "MacOS Intel" from the dropdown menu
3. Once downloaded, open the installer package
4. Follow the installation wizard to complete the setup
![ABP Studio Download Page](abp-studio-macos-intel.png)
## Conclusion
As ABP team, we're always looking for ways to improve the developer experience. By supporting Intel-based Macs, we're ensuring that all Mac users can access our development tools, regardless of their processor architecture.
Stay tuned for more updates and enhancements as we continue to optimize ABP Studio and please provide us with your invaluable feedback. Thanks in advance!

BIN
docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/abp-studio-macos-intel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

BIN
docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

BIN
docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/Load-User-Profile.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

77
docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/POST.md

@ -0,0 +1,77 @@
# Fixing OpenIddict Certificate Issues in IIS or Azure
When deploying an ABP application with OpenIddict to IIS or Azure, you may encounter issues with loading PFX/PKCS12 certificates. This article explains how to properly configure certificate loading to ensure it works correctly in these environments.
## The Problem
When running under IIS or Azure, the application pool identity may not have sufficient permissions to access certificate private keys. This commonly results in errors such as:
- `System.Security.Cryptography.CryptographicException: Access denied.`
- `WindowsCryptographicException: Access is denied.`
- `System.Security.Cryptography.CryptographicException: The system cannot find the file specified.`
## The Solution
### Using AddDevelopmentEncryptionAndSigningCertificate
For development environments using `DevelopmentEncryptionAndSigningCertificate`, you must configure the application pool to load a user profile.
> Note: We strongly recommend using `DevelopmentEncryptionAndSigningCertificate` only in development environments. For production, always create and use a separate certificate.
![Application Pool Configuration](Load-User-Profile.jpg)
### Using AddProductionEncryptionAndSigningCertificate
The ABP OpenIddict module provides an `AddProductionEncryptionAndSigningCertificate` extension method. By default, the template project attempts to load an `openiddict.pfx` certificate in production environments.
To ensure proper certificate loading in IIS or Azure, you need to specify appropriate `X509KeyStorageFlags` when calling this method:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (!hostingEnvironment.IsDevelopment())
{
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
options.AddDevelopmentEncryptionAndSigningCertificate = false;
});
PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{
var flag = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet;
serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "YourCertificatePassword", flag);
});
}
}
```
## Understanding X509KeyStorageFlags
The configuration uses two important flags:
* `X509KeyStorageFlags.MachineKeySet`: Specifies that the key belongs to the local computer key store, binding the key pair's lifecycle to the computer rather than a specific user.
* `X509KeyStorageFlags.EphemeralKeySet`: Indicates that the key will be stored only in memory and not persisted to disk or key store, enhancing security for runtime-only certificate requirements.
Using these flags in combination helps prevent permission-related issues in IIS and Azure environments.
## Troubleshooting Guide
If you continue to experience issues, verify the following:
* Confirm that the certificate password is correct
* Verify that the `openiddict.pfx` file exists in your deployment
* Ensure the certificate is valid - you can generate a new one using:
```bash
dotnet dev-certs https -v -ep openiddict.pfx -p YourCertificatePassword
```
* Check the stdout logs for related errors (See [how to get stdout-log](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/deployment-iis?UI=Blazor&DB=EF&Tiered=No#how-to-get-stdout-log))
## References
- [ABP OpenIddict Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/openiddict-deployment)
- [ABP IIS Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/deployment-iis)
- [ABP Azure Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/azure-deployment/azure-deployment)
- [How to Generate a New Certificate](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs#how-to-generate-a-new-certificate)
- [Load User Profile in IIS](https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#load-user-profile-for-an-application-pool)

11
docs/en/cli/index.md

@ -883,18 +883,13 @@ abp translate -c zh-Hans --online --deepl-auth-key <auth-key>
### login ### login
Some features of the CLI requires to be logged in to ABP Platform. To login with your username write: Some features of the CLI requires to be logged in to ABP Platform. The login command supports the following usage options:
```bash ```bash
abp login <username> # Allows you to enter your password hidden abp login # Opens a default browser to log in to ABP Platform via abp.io
abp login <username> -p <password> # Specify the password as a parameter (password is visible) abp login --device # Use device login flow
abp login <username> --organization <organization> # If you have multiple organizations, you need set your active organization
abp login <username> -p <password> -o <organization> # You can enter both your password and organization in the same command
abp login <username> --device # Use device login flow
``` ```
> When using the -p parameter, be careful as your password will be visible. It's useful for CI/CD automation pipelines.
A new login with an already active session overwrites the previous session. A new login with an already active session overwrites the previous session.
### login-info ### login-info

4
docs/en/docs-nav.json

@ -2292,6 +2292,10 @@
{ {
"text": "Tenant impersonation & User impersonation", "text": "Tenant impersonation & User impersonation",
"path": "modules/account/impersonation.md" "path": "modules/account/impersonation.md"
},
{
"text": "Idle Session Timeout",
"path": "modules/account/idle-session-timeout.md"
} }
] ]
}, },

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

@ -114,7 +114,7 @@ public async Task<IdentityUser> FindByNormalizedUserNameAsync(
bool includeDetails = true, bool includeDetails = true,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync()) return await (await GetQueryableAsync())
.FirstOrDefaultAsync( .FirstOrDefaultAsync(
u => u.NormalizedUserName == normalizedUserName, u => u.NormalizedUserName == normalizedUserName,
GetCancellationToken(cancellationToken) GetCancellationToken(cancellationToken)
@ -125,10 +125,10 @@ public async Task<IdentityUser> FindByNormalizedUserNameAsync(
`GetCancellationToken` fallbacks to the `ICancellationTokenProvider.Token` to obtain the cancellation token if it is not provided by the caller code. `GetCancellationToken` fallbacks to the `ICancellationTokenProvider.Token` to obtain the cancellation token if it is not provided by the caller code.
* **Do** ignore the `includeDetails` parameters for the repository implementation since MongoDB loads the aggregate root as a whole (including sub collections) by default. * **Do** ignore the `includeDetails` parameters for the repository implementation since MongoDB loads the aggregate root as a whole (including sub collections) by default.
* **Do** use the `GetMongoQueryableAsync()` method to obtain an `IQueryable<TEntity>` to perform queries wherever possible. Because; * **Do** use the `GetQueryableAsync()` method to obtain an `IQueryable<TEntity>` to perform queries wherever possible. Because;
* `GetMongoQueryableAsync()` method automatically uses the `ApplyDataFilters` method to filter the data based on the current data filters (like soft delete and multi-tenancy). * `GetQueryableAsync()` method automatically uses the `ApplyDataFilters` method to filter the data based on the current data filters (like soft delete and multi-tenancy).
* Using `IQueryable<TEntity>` makes the code as much as similar to the EF Core repository implementation and easy to write and read. * Using `IQueryable<TEntity>` makes the code as much as similar to the EF Core repository implementation and easy to write and read.
* **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method. * **Do** implement data filtering if it is not possible to use the `GetQueryableAsync()` method.
## Module Class ## Module Class

13
docs/en/framework/architecture/domain-driven-design/repositories.md

@ -393,19 +393,6 @@ This method is suggested;
* If you are developing an application and you **don't plan to change** EF Core in the future, or you can **tolerate** it if you need to change it later. We believe that's reasonable if you are developing a final application. * If you are developing an application and you **don't plan to change** EF Core in the future, or you can **tolerate** it if you need to change it later. We believe that's reasonable if you are developing a final application.
#### MongoDB Case
If you are using [MongoDB](../../data/mongodb), you need to add the [Volo.Abp.MongoDB](https://www.nuget.org/packages/Volo.Abp.MongoDB) NuGet package to your project. Even in this case, you can't directly use async LINQ extensions (like `ToListAsync`) because MongoDB doesn't provide async extension methods for `IQueryable<T>`, but provides for `IMongoQueryable<T>`. You need to cast the query to `IMongoQueryable<T>` first to be able to use the async extension methods.
**Example: Cast `IQueryable<T>` to `IMongoQueryable<T>` and use `ToListAsync()`**
````csharp
var queryable = await _personRepository.GetQueryableAsync();
var people = ((IMongoQueryable<Person>) queryable
.Where(p => p.Name.Contains(nameFilter)))
.ToListAsync();
````
### Option-2: Use the IRepository Async Extension Methods ### Option-2: Use the IRepository Async Extension Methods
ABP provides async extension methods for the repositories, just similar to async LINQ extension methods. ABP provides async extension methods for the repositories, just similar to async LINQ extension methods.

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

@ -97,9 +97,9 @@ Here we use `BookStore.ProductService` project as an example:
int skipCount = 0, int skipCount = 0,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var query = ApplyFilter(await GetMongoQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax); var query = ApplyFilter(await GetQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax);
query = query.OrderBy(string.IsNullOrWhiteSpace(sorting) ? ProductConsts.GetDefaultSorting(false) : sorting); query = query.OrderBy(string.IsNullOrWhiteSpace(sorting) ? ProductConsts.GetDefaultSorting(false) : sorting);
return await query.As<IMongoQueryable<Product>>().PageBy<Product, IMongoQueryable<Product>>(skipCount, maxResultCount).ToListAsync(cancellationToken); return await query.PageBy(skipCount, maxResultCount).ToListAsync(cancellationToken);
} }
public async Task<long> GetCountAsync( public async Task<long> GetCountAsync(
@ -109,8 +109,8 @@ Here we use `BookStore.ProductService` project as an example:
float? priceMax = null, float? priceMax = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var query = ApplyFilter(await GetMongoQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax); var query = ApplyFilter(await GetQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax);
return await query.As<IMongoQueryable<Product>>().LongCountAsync(GetCancellationToken(cancellationToken)); return await query.LongCountAsync(GetCancellationToken(cancellationToken));
} }
protected virtual IQueryable<Product> ApplyFilter( protected virtual IQueryable<Product> ApplyFilter(

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

@ -358,4 +358,4 @@ This module doesn't define any additional distributed event. See the [standard d
* [Impersonation](./account/impersonation.md) * [Impersonation](./account/impersonation.md)
* [Linked Accounts](./account/linkedaccounts.md) * [Linked Accounts](./account/linkedaccounts.md)
* [Session Management](./account/session-management.md) * [Session Management](./account/session-management.md)
* [Idle Session Timeout](./account/idle-session-timeout.md)] * [Idle Session Timeout](./account/idle-session-timeout.md)

59
docs/en/release-info/migration-guides/MongoDB-Driver-2-to-3.md

@ -0,0 +1,59 @@
# Migrating from MongoDB Driver 2 to 3
## Introduction
The release of MongoDB Driver 3 includes numerous user-requested fixes and improvements that were deferred in previous versions due to backward compatibility concerns. It also features internal improvements to reduce technical debt and enhance maintainability. One major update is the removal of a significant portion of the public API (primarily from `MongoDB.Driver.Core`), which was not intended for public use. The removed APIs were marked as deprecated in version 2.30.0.
Please refer to the [upgrade guide](https://www.mongodb.com/docs/drivers/csharp/current/upgrade/v3/) for a complete list of breaking changes and upgrade guidelines.
## Repository Changes
Some method signatures in the `MongoDbRepository` class have been updated because the `IMongoQueryable` has been removed. The specific changes are as follows:
- The new `GetQueryableAsync` method has been added to return `IQueryable<TEntity>`.
- The `GetMongoQueryable` and `GetMongoQueryableAsync` methods return `IQueryable<TEntity>` instead of `IMongoQueryable<TEntity>`,
- The `GetMongoQueryable` and `GetMongoQueryableAsync` methods are marked as obsolete, You should use the new `GetQueryableAsync` method instead.
Please update your application by searching for and replacing these method calls.
> The return value of the `GetQueryableAsync` method is `IQueryable<TEntity>`, which can be used directly to perform queries, similar to EF Core. Remove all instances of `IMongoQueryable` in your project and replace them with `IQueryable`.
**Previous code example:**
```csharp
var myEntity = await (await GetMongoQueryableAsync()).As<IMongoQueryable<MyEntity>>().FirstOrDefaultAsync(x => x.Id == id);
```
**Updated code example:**
```csharp
var myEntity = await GetQueryableAsync().FirstOrDefaultAsync(x => x.Id == id);
```
## Unit Test Changes
Previously, we used the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library for unit testing. However, it does not support the latest version of [MongoDB.Driver 3.x](https://github.com/mongodb/mongo-go-driver). You should replace it with [MongoSandbox](https://github.com/wassim-k/MongoSandbox).
In your unit test project files, replace the following:
```xml
<PackageReference Include="EphemeralMongo.Core" Version="1.1.3" />
<PackageReference Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
```
With:
```xml
<PackageReference Include="MongoSandbox.Core" Version="1.0.1" />
<PackageReference Include="MongoSandbox6.runtime.linux-x64" Version="1.0.1" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="MongoSandbox6.runtime.osx-x64" Version="1.0.1" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="MongoSandbox6.runtime.win-x64" Version="1.0.1" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
```
In your unit test classes, replace `using EphemeralMongo` with `using MongoSandbox`.
## Official Upgrade Guide
We recommend reviewing the [upgrade guide](https://www.mongodb.com/docs/drivers/csharp/current/upgrade/v3/) for MongoDB Driver 3 to ensure a smooth migration process.

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

@ -0,0 +1,7 @@
# ABP Version 9.1 Migration Guide
This document is a guide for upgrading ABP v9.0 solutions to ABP v9.1. There are no breaking changes in this version that would affect your application.
Only you might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the following migration guide:
- [OpenIddict 5.x to 6.x Migration Guide](./openiddict5-to-6.md)

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

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

106
docs/en/samples/index.md

@ -1,6 +1,6 @@
# ABP Samples # ABP Samples
Here, a list of official samples built with ABP. This document provides a list of samples built with ABP. Each sample is briefly explained below, along with its live demo (if available), source code, and tutorial links (where applicable).
## Event Hub ## Event Hub
@ -11,13 +11,13 @@ A reference application built with ABP. It implements the Domain Driven Design w
## eShopOnAbp ## eShopOnAbp
> ⚠️ **Important Notice**
> This project, "eShopOnAbp," is outdated. It served as a reference project for microservice architecture using the ABP Framework, but we now recommend using the [ABP Microservice Solution Template](https://abp.io/docs/latest/solution-templates/microservice) for new projects.
Reference microservice solution built with ABP and .NET. Reference microservice solution built with ABP and .NET.
* [Source code](https://github.com/abpframework/eShopOnAbp) * [Source code](https://github.com/abpframework/eShopOnAbp)
> ⚠️ **Important Notice**
> This project, "eShopOnAbp," is outdated. It served as a reference project for microservice architecture using the ABP Framework, but we now recommend using the [ABP Microservice Solution Template](https://abp.io/docs/latest/solution-templates/microservice) for new projects.
## CMS Kit Demo ## CMS Kit Demo
A minimal example website built with the [CMS Kit module](../modules/cms-kit/index.md). A minimal example website built with the [CMS Kit module](../modules/cms-kit/index.md).
@ -34,92 +34,48 @@ A middle-size CRM application built with ABP.
## Book Store ## Book Store
A simple CRUD application to show basic principles of developing an application with ABP. The same sample was implemented with different technologies and different modules. A simple CRUD application to show basic principles of developing an application with ABP. The same sample was implemented with different technologies and different modules:
### With Open Source Modules
The following samples uses only the open source (free) modules.
* **Book Store: Razor Pages UI & Entity Framework Core** * **Book Store: Razor Pages UI & Entity Framework Core**
* [Tutorial](../tutorials/book-store/part-01.md?UI=MVC&DB=EF) * [Tutorial](../tutorials/book-store/part-01.md?UI=MVC&DB=EF)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Download source code (with PRO modules) *](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-mvc-ef)
* **Book Store: Blazor UI & Entity Framework Core** * **Book Store: Blazor UI & Entity Framework Core**
* [Tutorial](../tutorials/book-store/part-01.md?UI=Blazor&DB=EF) * [Tutorial](../tutorials/book-store/part-01.md?UI=Blazor&DB=EF)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Download source code (with PRO modules) *](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-blazor-efcore)
* **Book Store: Angular UI & MongoDB** * **Book Store: Angular UI & MongoDB**
* [Tutorial](../tutorials/book-store/part-01.md?UI=NG&DB=Mongo) * [Tutorial](../tutorials/book-store/part-01.md?UI=NG&DB=Mongo)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
* [Download source code (with PRO modules) *](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-angular-mongodb)
* **Book Store: Modular application (Razor Pages UI & EF Core)** * **Book Store: Modular application (Razor Pages UI & EF Core)**
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular)
### With Pro Modules If you want to create the BookStore application and generate CRUD pages automatically with ABP Suite, please refer to the [Book Store Application (with ABP Suite) tutorial](../tutorials/book-store-with-abp-suite/part-01.md). Also, you can follow the [Mobile Application Development Tutorials](../tutorials/mobile/index.md), if you want to implement the CRUD operations for [MAUI](../tutorials/mobile/maui/index.md) & [React Native](../tutorials/mobile/react-native/index.md) mobile applications.
The following samples uses the pro modules. > **Note:** _Downloading source codes (with PRO modules) \*_ require an active [ABP License](https://abp.io/pricing).
- **Book Store: Razor Pages (MVC) UI & Entity Framework Core** ## ModularCRM
- [Tutorial](../tutorials/book-store/part-01.md?UI=MVC&DB=EF)
- [Download the source code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-mvc-ef) A modular monolith application that demonstrates how to create, compose, and communicate between application modules to build a modular web application:
- **Book Store: Blazor UI & Entity Framework Core**
- [Tutorial](../tutorials/book-store/part-01.md?UI=Blazor&DB=EF) * **ModularCRM: Razor Pages UI & Entity Framework Core**
- [Download the source code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-blazor-efcore) * [Tutorial](../tutorials/modular-crm/part-01.md?UI=MVC&DB=EF)
- **Book Store: Angular UI & MongoDB** * [Source code](https://github.com/abpframework/abp-samples/tree/master/ModularCrm)
- [Tutorial](../tutorials/book-store/part-01.md?UI=NG&DB=Mongo)
- [Download the source code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-angular-mongodb) ## CloudCrm
> This tutorial & sample application is suitable for those who have an [ABP Business or a higher license](https://abp.io/pricing).
A microservice solution that shows how to start a new microservice solution, create services and communicate between these services. It's a reference tutorial to learn to use these services from a web application through an API gateway and automatically generate CRUD pages using the ABP Suite tool:
* **CloudCRM: Razor Pages UI & Entity Framework Core**
* [Tutorial](../tutorials/microservice/part-01.md?UI=MVC&DB=EF)
* [Download source code](https://abp.io/api/download/samples/cloud-crm-mvc-ef)
## Other Samples ## Other Samples
* **Event Organizer**: A sample application to create events (meetups) and allow others to register the events. Developed using EF Core and Blazor UI. ABP Platform provides many sample applications demonstrating various use cases and integrations. You can:
* [Source code](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer)
* [Article](https://abp.io/community/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) * Browse all sample applications in the [abp-samples repository](https://github.com/abpframework/abp-samples).
* **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules. * Read detailed articles and tutorials in the [ABP Community](https://abp.io/community), which are shared by ABP Community & Contributors.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)
* [EF Core database migrations document](../framework/data/entity-framework-core/migrations.md)
* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo)
* [SignalR Integration document](../framework/real-time/signalr.md)
* **Real Time Messaging In A Distributed Architecture** (using SingalR & RabbitMQ)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo)
* [Article](https://abp.io/community/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17)
* **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo)
* [Widget documentation](../framework/ui/mvc-razor-pages/widgets.md)
* **RabbitMQ Event Bus Demo**: A solution consists of two applications communicating to each other via distributed events with RabbitMQ integration.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/RabbitMqEventBus)
* [Distributed event bus document](../framework/infrastructure/event-bus/distributed)
* [RabbitMQ distributed event bus integration document](../framework/infrastructure/event-bus/distributed/rabbitmq.md)
* **Text Templates Demo**: Shows different use cases of the text templating system.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo)
* [Text templating documentation](../framework/infrastructure/text-templating)
* **Stored Procedure Demo**: Demonstrates how to use stored procedures, database views and functions with best practices.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)
* **Passwordless Authentication**: Shows how to add a custom token provider to authenticate a user with a link, instead of entering a password.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication)
* [Article](https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj)
* **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)
* Related articles:
* [Azure Active Directory Authentication](https://abp.io/community/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf)
* [Customize the Login Page](https://abp.io/community/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd)
* [Customize the SignIn Manager](https://abp.io/community/articles/how-to-customize-the-signin-manager-3e858753)
* **GRPC Demo**: Shows how to add a gRPC service to an ABP based web application and consume it from a console application.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)
* **Telerik Blazor Integration**: Shows how to install and use Telerik Blazor components with ABP.
* [Article](https://abp.io/community/articles/how-to-integrate-the-telerik-blazor-components-to-the-abp-blazor-ui-q8g31abb)
* **Angular Material Integration**: Implemented the web application tutorial using the Angular Material library.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreAngularMaterial)
* [Article](https://abp.io/community/articles/using-angular-material-components-with-the-abp-framework-af8ft6t9)
* **DevExtreme Angular Component Integration**: How to install and use DevExtreme components in the ABP Angular UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular)
* [Article](https://abp.io/community/articles/using-devextreme-angular-components-with-the-abp-framework-x5nyvj3i)
* **DevExtreme MVC / Razor Pages Component Integration**: How to install and use DevExtreme components in the ABP MVC / Razor Pages UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc)
* [Article](https://abp.io/community/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv)
* **Syncfusion Blazor Integration**: Shows how to install and integrate Syncfusion UI the ABP Blazor UI.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/SyncfusionSample)
* [Article](https://abp.io/community/articles/using-syncfusion-components-with-the-abp-framework-5ccvi8kc)
* **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with ABP installed.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)
* [Documentation](../get-started/empty-aspnet-core-application.md)
* **Using Elsa Workflow with ABP**: Shows how to use the Elsa Core workflow library within an ABP-based application.
* [Source code](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo)
* [Article](https://abp.io/community/articles/using-elsa-workflow-with-the-abp-framework-773siqi9)

217
docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md

@ -15,7 +15,7 @@ This guide will guide you through how to build docker images for your applicatio
## Building Docker Images ## Building Docker Images
Each application contains a dockerfile called `Dockerfile.local` for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the project to be built in `Release` mode to create the image. Currently, if you are building your images using CI & CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own [multi-stage dockerfiles](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-7.0). Each application contains a dockerfile called `Dockerfile.local` for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the project to be built in `Release` mode to create the image. Currently, if you are building your images using CI & CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own [multi-stage dockerfiles](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-9.0).
Since they are not multi-staged Dockerfiles, if you want to build the images individually, you can navigate to the related to-be-hosted application folder and run the following command: Since they are not multi-staged Dockerfiles, if you want to build the images individually, you can navigate to the related to-be-hosted application folder and run the following command:
@ -31,7 +31,7 @@ docker build -f Dockerfile.local -t mycompanyname/myappname:version .
To manually build your application image. To manually build your application image.
To ease the process, application templates provide a build script to build all the images with a single script under `etc/build` folder named `build-images-locally.ps1`. To ease the process, application templates provide a build script to build all the images with a single script under `etc/docker-compose` folder named `build-images-locally.ps1`.
Based on your application name, UI and type, a build image script will be generated. Based on your application name, UI and type, a build image script will be generated.
{{ if UI == "MVC"}} {{ if UI == "MVC"}}
@ -204,8 +204,8 @@ DbMigrator is a console application that is used to migrate the database of your
`Dockerfile.local` is provided under this project as below; `Dockerfile.local` is provided under this project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 FROM mcr.microsoft.com/dotnet/aspnet:9.0
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
ENTRYPOINT ["dotnet", "BookStore.DbMigrator.dll"] ENTRYPOINT ["dotnet", "BookStore.DbMigrator.dll"]
``` ```
@ -226,7 +226,7 @@ docker build -f Dockerfile.local -t acme/bookstore-db-migrator:latest . #Builds
In the **WebModule** under authentication configuration, there is an extra configuration for containerized environment support: In the **WebModule** under authentication configuration, there is an extra configuration for containerized environment support:
```csharp ```csharp
if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"]))
{ {
context.Services.Configure<OpenIdConnectOptions>("oidc", options => context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
{ {
@ -268,13 +268,13 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"]))
This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect. This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect.
The `appsettings.json` file does not contain `AuthServer:IsContainerizedOnLocalhost` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. The `appsettings.json` file does not contain `AuthServer:IsOnK8s` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file.
`Dockerfile.local` is provided under this project as below; `Dockerfile.local` is provided under this project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 FROM mcr.microsoft.com/dotnet/aspnet:9.0
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
ENTRYPOINT ["dotnet", "Acme.BookStore.Web.dll"] ENTRYPOINT ["dotnet", "Acme.BookStore.Web.dll"]
``` ```
@ -289,11 +289,11 @@ docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the imag
​ {{ end }} {{ if Tiered == "No" }}MVC/Razor Pages application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below; ​ {{ end }} {{ if Tiered == "No" }}MVC/Razor Pages application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED
@ -318,36 +318,17 @@ if (!hostingEnvironment.IsDevelopment())
options.AddDevelopmentEncryptionAndSigningCertificate = false; options.AddDevelopmentEncryptionAndSigningCertificate = false;
}); });
PreConfigure<OpenIddictServerBuilder>(builder => PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{ {
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]));
}); });
} }
``` ```
This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization.
The `GetSigningCertificate` method is a private method located under the same **WebModule**: > You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**.
```csharp
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
{
var fileName = "authserver.pfx";
var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
if (!File.Exists(file))
{
throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
}
return new X509Certificate2(file, passPhrase);
}
```
> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically.
If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **Web** folder and run: If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **Web** folder and run:
@ -369,7 +350,7 @@ docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the imag
In the **BlazorModule** under authentication configuration, there is an extra configuration for containerized environment support: In the **BlazorModule** under authentication configuration, there is an extra configuration for containerized environment support:
```csharp ```csharp
if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"]))
{ {
context.Services.Configure<OpenIdConnectOptions>("oidc", options => context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
{ {
@ -411,13 +392,13 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"]))
This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect. This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect.
The `appsettings.json` file does not contain `AuthServer:IsContainerizedOnLocalhost` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. The `appsettings.json` file does not contain `AuthServer:IsOnK8s` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file.
`Dockerfile.local` is provided under this project as below; `Dockerfile.local` is provided under this project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 FROM mcr.microsoft.com/dotnet/aspnet:9.0
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
ENTRYPOINT ["dotnet", "Acme.BookStore.Blazor.dll"] ENTRYPOINT ["dotnet", "Acme.BookStore.Blazor.dll"]
``` ```
@ -432,11 +413,11 @@ docker build -f Dockerfile.local -t acme/bookstore-blazor:latest . #Builds the i
​ {{ end }} {{ if Tiered == "No" }}Blazor Server application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below; ​ {{ end }} {{ if Tiered == "No" }}Blazor Server application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED
@ -461,36 +442,17 @@ if (!hostingEnvironment.IsDevelopment())
options.AddDevelopmentEncryptionAndSigningCertificate = false; options.AddDevelopmentEncryptionAndSigningCertificate = false;
}); });
PreConfigure<OpenIddictServerBuilder>(builder => PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{ {
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]));
}); });
} }
``` ```
This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization.
The `GetSigningCertificate` method is a private method located under the same **BlazorModule**: > You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**.
```csharp
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
{
var fileName = "authserver.pfx";
var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
if (!File.Exists(file))
{
throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
}
return new X509Certificate2(file, passPhrase);
}
```
> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically.
If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **BlazorModule** folder and run: If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **BlazorModule** folder and run:
@ -562,7 +524,7 @@ server {
} }
``` ```
This configuration allows returning the `dynamic-env.json` file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. **The `dynamic-env.json` file you need to override is located under `aspnet-core/etc/docker`** folder. This configuration allows returning the `dynamic-env.json` file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. **The `dynamic-env.json` file you need to override is located under `aspnet-core/etc/docker-compose`** folder.
​ {{ if Tiered == "No" }} ​ {{ if Tiered == "No" }}
@ -645,8 +607,8 @@ docker build -f Dockerfile.local -t acme/bookstore-angular:latest . #Builds the
The Blazor application uses [nginx:alpine-slim](https://hub.docker.com/layers/library/nginx/alpine-slim/images/sha256-0f859db466fda2c52f62b48d0602fb26867d98edbd62c26ae21414b3dea8d8f4?context=explore) base image to host the blazor application. You can modify the base image based on your preference in the `Dockerfile.local` which provided under the Blazor folder of your solution as below; The Blazor application uses [nginx:alpine-slim](https://hub.docker.com/layers/library/nginx/alpine-slim/images/sha256-0f859db466fda2c52f62b48d0602fb26867d98edbd62c26ae21414b3dea8d8f4?context=explore) base image to host the blazor application. You can modify the base image based on your preference in the `Dockerfile.local` which provided under the Blazor folder of your solution as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS build FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS build
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
FROM nginx:alpine-slim AS final FROM nginx:alpine-slim AS final
WORKDIR /usr/share/nginx/html WORKDIR /usr/share/nginx/html
@ -701,11 +663,11 @@ docker build -f Dockerfile.local -t acme/bookstore-blazor:latest . #Builds the i
This is the backend application that contains the openid-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below; This is the backend application that contains the openid-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED
@ -730,36 +692,17 @@ if (!hostingEnvironment.IsDevelopment())
options.AddDevelopmentEncryptionAndSigningCertificate = false; options.AddDevelopmentEncryptionAndSigningCertificate = false;
}); });
PreConfigure<OpenIddictServerBuilder>(builder => PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{ {
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]));
}); });
} }
``` ```
This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization.
The `GetSigningCertificate` method is a private method located under the same **HttpApiHostModule**: > You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**.
```csharp
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
{
var fileName = "authserver.pfx";
var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
if (!File.Exists(file))
{
throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
}
return new X509Certificate2(file, passPhrase);
}
```
> You can always create any self-signed certificate using any other tooling outside of the dockerfile. You need to keep in mind to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically.
If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run: If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run:
@ -777,11 +720,11 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag
This is the backend application that contains the OpenID-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below; This is the backend application that contains the OpenID-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED
@ -806,36 +749,17 @@ if (!hostingEnvironment.IsDevelopment())
options.AddDevelopmentEncryptionAndSigningCertificate = false; options.AddDevelopmentEncryptionAndSigningCertificate = false;
}); });
PreConfigure<OpenIddictServerBuilder>(builder => PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{ {
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]));
}); });
} }
``` ```
This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different customization options. This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization.
The `GetSigningCertificate` method is a private method located under the same **HttpApiHostModule**:
```csharp
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
{
var fileName = "authserver.pfx";
var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
if (!File.Exists(file))
{
throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
}
return new X509Certificate2(file, passPhrase);
}
```
> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. > You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**.
If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run: If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run:
@ -855,11 +779,11 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag
This is the openid-provider application, the authentication server, which should be individually hosted compared to non-tiered application templates. The `dockerfile.local` is located under the `AuthServer` project as below; This is the openid-provider application, the authentication server, which should be individually hosted compared to non-tiered application templates. The `dockerfile.local` is located under the `AuthServer` project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED
@ -884,36 +808,17 @@ if (!hostingEnvironment.IsDevelopment())
options.AddDevelopmentEncryptionAndSigningCertificate = false; options.AddDevelopmentEncryptionAndSigningCertificate = false;
}); });
PreConfigure<OpenIddictServerBuilder>(builder => PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{ {
builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!);
builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]));
}); });
} }
``` ```
This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization.
The `GetSigningCertificate` method is a private method located under the same **AuthServerModule**: > You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**.
```csharp
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
{
var fileName = "authserver.pfx";
var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
if (!File.Exists(file))
{
throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
}
return new X509Certificate2(file, passPhrase);
}
```
> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically.
If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **AuthServer** folder and run: If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **AuthServer** folder and run:
@ -927,8 +832,8 @@ docker build -f Dockerfile.local -t acme/bookstore-authserver:latest . #Builds t
This is the backend application that exposes the endpoints and swagger UI. It is not a multi-stage dockerfile; hence you need to have already built this application in **Release mode** to use this dockerfile. The `dockerfile.local` is located under the `Http.Api.Host` project as below; This is the backend application that exposes the endpoints and swagger UI. It is not a multi-stage dockerfile; hence you need to have already built this application in **Release mode** to use this dockerfile. The `dockerfile.local` is located under the `Http.Api.Host` project as below;
```dockerfile ```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:7.0 FROM mcr.microsoft.com/dotnet/aspnet:9.0
COPY bin/Release/net7.0/publish/ app/ COPY bin/Release/net9.0/publish/ app/
WORKDIR /app WORKDIR /app
ENTRYPOINT ["dotnet", "Acme.BookStore.HttpApi.Host.dll"] ENTRYPOINT ["dotnet", "Acme.BookStore.HttpApi.Host.dll"]
``` ```
@ -944,7 +849,7 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag
## Running Docker-Compose on Localhost ## Running Docker-Compose on Localhost
Under the `etc/docker` folder, you can find the `docker-compose.yml` to run your application. To ease the running process, the template provides `run-docker.ps1` (and `run-docker.sh`) scripts that handle the HTTPS certificate creation, which is used in environment variables; Under the `etc/docker-compose` folder, you can find the `docker-compose.yml` to run your application. To ease the running process, the template provides `run-docker.ps1` (and `run-docker.sh`) scripts that handle the HTTPS certificate creation, which is used in environment variables;
```powershell ```powershell
$currentFolder = $PSScriptRoot $currentFolder = $PSScriptRoot
@ -1203,7 +1108,7 @@ This is the angular application we deploy on http://localhost:4200 by default us
> Don't forget to rebuild the `acme/bookstore-angular:latest` image after updating the `nginx.conf` file. > Don't forget to rebuild the `acme/bookstore-angular:latest` image after updating the `nginx.conf` file.
The bookstore-angular service mounts the `etc/docker/dynamic-env.json` file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. **Do not forget to override the `dynamic-env.json` located under the `aspnet-core/etc/docker`** folder. The bookstore-angular service mounts the `etc/docker-compose/dynamic-env.json` file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. **Do not forget to override the `dynamic-env.json` located under the `aspnet-core/etc/docker-compose`** folder.
> If you are not using Docker with WSL, you may have problems with the volume mount permissions. You need to grant docker to be able to use the local file system. See this [SO answer](https://stackoverflow.com/a/20652410) for more information. > If you are not using Docker with WSL, you may have problems with the volume mount permissions. You need to grant docker to be able to use the local file system. See this [SO answer](https://stackoverflow.com/a/20652410) for more information.
@ -1438,7 +1343,7 @@ bookstore-web:
- Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc
- App__SelfUrl=https://localhost:44353 - App__SelfUrl=https://localhost:44353
- AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }} - AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }}
- AuthServer__IsContainerizedOnLocalhost=true - AuthServer__IsOnK8s=true
- AuthServer__Authority=https://localhost:44334/ - AuthServer__Authority=https://localhost:44334/
- RemoteServices__Default__BaseUrl=http://bookstore-api - RemoteServices__Default__BaseUrl=http://bookstore-api
- RemoteServices__AbpAccountPublic__BaseUrl=http://bookstore-authserver - RemoteServices__AbpAccountPublic__BaseUrl=http://bookstore-authserver
@ -1464,7 +1369,7 @@ This is the MVC/Razor Page application docker service is using the `acme/booksto
The MVC/Razor Page is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events: The MVC/Razor Page is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events:
```csharp ```csharp
if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"]))
{ {
context.Services.Configure<OpenIdConnectOptions>("oidc", options => context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
{ {
@ -1511,7 +1416,7 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"]))
{{ if Tiered == "Yes" }} {{ if Tiered == "Yes" }}
- `AuthServer__IsContainerizedOnLocalhost` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercepting the URLS for *authorization* and *logout* endpoints. - `AuthServer__IsOnK8s` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercepting the URLS for *authorization* and *logout* endpoints.
- `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default. - `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default.
@ -1691,7 +1596,7 @@ bookstore-blazor:
- Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc
- App__SelfUrl=https://localhost:44314 - App__SelfUrl=https://localhost:44314
- AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }} - AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }}
- AuthServer__IsContainerizedOnLocalhost=true - AuthServer__IsOnK8s=true
- AuthServer__Authority=https://localhost:44334/ - AuthServer__Authority=https://localhost:44334/
- AuthServer__MetaAddress=http://bookstore-authserver - AuthServer__MetaAddress=http://bookstore-authserver
- RemoteServices__Default__BaseUrl=http://bookstore-api - RemoteServices__Default__BaseUrl=http://bookstore-api
@ -1718,7 +1623,7 @@ This is the Blazor Server application Docker service is using the `acme/bookstor
The Blazor Server is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events: The Blazor Server is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events:
```csharp ```csharp
if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"]))
{ {
context.Services.Configure<OpenIdConnectOptions>("oidc", options => context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
{ {
@ -1765,7 +1670,7 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"]))
{{ if Tiered == "Yes" }} {{ if Tiered == "Yes" }}
- `AuthServer__IsContainerizedOnLocalhost` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercept the URLS for *authorization* and *logout* endpoints. - `AuthServer__IsOnK8s` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercept the URLS for *authorization* and *logout* endpoints.
- `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing the access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default. - `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing the access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default.

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

@ -214,3 +214,7 @@ This project is an application that hosts the API of the solution. It has its ow
Just like the default structure, this project contains the User Interface (UI) of the application. It contains razor pages, JavaScript files, style files, images and so on... Just like the default structure, this project contains the User Interface (UI) of the application. It contains razor pages, JavaScript files, style files, images and so on...
This project contains an `appsettings.json` file, but this time it does not have a connection string because it never connects to the database. Instead, it mainly contains the endpoint of the remote API server and the authentication server. This project contains an `appsettings.json` file, but this time it does not have a connection string because it never connects to the database. Instead, it mainly contains the endpoint of the remote API server and the authentication server.
### Docker Compose
The **docker-compose** configuration files in the `etc/docker-compose` folder is configured to run the solution with Docker. See [Docker Deployment using Docker Compose](deployment/deployment-docker-compose.md) for more information.

2
docs/en/studio/monitoring-applications.md

@ -48,7 +48,7 @@ public override void ConfigureServices(ServiceConfigurationContext context)
In this tab, you can view comprehensive overall information. You have the option to search by application name and filter by application state. To reset all filters, use the *Clear Filters* button. When you apply a filter header informations gonna refresh by filtered applications. In this tab, you can view comprehensive overall information. You have the option to search by application name and filter by application state. To reset all filters, use the *Clear Filters* button. When you apply a filter header informations gonna refresh by filtered applications.
- `Apps Running`: The number of applications running. It includes CLI and C# applications. In the example two C# microservice applications and one CLI application is running. - `Apps Running`: The number of applications running. It includes only C# applications. In the example, nine C# microservice applications are running.
- `Requests`: The number of HTTP requests received by all C# applications. - `Requests`: The number of HTTP requests received by all C# applications.
- `Events`: The number of [Distributed Event](../framework/infrastructure/event-bus/distributed) sent or received by all C# applications. - `Events`: The number of [Distributed Event](../framework/infrastructure/event-bus/distributed) sent or received by all C# applications.
- `Exceptions`: The number of exceptions thrown by all C# applications. - `Exceptions`: The number of exceptions thrown by all C# applications.

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

@ -2,6 +2,20 @@
This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version. This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version.
## 0.9.22 (2025-01-22)
* Allowed to display multiple installation notes.
* Showed **Inner Exceptions** in the Solution Runner side-panel
* Hidden logs when no selected application is present.
* Added full docker compose to template.
* Zipped microservice module template for better structuring the solution.
* Upgraded templates to version `9.0.4`.
## 0.9.21 (2025-01-09)
* Showed a db test connection message while testing the database connection.
* Fixed books sample's application service.
## 0.9.20 (2025-01-08) ## 0.9.20 (2025-01-08)
* Upgraded templates to version `9.0.3`. * Upgraded templates to version `9.0.3`.

8
docs/en/studio/running-applications.md

@ -230,3 +230,11 @@ CLI applications uses the [powershell](https://learn.microsoft.com/en-us/powersh
- `Remove`: This option allows you to delete the selected application. - `Remove`: This option allows you to delete the selected application.
> When CLI applications start chain icon won't be visible, because only C# applications can connect the ABP Studio. > When CLI applications start chain icon won't be visible, because only C# applications can connect the ABP Studio.
## Docker Compose
You can manually run applications using [Docker Compose](https://docs.docker.com/compose/). This allows for easy setup and management of multi-container Docker applications. To get started, ensure you have Docker and Docker Compose installed on your machine.
Refer to the [Deployment with Docker Compose](../solution-templates/layered-web-application/deployment/deployment-docker-compose.md) documentation for detailed instructions on how to configure and run your applications using `docker-compose`.
> Note: The **Docker Compose** is not available in the ABP Studio interface.

3
docs/en/studio/version-mapping.md

@ -4,7 +4,8 @@ This document provides a general overview of the relationship between various ve
| **ABP Studio Version** | **ABP Version of Startup Template** | | **ABP Studio Version** | **ABP Version of Startup Template** |
|------------------------|---------------------------| |------------------------|---------------------------|
| 0.9.20 | 9.0.3 | | 0.9.22 | 9.0.4 |
| 0.9.20 - 0.9.21 | 9.0.3 |
| 0.9.17 to 0.9.19 | 9.0.2 | | 0.9.17 to 0.9.19 | 9.0.2 |
| 0.9.15 - 0.9.16 | 9.0.1 | | 0.9.15 - 0.9.16 | 9.0.1 |
| 0.9.9 to 0.9.14 | 9.0.0 | | 0.9.9 to 0.9.14 | 9.0.0 |

7
docs/en/tutorials/book-store/part-07.md

@ -180,7 +180,7 @@ public class MongoDbAuthorRepository
public async Task<Author> FindByNameAsync(string name) public async Task<Author> FindByNameAsync(string name)
{ {
var queryable = await GetMongoQueryableAsync(); var queryable = await GetQueryableAsync();
return await queryable.FirstOrDefaultAsync(author => author.Name == name); return await queryable.FirstOrDefaultAsync(author => author.Name == name);
} }
@ -190,14 +190,13 @@ public class MongoDbAuthorRepository
string sorting, string sorting,
string filter = null) string filter = null)
{ {
var queryable = await GetMongoQueryableAsync(); var queryable = await GetQueryableAsync();
return await queryable return await queryable
.WhereIf<Author, IMongoQueryable<Author>>( .WhereIf<Author, IQueryable<Author>>(
!filter.IsNullOrWhiteSpace(), !filter.IsNullOrWhiteSpace(),
author => author.Name.Contains(filter) author => author.Name.Contains(filter)
) )
.OrderBy(sorting) .OrderBy(sorting)
.As<IMongoQueryable<Author>>()
.Skip(skipCount) .Skip(skipCount)
.Take(maxResultCount) .Take(maxResultCount)
.ToListAsync(); .ToListAsync();

BIN
docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 63 KiB

4
docs/en/tutorials/modular-crm/index.md

@ -29,8 +29,8 @@ This tutorial is organized as the following parts:
## Download the Source Code ## Download the Source Code
You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCRM). You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCrm).
## See Also ## See Also
* [Book Store: Web Application Development Tutorial](../book-store/index.md) * [Book Store: Web Application Development Tutorial](../book-store/index.md)

7
docs/en/tutorials/modular-crm/part-03.md

@ -387,7 +387,10 @@ Configure<AbpAspNetCoreMvcOptions>(options =>
options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly);
//ADD THE FOLLOWING LINE: //ADD THE FOLLOWING LINE:
options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly, settings =>
{
settings.RootPath = "products";
});
}); });
```` ````
@ -409,7 +412,7 @@ Once you see the user interface of the web application, type `/swagger` at the e
![abp-studio-swagger-ui-in-browser](images/abp-studio-swagger-ui-in-browser.png) ![abp-studio-swagger-ui-in-browser](images/abp-studio-swagger-ui-in-browser.png)
Expand the `/api/app/product` API and click the *Try it out* button as shown in the following figure: Expand the `/api/products/product` API and click the *Try it out* button as shown in the following figure:
![abp-studio-swagger-ui-create-product-try](images/abp-studio-swagger-ui-create-product-try.png) ![abp-studio-swagger-ui-create-product-try](images/abp-studio-swagger-ui-create-product-try.png)

16
docs/en/tutorials/modular-crm/part-05.md

@ -345,7 +345,9 @@ public class OrderAppService : OrderingAppService, IOrderAppService
} }
```` ````
Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method: ### Exposing Application Services as HTTP API Controllers
After implementing the application service, now we need to create HTTP API endpoints for the ordering module. For that purpose, open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method:
````csharp ````csharp
private void ConfigureAutoApiControllers() private void ConfigureAutoApiControllers()
@ -353,10 +355,16 @@ private void ConfigureAutoApiControllers()
Configure<AbpAspNetCoreMvcOptions>(options => Configure<AbpAspNetCoreMvcOptions>(options =>
{ {
options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly);
options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly, settings =>
{
settings.RootPath = "products";
});
//ADD THE FOLLOWING LINE: //ADD THE FOLLOWING LINE:
options.ConventionalControllers.Create(typeof(OrderingModule).Assembly); options.ConventionalControllers.Create(typeof(OrderingModule).Assembly, settings =>
{
settings.RootPath = "orders";
});
}); });
} }
```` ````
@ -373,7 +381,7 @@ Once you see the user interface of the web application, type `/swagger` at the e
![abp-studio-ordering-swagger-ui-in-browser](images/abp-studio-ordering-swagger-ui-in-browser.png) ![abp-studio-ordering-swagger-ui-in-browser](images/abp-studio-ordering-swagger-ui-in-browser.png)
Expand the `/api/app/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button: Expand the `/api/orders/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button:
![abp-studio-swagger-ui-create-order-execute](images/abp-studio-swagger-ui-create-order-execute.png) ![abp-studio-swagger-ui-create-order-execute](images/abp-studio-swagger-ui-create-order-execute.png)

15
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs

@ -37,9 +37,9 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
{ {
var defaultThemeName = context.BuildArgs.TemplateName is AppTemplate.TemplateName or AppNoLayersTemplate.TemplateName var defaultThemeName = context.BuildArgs.TemplateName is AppTemplate.TemplateName or AppNoLayersTemplate.TemplateName
? LeptonXLite : LeptonX; ? LeptonXLite : LeptonX;
new RemoveFilesStep($"/Themes/{defaultThemeName}").Execute(context); new RemoveFilesStep($"/Themes/{defaultThemeName}").Execute(context);
ChangeThemeToBasicForMvcProjects(context, defaultThemeName); ChangeThemeToBasicForMvcProjects(context, defaultThemeName);
ChangeThemeToBasicForBlazorProjects(context, defaultThemeName); ChangeThemeToBasicForBlazorProjects(context, defaultThemeName);
ChangeThemeToBasicForBlazorServerProjects(context, defaultThemeName); ChangeThemeToBasicForBlazorServerProjects(context, defaultThemeName);
@ -622,6 +622,11 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
return; return;
} }
if (!file.Content.Contains("ThemeModule"))
{
return;
}
file.NormalizeLineEndings(); file.NormalizeLineEndings();
var lines = file.GetLines().ToList(); var lines = file.GetLines().ToList();
@ -630,7 +635,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
{ {
lines.AddFirst(@namespace); lines.AddFirst(@namespace);
} }
file.SetLines(lines); file.SetLines(lines);
} }
@ -641,7 +646,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
".Web", ".AuthServer", ".Web.Public", ".Web.Public.Host", ".Web", ".AuthServer", ".Web.Public", ".Web.Public.Host",
"" //for app-nolayers-mvc "" //for app-nolayers-mvc
}; };
if(!context.Symbols.Contains("tiered")) if(!context.Symbols.Contains("tiered"))
{ {
projectNames.Add(".HttpApi.Host"); projectNames.Add(".HttpApi.Host");
@ -716,7 +721,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
context, context,
$"_Host.cshtml", $"_Host.cshtml",
$"{defaultThemeName}Theme.Components", $"{defaultThemeName}Theme.Components",
"BasicTheme.Themes.Basic" "BasicTheme.Themes.Basic"
); );
ReplaceAllKeywords( ReplaceAllKeywords(

24
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs

@ -31,6 +31,14 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
{ {
if (entityEntry.State is EntityState.Unchanged or EntityState.Modified)
{
foreach (var entry in EntityEntries.Values.Where(x => x.NavigationEntries.Any()))
{
entry.UpdateNavigationEntries();
}
}
if (entityEntry.State != EntityState.Unchanged) if (entityEntry.State != EntityState.Unchanged)
{ {
return; return;
@ -189,6 +197,22 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return navigationEntryProperty != null && navigationEntryProperty.IsModified; return navigationEntryProperty != null && navigationEntryProperty.IsModified;
} }
public virtual AbpNavigationEntry? GetNavigationEntry(EntityEntry entityEntry, int navigationEntryIndex)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return null;
}
if (!EntityEntries.TryGetValue(entryId, out var abpEntityEntry))
{
return null;
}
return abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex);
}
protected virtual string? GetEntityEntryIdentity(EntityEntry entityEntry) protected virtual string? GetEntityEntryIdentity(EntityEntry entityEntry)
{ {
if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1) if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1)

64
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -32,6 +33,49 @@ public class AbpEntityEntry
EntityEntry = entityEntry; EntityEntry = entityEntry;
NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList(); NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList();
} }
public void UpdateNavigationEntries()
{
foreach (var navigationEntry in NavigationEntries)
{
if (IsModified ||
EntityEntry.State == EntityState.Modified ||
navigationEntry.IsModified ||
navigationEntry.NavigationEntry.IsModified)
{
continue;
}
var navigation = EntityEntry.Navigations.FirstOrDefault(n => n.Metadata.Name == navigationEntry.Name);
var currentValue = AbpNavigationEntry.GetOriginalValue(navigation?.CurrentValue);
if (currentValue == null)
{
continue;
}
switch (navigationEntry.OriginalValue)
{
case null:
navigationEntry.OriginalValue = currentValue;
break;
case IEnumerable originalValueCollection when currentValue is IEnumerable currentValueCollection:
{
var existingList = originalValueCollection.Cast<object?>().ToList();
var newList = currentValueCollection.Cast<object?>().ToList();
if (newList.Count > existingList.Count)
{
navigationEntry.OriginalValue = currentValue;
}
break;
}
default:
navigationEntry.OriginalValue = currentValue;
break;
}
}
}
} }
public class AbpNavigationEntry public class AbpNavigationEntry
@ -42,9 +86,29 @@ public class AbpNavigationEntry
public bool IsModified { get; set; } public bool IsModified { get; set; }
public List<object>? OriginalValue { get; set; }
public object? CurrentValue => NavigationEntry.CurrentValue;
public AbpNavigationEntry(NavigationEntry navigationEntry, string name) public AbpNavigationEntry(NavigationEntry navigationEntry, string name)
{ {
NavigationEntry = navigationEntry; NavigationEntry = navigationEntry;
Name = name; Name = name;
OriginalValue = GetOriginalValue(navigationEntry.CurrentValue);
}
public static List<object>? GetOriginalValue(object? currentValue)
{
if (currentValue is null)
{
return null;
}
if (currentValue is IEnumerable enumerable)
{
return enumerable.Cast<object>().ToList();
}
return new List<object> { currentValue };
} }
} }

47
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -193,16 +194,20 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
} }
} }
if (Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null) if (AbpEfCoreNavigationHelper != null)
{ {
foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i )))
{ {
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index))
{ {
var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index);
var isCollection = navigationEntry.Metadata.IsCollection;
propertyChanges.Add(new EntityPropertyChangeInfo propertyChanges.Add(new EntityPropertyChangeInfo
{ {
PropertyName = navigationEntry.Metadata.Name, PropertyName = navigationEntry.Metadata.Name,
PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName! PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!,
OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection),
NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection)
}); });
} }
} }
@ -211,6 +216,44 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
return propertyChanges; return propertyChanges;
} }
protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection)
{
switch (entity)
{
case null:
return null;
case IEntity entryEntity:
var keys = entryEntity.GetKeys();
return keys.Length == 0 ? null : string.Join(", ",keys).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength);
case IEnumerable enumerable:
var keysList = new List<string>();
foreach (var item in enumerable)
{
var id = GetNavigationPropertyValue(item, false);
if (id != null)
{
keysList.Add(id);
}
}
if (keysList.Count == 0)
{
return null;
}
var serializedKeysEnumerable = keysList.Count == 1 && !isCollection
? keysList.First()
: JsonSerializer.Serialize(keysList);
return serializedKeysEnumerable.TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength);
default:
return null;
}
}
protected virtual bool IsCreated(EntityEntry entityEntry) protected virtual bool IsCreated(EntityEntry entityEntry)
{ {
return entityEntry.State == EntityState.Added; return entityEntry.State == EntityState.Added;

11
framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs

@ -102,6 +102,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{ {
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.GetCorrelationId(), outgoingEvent.Id);
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -111,8 +113,6 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.GetCorrelationId(), outgoingEvent.Id);
} }
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig) public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
@ -141,7 +141,12 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
throw new AbpException( throw new AbpException(
"The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number"); "The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number");
} }
}
await publisher.SendMessagesAsync(messageBatch);
foreach (var outgoingEvent in outgoingEventArray)
{
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -152,8 +157,6 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}); });
} }
} }
await publisher.SendMessagesAsync(messageBatch);
} }
public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig)

8
framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs

@ -153,6 +153,8 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{ {
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -162,8 +164,6 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
} }
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig) public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
@ -172,6 +172,8 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
foreach (var outgoingEvent in outgoingEventArray) foreach (var outgoingEvent in outgoingEventArray)
{ {
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -181,8 +183,6 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
} }
} }

38
framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs

@ -197,16 +197,6 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
OutgoingEventInfo outgoingEvent, OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig) OutboxConfig outboxConfig)
{ {
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
var headers = new Headers var headers = new Headers
{ {
{ "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) }
@ -222,6 +212,16 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
outgoingEvent.EventData, outgoingEvent.EventData,
headers headers
); );
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
} }
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig) public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
@ -242,6 +242,15 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!)); headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!));
} }
producer.Produce(
AbpKafkaEventBusOptions.TopicName,
new Message<string, byte[]>
{
Key = outgoingEvent.EventName,
Value = outgoingEvent.EventData,
Headers = headers
});
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -251,15 +260,6 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
producer.Produce(
AbpKafkaEventBusOptions.TopicName,
new Message<string, byte[]>
{
Key = outgoingEvent.EventName,
Value = outgoingEvent.EventData,
Headers = headers
});
} }
} }

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

@ -207,6 +207,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
OutgoingEventInfo outgoingEvent, OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig) OutboxConfig outboxConfig)
{ {
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, eventId: outgoingEvent.Id, correlationId: outgoingEvent.GetCorrelationId());
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -216,8 +218,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, eventId: outgoingEvent.Id, correlationId: outgoingEvent.GetCorrelationId());
} }
public async override Task PublishManyFromOutboxAsync( public async override Task PublishManyFromOutboxAsync(
@ -231,6 +231,13 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
foreach (var outgoingEvent in outgoingEventArray) foreach (var outgoingEvent in outgoingEventArray)
{ {
await PublishAsync(
channel,
outgoingEvent.EventName,
outgoingEvent.EventData,
eventId: outgoingEvent.Id,
correlationId: outgoingEvent.GetCorrelationId());
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -240,13 +247,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
await PublishAsync(
channel,
outgoingEvent.EventName,
outgoingEvent.EventData,
eventId: outgoingEvent.Id,
correlationId: outgoingEvent.GetCorrelationId());
} }
channel.WaitForConfirmsOrDie(); channel.WaitForConfirmsOrDie();

20
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs

@ -253,6 +253,14 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName)!; var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName)!;
var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType); var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType);
var headers = new Dictionary<string, string>();
if (outgoingEvent.GetCorrelationId() != null)
{
headers.Add(EventBusConsts.CorrelationIdHeaderName, outgoingEvent.GetCorrelationId()!);
}
await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id, headersArguments: headers);
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() { await TriggerDistributedEventSentAsync(new DistributedEventSent() {
@ -261,14 +269,6 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
var headers = new Dictionary<string, string>();
if (outgoingEvent.GetCorrelationId() != null)
{
headers.Add(EventBusConsts.CorrelationIdHeaderName, outgoingEvent.GetCorrelationId()!);
}
await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id, headersArguments: headers);
} }
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig) public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
@ -279,6 +279,8 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{ {
foreach (var outgoingEvent in outgoingEventArray) foreach (var outgoingEvent in outgoingEventArray)
{ {
await PublishFromOutboxAsync(outgoingEvent, outboxConfig);
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{ {
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
@ -288,8 +290,6 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
EventData = outgoingEvent.EventData EventData = outgoingEvent.EventData
}); });
} }
await PublishFromOutboxAsync(outgoingEvent, outboxConfig);
} }
await scope.CompleteAsync(); await scope.CompleteAsync();

6
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -85,14 +85,14 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
} }
} }
await PublishToEventBusAsync(eventType, eventData);
await TriggerDistributedEventSentAsync(new DistributedEventSent() await TriggerDistributedEventSentAsync(new DistributedEventSent()
{ {
Source = DistributedEventSource.Direct, Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType), EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData EventData = eventData
}); });
await PublishToEventBusAsync(eventType, eventData);
} }
public abstract Task PublishFromOutboxAsync( public abstract Task PublishFromOutboxAsync(

26
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs

@ -165,7 +165,6 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return false; return false;
} }
protected async override Task PublishToEventBusAsync(Type eventType, object eventData) protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{ {
await LocalEventBus.PublishAsync(eventType, eventData); await LocalEventBus.PublishAsync(eventType, eventData);
@ -176,4 +175,27 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{ {
return JsonSerializer.SerializeToUtf8Bytes(eventData); return JsonSerializer.SerializeToUtf8Bytes(eventData);
} }
}
private async Task PublishDistributedEventSentReceivedAsync(Type eventType, object eventData, bool onUnitOfWorkComplete)
{
if (eventType != typeof(DistributedEventSent))
{
await _localEventBus.PublishAsync(new DistributedEventSent
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
}, onUnitOfWorkComplete);
}
if (eventType != typeof(DistributedEventReceived))
{
await _localEventBus.PublishAsync(new DistributedEventReceived
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
}, onUnitOfWorkComplete);
}
}
}

3
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
namespace Volo.Abp.Http.Modeling; namespace Volo.Abp.Http.Modeling;
@ -46,7 +47,7 @@ public class TypeApiDescriptionModel
else else
{ {
typeModel.Properties = type typeModel.Properties = type
.GetProperties() .GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.DeclaringType == type) .Where(p => p.DeclaringType == type)
.Select(PropertyApiDescriptionModel.Create) .Select(PropertyApiDescriptionModel.Create)
.ToArray(); .ToArray();

11
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs

@ -1,8 +1,8 @@
using System; using System;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Domain.Repositories.MongoDB; namespace Volo.Abp.Domain.Repositories.MongoDB;
@ -20,10 +20,13 @@ public interface IMongoDbRepository<TEntity> : IRepository<TEntity>
Task<IMongoCollection<TEntity>> GetCollectionAsync(CancellationToken cancellationToken = default); Task<IMongoCollection<TEntity>> GetCollectionAsync(CancellationToken cancellationToken = default);
[Obsolete("Use GetMongoQueryableAsync method.")] [Obsolete("Use GetQueryable method.")]
IMongoQueryable<TEntity> GetMongoQueryable(); IQueryable<TEntity> GetMongoQueryable();
Task<IMongoQueryable<TEntity>> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null); [Obsolete("Use GetQueryableAsync method.")]
Task<IQueryable<TEntity>> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null);
Task<IQueryable<TEntity>> GetQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null);
Task<IAggregateFluent<TEntity>> GetAggregateAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null); Task<IAggregateFluent<TEntity>> GetAggregateAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null);
} }

53
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs

@ -455,19 +455,19 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
public async override Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) public async override Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)).ToListAsync(cancellationToken); return await (await GetQueryableAsync(cancellationToken)).ToListAsync(cancellationToken);
} }
public async override Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default) public async override Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)).Where(predicate).ToListAsync(cancellationToken); return await (await GetQueryableAsync(cancellationToken)).Where(predicate).ToListAsync(cancellationToken);
} }
public async override Task<long> GetCountAsync(CancellationToken cancellationToken = default) public async override Task<long> GetCountAsync(CancellationToken cancellationToken = default)
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)).LongCountAsync(cancellationToken); return await (await GetQueryableAsync(cancellationToken)).LongCountAsync(cancellationToken);
} }
public async override Task<List<TEntity>> GetPagedListAsync( public async override Task<List<TEntity>> GetPagedListAsync(
@ -479,10 +479,9 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.OrderByIf<TEntity, IQueryable<TEntity>>(!sorting.IsNullOrWhiteSpace(), sorting) .OrderByIf<TEntity, IQueryable<TEntity>>(!sorting.IsNullOrWhiteSpace(), sorting)
.As<IMongoQueryable<TEntity>>() .PageBy<TEntity, IQueryable<TEntity>>(skipCount, maxResultCount)
.PageBy<TEntity, IMongoQueryable<TEntity>>(skipCount, maxResultCount)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
} }
@ -493,14 +492,14 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
var entities = await (await GetMongoQueryableAsync(cancellationToken)) var entities = await (await GetQueryableAsync(cancellationToken))
.Where(predicate) .Where(predicate)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
await DeleteManyAsync(entities, autoSave, cancellationToken); await DeleteManyAsync(entities, autoSave, cancellationToken);
} }
public override async Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) public async override Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
@ -527,12 +526,16 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
[Obsolete("Use GetQueryableAsync method.")] [Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable<TEntity> GetQueryable() protected override IQueryable<TEntity> GetQueryable()
{ {
return GetMongoQueryable(); return ApplyDataFilters(
SessionHandle != null
? Collection.AsQueryable(SessionHandle)
: Collection.AsQueryable()
);
} }
public async override Task<IQueryable<TEntity>> GetQueryableAsync() public async override Task<IQueryable<TEntity>> GetQueryableAsync()
{ {
return await GetMongoQueryableAsync(); return await GetQueryableAsync();
} }
public async override Task<TEntity?> FindAsync( public async override Task<TEntity?> FindAsync(
@ -542,34 +545,36 @@ public class MongoDbRepository<TMongoDbContext, TEntity>
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.Where(predicate) .Where(predicate)
.SingleOrDefaultAsync(cancellationToken); .SingleOrDefaultAsync(cancellationToken);
} }
[Obsolete("Use GetMongoQueryableAsync method.")] [Obsolete("Use GetQueryableAsync method.")]
public virtual IMongoQueryable<TEntity> GetMongoQueryable() public virtual IQueryable<TEntity> GetMongoQueryable()
{ {
return ApplyDataFilters( return GetQueryable();
SessionHandle != null }
? Collection.AsQueryable(SessionHandle)
: Collection.AsQueryable() [Obsolete("Use GetQueryableAsync method.")]
); public virtual Task<IQueryable<TEntity>> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null)
{
return GetQueryableAsync<TEntity>(cancellationToken, options);
} }
public virtual Task<IMongoQueryable<TEntity>> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) public virtual async Task<IQueryable<TEntity>> GetQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null)
{ {
return GetMongoQueryableAsync<TEntity>(cancellationToken, aggregateOptions); return await GetQueryableAsync<TEntity>(cancellationToken, options);
} }
protected virtual async Task<IMongoQueryable<TOtherEntity>> GetMongoQueryableAsync<TOtherEntity>(CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) protected virtual async Task<IQueryable<TOtherEntity>> GetQueryableAsync<TOtherEntity>(CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null)
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
var dbContext = await GetDbContextAsync(cancellationToken); var dbContext = await GetDbContextAsync(cancellationToken);
var collection = dbContext.Collection<TOtherEntity>(); var collection = dbContext.Collection<TOtherEntity>();
return ApplyDataFilters<IMongoQueryable<TOtherEntity>, TOtherEntity>( return ApplyDataFilters<IQueryable<TOtherEntity>, TOtherEntity>(
dbContext.SessionHandle != null dbContext.SessionHandle != null
? collection.AsQueryable(dbContext.SessionHandle, aggregateOptions) ? collection.AsQueryable(dbContext.SessionHandle, aggregateOptions)
: collection.AsQueryable(aggregateOptions) : collection.AsQueryable(aggregateOptions)
@ -810,7 +815,7 @@ public class MongoDbRepository<TMongoDbContext, TEntity, TKey>
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.Where(x => x.Id!.Equals(id)) .Where(x => x.Id!.Equals(id))
.FirstOrDefaultAsync(cancellationToken); .FirstOrDefaultAsync(cancellationToken);
} }
@ -827,7 +832,7 @@ public class MongoDbRepository<TMongoDbContext, TEntity, TKey>
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
var entities = await (await GetMongoQueryableAsync(cancellationToken)) var entities = await (await GetQueryableAsync(cancellationToken))
.Where(x => ids.Contains(x.Id)) .Where(x => ids.Contains(x.Id))
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);

15
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs

@ -1,8 +1,8 @@
using System; using System;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Domain.Repositories.MongoDB;
@ -36,19 +36,26 @@ public static class MongoDbCoreRepositoryExtensions
return repository.ToMongoDbRepository().GetCollectionAsync(cancellationToken); return repository.ToMongoDbRepository().GetCollectionAsync(cancellationToken);
} }
[Obsolete("Use GetMongoQueryableAsync method.")] [Obsolete("Use GetQueryableAsync method.")]
public static IMongoQueryable<TEntity> GetMongoQueryable<TEntity>(this IReadOnlyBasicRepository<TEntity> repository) public static IQueryable<TEntity> GetMongoQueryable<TEntity>(this IReadOnlyBasicRepository<TEntity> repository)
where TEntity : class, IEntity where TEntity : class, IEntity
{ {
return repository.ToMongoDbRepository().GetMongoQueryable(); return repository.ToMongoDbRepository().GetMongoQueryable();
} }
public static Task<IMongoQueryable<TEntity>> GetMongoQueryableAsync<TEntity>(this IReadOnlyBasicRepository<TEntity> repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) [Obsolete("Use GetQueryableAsync method.")]
public static Task<IQueryable<TEntity>> GetMongoQueryableAsync<TEntity>(this IReadOnlyBasicRepository<TEntity> repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null)
where TEntity : class, IEntity where TEntity : class, IEntity
{ {
return repository.ToMongoDbRepository().GetMongoQueryableAsync(cancellationToken, aggregateOptions); return repository.ToMongoDbRepository().GetMongoQueryableAsync(cancellationToken, aggregateOptions);
} }
public static Task<IQueryable<TEntity>> GetQueryableAsync<TEntity>(this IReadOnlyBasicRepository<TEntity> repository)
where TEntity : class, IEntity
{
return repository.ToMongoDbRepository().GetQueryableAsync();
}
public static Task<IAggregateFluent<TEntity>> GetAggregateAsync<TEntity>(this IReadOnlyBasicRepository<TEntity> repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) public static Task<IAggregateFluent<TEntity>> GetAggregateAsync<TEntity>(this IReadOnlyBasicRepository<TEntity> repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null)
where TEntity : class, IEntity where TEntity : class, IEntity
{ {

2
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs

@ -5,7 +5,7 @@ using MongoDB.Bson.Serialization.Serializers;
namespace Volo.Abp.MongoDB; namespace Volo.Abp.MongoDB;
public class AbpMongoDbDateTimeSerializer : DateTimeSerializer public class AbpMongoDbDateTimeSerializer : StructSerializerBase<DateTime>
{ {
protected DateTimeKind DateTimeKind { get; set; } protected DateTimeKind DateTimeKind { get; set; }
protected bool DisableDateTimeNormalization { get; set; } protected bool DisableDateTimeNormalization { get; set; }

5
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs

@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -19,6 +22,8 @@ public class AbpMongoDbModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
BsonSerializer.TryRegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
context.Services.TryAddTransient( context.Services.TryAddTransient(
typeof(IMongoDbContextProvider<>), typeof(IMongoDbContextProvider<>),
typeof(UnitOfWorkMongoDbContextProvider<>) typeof(UnitOfWorkMongoDbContextProvider<>)

1
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs

@ -68,7 +68,6 @@ public class MongoDbContextEventInbox<TMongoDbContext> : IMongoDbContextEventInb
.WhereIf(transformedFilter != null, transformedFilter!) .WhereIf(transformedFilter != null, transformedFilter!)
.OrderBy(x => x.CreationTime) .OrderBy(x => x.CreationTime)
.Take(maxCount) .Take(maxCount)
.As<IMongoQueryable<IncomingEventRecord>>()
.ToListAsync(cancellationToken: cancellationToken); .ToListAsync(cancellationToken: cancellationToken);
return outgoingEventRecords return outgoingEventRecords

1
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs

@ -56,7 +56,6 @@ public class MongoDbContextEventOutbox<TMongoDbContext> : IMongoDbContextEventOu
.WhereIf(transformedFilter != null, transformedFilter!) .WhereIf(transformedFilter != null, transformedFilter!)
.OrderBy(x => x.CreationTime) .OrderBy(x => x.CreationTime)
.Take(maxCount) .Take(maxCount)
.As<IMongoQueryable<OutgoingEventRecord>>()
.ToListAsync(cancellationToken: cancellationToken); .ToListAsync(cancellationToken: cancellationToken);
return outgoingEventRecords return outgoingEventRecords

141
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs

@ -6,9 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Linq; using Volo.Abp.Linq;
using MongoDB.Driver;
using MongoDB.Driver.Linq; using MongoDB.Driver.Linq;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.MongoDB; namespace Volo.Abp.MongoDB;
@ -16,344 +14,339 @@ public class MongoDbAsyncQueryableProvider : IAsyncQueryableProvider, ISingleton
{ {
public bool CanExecute<T>(IQueryable<T> queryable) public bool CanExecute<T>(IQueryable<T> queryable)
{ {
return ProxyHelper.UnProxy(queryable) is IMongoQueryable<T>; return queryable.Provider is IMongoQueryProvider;
}
protected virtual IMongoQueryable<T> GetMongoQueryable<T>(IQueryable<T> queryable)
{
return ProxyHelper.UnProxy(queryable).As<IMongoQueryable<T>>();
} }
public Task<bool> ContainsAsync<T>(IQueryable<T> queryable, T item, CancellationToken cancellationToken = default) public Task<bool> ContainsAsync<T>(IQueryable<T> queryable, T item, CancellationToken cancellationToken = default)
{ {
return Task.FromResult(GetMongoQueryable(queryable).Contains(item)); return Task.FromResult(queryable.Contains(item));
} }
public Task<bool> AnyAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<bool> AnyAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AnyAsync(cancellationToken); return queryable.AnyAsync(cancellationToken);
} }
public Task<bool> AnyAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<bool> AnyAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AnyAsync(predicate, cancellationToken); return queryable.AnyAsync(predicate, cancellationToken);
} }
public Task<bool> AllAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<bool> AllAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return Task.FromResult(GetMongoQueryable(queryable).All(predicate)); return Task.FromResult(queryable.All(predicate));
} }
public Task<int> CountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<int> CountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).CountAsync(cancellationToken); return queryable.CountAsync(cancellationToken);
} }
public Task<int> CountAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<int> CountAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).CountAsync(predicate, cancellationToken); return queryable.CountAsync(predicate, cancellationToken);
} }
public Task<long> LongCountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<long> LongCountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).LongCountAsync(cancellationToken); return queryable.LongCountAsync(cancellationToken);
} }
public Task<long> LongCountAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<long> LongCountAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).LongCountAsync(predicate, cancellationToken); return queryable.LongCountAsync(predicate, cancellationToken);
} }
public Task<T> FirstAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T> FirstAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).FirstAsync(cancellationToken); return queryable.FirstAsync(cancellationToken);
} }
public Task<T> FirstAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<T> FirstAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).FirstAsync(predicate, cancellationToken); return queryable.FirstAsync(predicate, cancellationToken);
} }
public Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).FirstOrDefaultAsync(cancellationToken)!; return queryable.FirstOrDefaultAsync(cancellationToken)!;
} }
public Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, public Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).FirstOrDefaultAsync(predicate, cancellationToken)!; return queryable.FirstOrDefaultAsync(predicate, cancellationToken)!;
} }
public Task<T> LastAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T> LastAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return Task.FromResult(GetMongoQueryable(queryable).Last()); return Task.FromResult(queryable.Last());
} }
public Task<T> LastAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<T> LastAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return Task.FromResult(GetMongoQueryable(queryable).Last(predicate)); return Task.FromResult(queryable.Last(predicate));
} }
public Task<T?> LastOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T?> LastOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return Task.FromResult<T?>(GetMongoQueryable(queryable).LastOrDefault()); return Task.FromResult<T?>(queryable.LastOrDefault());
} }
public Task<T?> LastOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, public Task<T?> LastOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return Task.FromResult<T?>(GetMongoQueryable(queryable).LastOrDefault(predicate)); return Task.FromResult<T?>(queryable.LastOrDefault(predicate));
} }
public Task<T> SingleAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T> SingleAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SingleAsync(cancellationToken); return queryable.SingleAsync(cancellationToken);
} }
public Task<T> SingleAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default) public Task<T> SingleAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SingleAsync(predicate, cancellationToken); return queryable.SingleAsync(predicate, cancellationToken);
} }
public Task<T?> SingleOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T?> SingleOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SingleOrDefaultAsync(cancellationToken)!; return queryable.SingleOrDefaultAsync(cancellationToken)!;
} }
public Task<T?> SingleOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate, public Task<T?> SingleOrDefaultAsync<T>(IQueryable<T> queryable, Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SingleOrDefaultAsync(predicate, cancellationToken)!; return queryable.SingleOrDefaultAsync(predicate, cancellationToken)!;
} }
public Task<T> MinAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T> MinAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).MinAsync(cancellationToken); return queryable.MinAsync(cancellationToken);
} }
public Task<TResult> MinAsync<T, TResult>(IQueryable<T> queryable, Expression<Func<T, TResult>> selector, CancellationToken cancellationToken = default) public Task<TResult> MinAsync<T, TResult>(IQueryable<T> queryable, Expression<Func<T, TResult>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).MinAsync(selector, cancellationToken); return queryable.MinAsync(selector, cancellationToken);
} }
public Task<T> MaxAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<T> MaxAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).MaxAsync(cancellationToken); return queryable.MaxAsync(cancellationToken);
} }
public Task<TResult> MaxAsync<T, TResult>(IQueryable<T> queryable, Expression<Func<T, TResult>> selector, CancellationToken cancellationToken = default) public Task<TResult> MaxAsync<T, TResult>(IQueryable<T> queryable, Expression<Func<T, TResult>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).MaxAsync(selector, cancellationToken); return queryable.MaxAsync(selector, cancellationToken);
} }
public Task<decimal> SumAsync(IQueryable<decimal> queryable, CancellationToken cancellationToken = default) public Task<decimal> SumAsync(IQueryable<decimal> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<decimal?> SumAsync(IQueryable<decimal?> queryable, CancellationToken cancellationToken = default) public Task<decimal?> SumAsync(IQueryable<decimal?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<decimal> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal>> selector, CancellationToken cancellationToken = default) public Task<decimal> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<decimal?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal?>> selector, CancellationToken cancellationToken = default) public Task<decimal?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<int> SumAsync(IQueryable<int> queryable, CancellationToken cancellationToken = default) public Task<int> SumAsync(IQueryable<int> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<int?> SumAsync(IQueryable<int?> queryable, CancellationToken cancellationToken = default) public Task<int?> SumAsync(IQueryable<int?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<int> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, int>> selector, CancellationToken cancellationToken = default) public Task<int> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, int>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<int?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, int?>> selector, CancellationToken cancellationToken = default) public Task<int?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, int?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<long> SumAsync(IQueryable<long> queryable, CancellationToken cancellationToken = default) public Task<long> SumAsync(IQueryable<long> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<long?> SumAsync(IQueryable<long?> queryable, CancellationToken cancellationToken = default) public Task<long?> SumAsync(IQueryable<long?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<long> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, long>> selector, CancellationToken cancellationToken = default) public Task<long> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, long>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<long?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, long?>> selector, CancellationToken cancellationToken = default) public Task<long?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, long?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<double> SumAsync(IQueryable<double> queryable, CancellationToken cancellationToken = default) public Task<double> SumAsync(IQueryable<double> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<double?> SumAsync(IQueryable<double?> queryable, CancellationToken cancellationToken = default) public Task<double?> SumAsync(IQueryable<double?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<double> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, double>> selector, CancellationToken cancellationToken = default) public Task<double> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, double>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<double?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, double?>> selector, CancellationToken cancellationToken = default) public Task<double?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, double?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<float> SumAsync(IQueryable<float> queryable, CancellationToken cancellationToken = default) public Task<float> SumAsync(IQueryable<float> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<float?> SumAsync(IQueryable<float?> queryable, CancellationToken cancellationToken = default) public Task<float?> SumAsync(IQueryable<float?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(cancellationToken); return queryable.SumAsync(cancellationToken);
} }
public Task<float> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, float>> selector, CancellationToken cancellationToken = default) public Task<float> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, float>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<float?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, float?>> selector, CancellationToken cancellationToken = default) public Task<float?> SumAsync<T>(IQueryable<T> queryable, Expression<Func<T, float?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); return queryable.SumAsync(selector, cancellationToken);
} }
public Task<decimal> AverageAsync(IQueryable<decimal> queryable, CancellationToken cancellationToken = default) public Task<decimal> AverageAsync(IQueryable<decimal> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<decimal?> AverageAsync(IQueryable<decimal?> queryable, CancellationToken cancellationToken = default) public Task<decimal?> AverageAsync(IQueryable<decimal?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<decimal> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal>> selector, CancellationToken cancellationToken = default) public Task<decimal> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<decimal?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal?>> selector, CancellationToken cancellationToken = default) public Task<decimal?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, decimal?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<double> AverageAsync(IQueryable<int> queryable, CancellationToken cancellationToken = default) public Task<double> AverageAsync(IQueryable<int> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<double?> AverageAsync(IQueryable<int?> queryable, CancellationToken cancellationToken = default) public Task<double?> AverageAsync(IQueryable<int?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<double> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, int>> selector, CancellationToken cancellationToken = default) public Task<double> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, int>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<double?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, int?>> selector, CancellationToken cancellationToken = default) public Task<double?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, int?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<double> AverageAsync(IQueryable<long> queryable, CancellationToken cancellationToken = default) public Task<double> AverageAsync(IQueryable<long> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<double?> AverageAsync(IQueryable<long?> queryable, CancellationToken cancellationToken = default) public Task<double?> AverageAsync(IQueryable<long?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<double> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, long>> selector, CancellationToken cancellationToken = default) public Task<double> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, long>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<double?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, long?>> selector, CancellationToken cancellationToken = default) public Task<double?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, long?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<double> AverageAsync(IQueryable<double> queryable, CancellationToken cancellationToken = default) public Task<double> AverageAsync(IQueryable<double> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<double?> AverageAsync(IQueryable<double?> queryable, CancellationToken cancellationToken = default) public Task<double?> AverageAsync(IQueryable<double?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<double> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, double>> selector, CancellationToken cancellationToken = default) public Task<double> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, double>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<double?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, double?>> selector, CancellationToken cancellationToken = default) public Task<double?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, double?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<float> AverageAsync(IQueryable<float> queryable, CancellationToken cancellationToken = default) public Task<float> AverageAsync(IQueryable<float> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<float?> AverageAsync(IQueryable<float?> queryable, CancellationToken cancellationToken = default) public Task<float?> AverageAsync(IQueryable<float?> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(cancellationToken); return queryable.AverageAsync(cancellationToken);
} }
public Task<float> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, float>> selector, CancellationToken cancellationToken = default) public Task<float> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, float>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<float?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, float?>> selector, CancellationToken cancellationToken = default) public Task<float?> AverageAsync<T>(IQueryable<T> queryable, Expression<Func<T, float?>> selector, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); return queryable.AverageAsync(selector, cancellationToken);
} }
public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return GetMongoQueryable(queryable).ToListAsync(cancellationToken); return queryable.ToListAsync(cancellationToken);
} }
public async Task<T[]> ToArrayAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) public async Task<T[]> ToArrayAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{ {
return (await GetMongoQueryable(queryable).ToListAsync(cancellationToken)).ToArray(); return (await queryable.ToListAsync(cancellationToken)).ToArray();
} }
} }

72
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
@ -512,7 +513,9 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[1].PropertyChanges[0].NewValue == entityId.ToString()));
AuditingStore.ClearReceivedCalls(); AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014
@ -539,10 +542,13 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == entityId.ToString() &&
x.EntityChanges[1].PropertyChanges[0].NewValue == null));
AuditingStore.ClearReceivedCalls(); AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014
var oneToManyId = "";
using (var scope = _auditingManager.BeginScope()) using (var scope = _auditingManager.BeginScope())
{ {
using (var uow = _unitOfWorkManager.Begin()) using (var uow = _unitOfWorkManager.Begin())
@ -561,6 +567,8 @@ public class Auditing_Tests : AbpAuditingTestBase
await repository.UpdateAsync(entity); await repository.UpdateAsync(entity);
await uow.CompleteAsync(); await uow.CompleteAsync();
await scope.SaveAsync(); await scope.SaveAsync();
oneToManyId = entity.OneToMany.First().Id.ToString();
} }
} }
@ -572,36 +580,80 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[1].PropertyChanges[0].NewValue == $"[\"{oneToManyId}\"]"));
AuditingStore.ClearReceivedCalls(); AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014
var newOneToManyId = "";
using (var scope = _auditingManager.BeginScope()) using (var scope = _auditingManager.BeginScope())
{ {
using (var uow = _unitOfWorkManager.Begin()) using (var uow = _unitOfWorkManager.Begin())
{ {
var entity = await repository.GetAsync(entityId); var entity = await repository.GetAsync(entityId);
entity.OneToMany = null; entity.OneToMany.Add(new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName2"
});
await repository.UpdateAsync(entity); await repository.UpdateAsync(entity);
await uow.CompleteAsync(); await uow.CompleteAsync();
await scope.SaveAsync(); await scope.SaveAsync();
newOneToManyId = JsonSerializer.Serialize(entity.OneToMany.Select(x => x.Id).ToList());
} }
} }
#pragma warning disable 4014 #pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 2 && AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 2 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName && x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
x.EntityChanges[1].ChangeType == EntityChangeType.Updated && x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == $"[\"{oneToManyId}\"]" &&
x.EntityChanges[1].PropertyChanges[0].NewValue == newOneToManyId));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
newOneToManyId = JsonSerializer.Serialize(entity.OneToMany.Select(x => x.Id).ToList());
entity.OneToMany = null;
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
}
}
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 3 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Deleted &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
x.EntityChanges[1].ChangeType == EntityChangeType.Deleted &&
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
x.EntityChanges[2].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[2].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[2].PropertyChanges.Count == 1 &&
x.EntityChanges[2].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
x.EntityChanges[2].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName &&
x.EntityChanges[2].PropertyChanges[0].OriginalValue == newOneToManyId &&
x.EntityChanges[2].PropertyChanges[0].NewValue == null));
AuditingStore.ClearReceivedCalls(); AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014
var manyToManyId = "";
using (var scope = _auditingManager.BeginScope()) using (var scope = _auditingManager.BeginScope())
{ {
using (var uow = _unitOfWorkManager.Begin()) using (var uow = _unitOfWorkManager.Begin())
@ -619,6 +671,8 @@ public class Auditing_Tests : AbpAuditingTestBase
await repository.UpdateAsync(entity); await repository.UpdateAsync(entity);
await uow.CompleteAsync(); await uow.CompleteAsync();
await scope.SaveAsync(); await scope.SaveAsync();
manyToManyId = entity.ManyToMany.First().Id.ToString();
} }
} }
@ -630,7 +684,9 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[1].PropertyChanges[0].NewValue == $"[\"{manyToManyId}\"]"));
#pragma warning restore 4014 #pragma warning restore 4014
@ -655,6 +711,8 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[0].PropertyChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 1 &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) &&
x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName && x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == $"[\"{manyToManyId}\"]" &&
x.EntityChanges[0].PropertyChanges[0].NewValue == null &&
x.EntityChanges[1].ChangeType == EntityChangeType.Updated && x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName &&

22
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/DistributedEventHandles.cs

@ -1,22 +0,0 @@
using System.Threading.Tasks;
namespace Volo.Abp.EventBus.Distributed;
public class DistributedEventHandles : ILocalEventHandler<DistributedEventSent>, ILocalEventHandler<DistributedEventReceived>
{
public static int SentCount { get; set; }
public static int ReceivedCount { get; set; }
public Task HandleEventAsync(DistributedEventSent eventData)
{
SentCount++;
return Task.CompletedTask;
}
public Task HandleEventAsync(DistributedEventReceived eventData)
{
ReceivedCount++;
return Task.CompletedTask;
}
}

63
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Shouldly;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Local; using Volo.Abp.EventBus.Local;
using Volo.Abp.Uow; using Volo.Abp.Uow;
@ -11,12 +10,6 @@ namespace Volo.Abp.EventBus.Distributed;
public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
{ {
protected override void AfterAddApplication(IServiceCollection services)
{
services.Replace(ServiceDescriptor.Singleton<ILocalEventBus, UnitTestLocalEventBus>());
base.AfterAddApplication(services);
}
[Fact] [Fact]
public async Task Should_Call_Handler_AndDispose() public async Task Should_Call_Handler_AndDispose()
{ {
@ -76,60 +69,56 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
public async Task DistributedEventSentAndReceived_Test() public async Task DistributedEventSentAndReceived_Test()
{ {
var localEventBus = GetRequiredService<ILocalEventBus>(); var localEventBus = GetRequiredService<ILocalEventBus>();
if (localEventBus is UnitTestLocalEventBus eventBus)
{
eventBus.OnEventHandleInvoking = async (eventType, eventData) =>
{
await localEventBus.PublishAsync(new DistributedEventReceived()
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
}, onUnitOfWorkComplete: false);
};
eventBus.OnPublishing = async (eventType, eventData) =>
{
await localEventBus.PublishAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
}, onUnitOfWorkComplete: false);
};
}
GetRequiredService<ILocalEventBus>().Subscribe<DistributedEventSent, DistributedEventHandles>(); localEventBus.Subscribe<DistributedEventSent, DistributedEventHandles>();
GetRequiredService<ILocalEventBus>().Subscribe<DistributedEventReceived, DistributedEventHandles>(); localEventBus.Subscribe<DistributedEventReceived, DistributedEventHandles>();
DistributedEventBus.Subscribe<MyEventDate, MyEventHandle>(); DistributedEventBus.Subscribe<MyEventDate, MyEventHandle>();
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin()) using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin())
{ {
MyEventDate.Order = string.Empty;
await DistributedEventBus.PublishAsync(new MyEventDate(), onUnitOfWorkComplete: false); await DistributedEventBus.PublishAsync(new MyEventDate(), onUnitOfWorkComplete: false);
Assert.Equal(1, DistributedEventHandles.SentCount); MyEventDate.Order.ShouldBe(nameof(DistributedEventSent) + nameof(DistributedEventReceived) + nameof(MyEventHandle));
Assert.Equal(1, DistributedEventHandles.ReceivedCount);
MyEventDate.Order = string.Empty;
await DistributedEventBus.PublishAsync(new MyEventDate(), onUnitOfWorkComplete: true); await DistributedEventBus.PublishAsync(new MyEventDate(), onUnitOfWorkComplete: true);
MyEventDate.Order.ShouldBe(string.Empty);
await uow.CompleteAsync(); await uow.CompleteAsync();
Assert.Equal(2, DistributedEventHandles.SentCount); MyEventDate.Order.ShouldBe(nameof(DistributedEventSent) + nameof(DistributedEventReceived) + nameof(MyEventHandle));
Assert.Equal(2, DistributedEventHandles.ReceivedCount);
} }
} }
class MyEventDate class MyEventDate
{ {
public static string Order { get; set; } = string.Empty;
} }
class MyEventHandle : IDistributedEventHandler<MyEventDate> class MyEventHandle : IDistributedEventHandler<MyEventDate>
{ {
public Task HandleEventAsync(MyEventDate eventData) public Task HandleEventAsync(MyEventDate eventData)
{ {
MyEventDate.Order += nameof(MyEventHandle);
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
class DistributedEventHandles : ILocalEventHandler<DistributedEventSent>, ILocalEventHandler<DistributedEventReceived>
{
public Task HandleEventAsync(DistributedEventSent eventData)
{
MyEventDate.Order += nameof(DistributedEventSent);
return Task.CompletedTask;
}
public Task HandleEventAsync(DistributedEventReceived eventData)
{
MyEventDate.Order += nameof(DistributedEventReceived);
return Task.CompletedTask;
}
}
} }

62
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/UnitTestLocalEventBus.cs

@ -1,62 +0,0 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.EventBus.Local;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Distributed;
/// <summary>
/// This class is used in unit tests and supports to publish DistributedEventSent and DistributedEventReceived events.
/// </summary>
public class UnitTestLocalEventBus : LocalEventBus
{
public UnitTestLocalEventBus(
[NotNull] IOptions<AbpLocalEventBusOptions> options,
[NotNull] IServiceScopeFactory serviceScopeFactory,
[NotNull] ICurrentTenant currentTenant,
[NotNull] IUnitOfWorkManager unitOfWorkManager,
[NotNull] IEventHandlerInvoker eventHandlerInvoker)
: base(options, serviceScopeFactory, currentTenant, unitOfWorkManager, eventHandlerInvoker)
{
}
public Func<Type, object, Task> OnEventHandleInvoking { get; set; }
protected async override Task InvokeEventHandlerAsync(IEventHandler eventHandler, object eventData, Type eventType)
{
if (OnEventHandleInvoking != null && eventType != typeof(DistributedEventSent) && eventType != typeof(DistributedEventReceived))
{
await OnEventHandleInvoking(eventType, eventData);
}
await base.InvokeEventHandlerAsync(eventHandler, eventData, eventType);
}
public Func<Type, object, Task> OnPublishing { get; set; }
public async override Task PublishAsync(
Type eventType,
object eventData,
bool onUnitOfWorkComplete = true)
{
if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null)
{
AddToUnitOfWork(
UnitOfWorkManager.Current,
new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext())
);
return;
}
if (OnPublishing != null && eventType != typeof(DistributedEventSent) && eventType != typeof(DistributedEventReceived))
{
await OnPublishing(eventType, eventData);
}
await PublishToEventBusAsync(eventType, eventData);
}
}

8
framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj

@ -15,10 +15,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EphemeralMongo.Core" /> <PackageReference Include="MongoSandbox.Core" />
<PackageReference Include="EphemeralMongo6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> <PackageReference Include="MongoSandbox6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="EphemeralMongo6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> <PackageReference Include="MongoSandbox6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="EphemeralMongo6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> <PackageReference Include="MongoSandbox6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup> </ItemGroup>

5
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs

@ -1,5 +1,5 @@
using System; using System;
using EphemeralMongo; using MongoSandbox;
namespace Volo.Abp.MongoDB; namespace Volo.Abp.MongoDB;
@ -11,7 +11,8 @@ public class MongoDbFixture : IDisposable
{ {
MongoDbRunner = MongoRunner.Run(new MongoRunnerOptions MongoDbRunner = MongoRunner.Run(new MongoRunnerOptions
{ {
UseSingleNodeReplicaSet = true UseSingleNodeReplicaSet = true,
ReplicaSetSetupTimeout = TimeSpan.FromSeconds(30)
}); });
} }

12
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs

@ -18,9 +18,9 @@ public class Repository_Basic_Tests : Repository_Basic_Tests<AbpMongoDbTestModul
public async Task ToMongoQueryable_Test() public async Task ToMongoQueryable_Test()
{ {
(await PersonRepository.GetQueryableAsync()).ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).ShouldNotBeNull();
(await PersonRepository.GetQueryableAsync()).As<IMongoQueryable<Person>>().ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).ShouldNotBeNull();
((IMongoQueryable<Person>)(await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas")).ShouldNotBeNull(); ((IQueryable<Person>)(await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas")).ShouldNotBeNull();
(await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas").As<IMongoQueryable<Person>>().ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas").ShouldNotBeNull();
} }
[Fact] [Fact]
@ -69,9 +69,9 @@ public class Repository_Basic_Tests : Repository_Basic_Tests<AbpMongoDbTestModul
[Fact] [Fact]
public async Task Filter_Case_Insensitive() public async Task Filter_Case_Insensitive()
{ {
(await CityRepository.GetMongoQueryableAsync()).FirstOrDefault(c => c.Name == "ISTANBUL").ShouldBeNull(); (await CityRepository.GetQueryableAsync()).FirstOrDefault(c => c.Name == "ISTANBUL").ShouldBeNull();
(await CityRepository.GetMongoQueryableAsync()).FirstOrDefault(c => c.Name == "istanbul").ShouldBeNull(); (await CityRepository.GetQueryableAsync()).FirstOrDefault(c => c.Name == "istanbul").ShouldBeNull();
(await CityRepository.GetMongoQueryableAsync()).FirstOrDefault(c => c.Name == "Istanbul").ShouldNotBeNull(); (await CityRepository.GetQueryableAsync()).FirstOrDefault(c => c.Name == "Istanbul").ShouldNotBeNull();
(await PersonRepository.GetQueryableAsync()).FirstOrDefault(p => p.Name == "douglas").ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).FirstOrDefault(p => p.Name == "douglas").ShouldNotBeNull();
(await PersonRepository.GetQueryableAsync()).FirstOrDefault(p => p.Name == "DOUGLAS").ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).FirstOrDefault(p => p.Name == "DOUGLAS").ShouldNotBeNull();

1
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/CityRepository.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; using MongoDB.Driver.Linq;

9
latest-versions.json

@ -1,4 +1,13 @@
[ [
{
"version": "9.0.4",
"releaseDate": "",
"type": "stable",
"message": "",
"leptonx": {
"version": "4.0.5"
}
},
{ {
"version": "9.0.3", "version": "9.0.3",
"releaseDate": "", "releaseDate": "",

101
modules/account/src/Volo.Abp.Account.HttpApi/Volo.Abp.Account.HttpApi.abppkg.analyze.json

@ -68,107 +68,6 @@
"contentType": "abpModule", "contentType": "abpModule",
"name": "AbpAccountHttpApiModule", "name": "AbpAccountHttpApiModule",
"summary": null "summary": null
},
{
"apis": [
{
"path": "api/account/register",
"method": "POST",
"groupName": "Account",
"responseType": "Volo.Abp.Identity.IdentityUserDto",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "input",
"type": "RegisterDto"
}
]
},
{
"path": "api/account/send-password-reset-code",
"method": "POST",
"groupName": "Account",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "input",
"type": "SendPasswordResetCodeDto"
}
]
},
{
"path": "api/account/verify-password-reset-token",
"method": "POST",
"groupName": "Account",
"responseType": "System.Boolean",
"responseTypeAsJson": "true",
"parameterDescriptions": [
{
"name": "input",
"type": "VerifyPasswordResetTokenInput"
}
]
},
{
"path": "api/account/reset-password",
"method": "POST",
"groupName": "Account",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "input",
"type": "ResetPasswordDto"
}
]
},
{
"path": "api/account/dynamic-claims/refresh",
"method": "POST",
"groupName": "DynamicClaims",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": []
},
{
"path": "api/account/my-profile",
"method": "GET",
"groupName": "Profile",
"responseType": "Volo.Abp.Account.ProfileDto",
"responseTypeAsJson": "",
"parameterDescriptions": []
},
{
"path": "api/account/my-profile",
"method": "PUT",
"groupName": "Profile",
"responseType": "Volo.Abp.Account.ProfileDto",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "input",
"type": "UpdateProfileDto"
}
]
},
{
"path": "api/account/my-profile/change-password",
"method": "POST",
"groupName": "Profile",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "input",
"type": "ChangePasswordInput"
}
]
}
],
"contentType": "httpApi",
"name": "Defined Http Apis",
"summary": null
} }
] ]
} }

41
modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg.analyze.json

@ -79,47 +79,6 @@
"name": "AbpAccountWebModule", "name": "AbpAccountWebModule",
"summary": null "summary": null
}, },
{
"apis": [
{
"path": "api/account/login",
"method": "POST",
"groupName": "Account",
"responseType": "Volo.Abp.Account.Web.Areas.Account.Controllers.Models.AbpLoginResult",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "login",
"type": "UserLoginInfo"
}
]
},
{
"path": "api/account/logout",
"method": "GET",
"groupName": "Account",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": []
},
{
"path": "api/account/check-password",
"method": "POST",
"groupName": "Account",
"responseType": "Volo.Abp.Account.Web.Areas.Account.Controllers.Models.AbpLoginResult",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "login",
"type": "UserLoginInfo"
}
]
}
],
"contentType": "httpApi",
"name": "Defined Http Apis",
"summary": null
},
{ {
"namespace": "Volo.Abp.Account.Web.Pages.Account", "namespace": "Volo.Abp.Account.Web.Pages.Account",
"contentType": "webPage", "contentType": "webPage",

2
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json

@ -48,7 +48,7 @@
"ChangeType": "Change type", "ChangeType": "Change type",
"ChangeTime": "Time", "ChangeTime": "Time",
"NewValue": "New value", "NewValue": "New value",
"OriginalValue": "Original value", "OriginalValue": "Old value",
"PropertyName": "Property name", "PropertyName": "Property name",
"PropertyTypeFullName": "Property Type Full Name", "PropertyTypeFullName": "Property Type Full Name",
"Yes": "Yes", "Yes": "Yes",

39
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter.cs

@ -0,0 +1,39 @@
using System;
using System.Text.RegularExpressions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AuditLogging;
public class AuditLogEntityTypeFullNameConverter : ITransientDependency
{
public virtual string Convert(string typeFullName)
{
var genericType = Regex.Match(typeFullName, @"(.+?)`1\[\[");
if (!genericType.Success)
{
return ReplaceGenericSymbol(typeFullName);
}
var type = Regex.Match(typeFullName, @"`1\[\[(.+?), ");
if (!type.Success)
{
return typeFullName;
}
if (type.Groups[1].Value.Contains("System.Nullable`1[["))
{
return genericType.Groups[1].Value + "<" + type.Groups[1].Value.Replace("System.Nullable`1[[", "") + "?>";
}
return genericType.Groups[1].Value.Contains("System.Nullable")
? type.Groups[1].Value + "?"
: genericType.Groups[1].Value + "<" + ReplaceGenericSymbol(type.Groups[1].Value) + ">";
}
protected virtual string ReplaceGenericSymbol(string typeFullName)
{
return typeFullName.Contains("`1+")
? typeFullName.Substring(0, typeFullName.IndexOf("[[", StringComparison.Ordinal)).Replace("`1+", ".")
: typeFullName;
}
}

18
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs

@ -19,12 +19,19 @@ public class AuditLogInfoToAuditLogConverter : IAuditLogInfoToAuditLogConverter,
protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; } protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; }
protected IJsonSerializer JsonSerializer { get; } protected IJsonSerializer JsonSerializer { get; }
protected AbpExceptionHandlingOptions ExceptionHandlingOptions { get; } protected AbpExceptionHandlingOptions ExceptionHandlingOptions { get; }
protected AuditLogEntityTypeFullNameConverter AuditLogEntityTypeFullNameConverter { get; }
public AuditLogInfoToAuditLogConverter(IGuidGenerator guidGenerator, IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IJsonSerializer jsonSerializer, IOptions<AbpExceptionHandlingOptions> exceptionHandlingOptions) public AuditLogInfoToAuditLogConverter(
IGuidGenerator guidGenerator,
IExceptionToErrorInfoConverter exceptionToErrorInfoConverter,
IJsonSerializer jsonSerializer,
IOptions<AbpExceptionHandlingOptions> exceptionHandlingOptions,
AuditLogEntityTypeFullNameConverter auditLogEntityTypeFullNameConverter)
{ {
GuidGenerator = guidGenerator; GuidGenerator = guidGenerator;
ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter; ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter;
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
AuditLogEntityTypeFullNameConverter = auditLogEntityTypeFullNameConverter;
ExceptionHandlingOptions = exceptionHandlingOptions.Value; ExceptionHandlingOptions = exceptionHandlingOptions.Value;
} }
@ -41,6 +48,15 @@ public class AuditLogInfoToAuditLogConverter : IAuditLogInfoToAuditLogConverter,
} }
} }
foreach (var entityChange in auditLogInfo.EntityChanges ?? Enumerable.Empty<EntityChangeInfo>())
{
entityChange.EntityTypeFullName = AuditLogEntityTypeFullNameConverter.Convert(entityChange.EntityTypeFullName);
foreach (var propertyChange in entityChange.PropertyChanges ?? Enumerable.Empty<EntityPropertyChangeInfo>())
{
propertyChange.PropertyTypeFullName = AuditLogEntityTypeFullNameConverter.Convert(propertyChange.PropertyTypeFullName);
}
}
var entityChanges = auditLogInfo var entityChanges = auditLogInfo
.EntityChanges? .EntityChanges?
.Select(entityChangeInfo => new EntityChange(GuidGenerator, auditLogId, entityChangeInfo, tenantId: auditLogInfo.TenantId)) .Select(entityChangeInfo => new EntityChange(GuidGenerator, auditLogId, entityChangeInfo, tenantId: auditLogInfo.TenantId))

9
modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo.Abp.AuditLogging.EntityFrameworkCore.abppkg.analyze.json

@ -67,7 +67,14 @@
{ {
"namespace": "Volo.Abp.AuditLogging.EntityFrameworkCore", "namespace": "Volo.Abp.AuditLogging.EntityFrameworkCore",
"connectionStringName": "AbpAuditLogging", "connectionStringName": "AbpAuditLogging",
"databaseTables": [], "databaseTables": [
{
"entityFullName": "Volo.Abp.AuditLogging.AuditLog",
"contentType": "databaseTable",
"name": "AuditLogs",
"summary": null
}
],
"replacedDbContexts": [], "replacedDbContexts": [],
"modelBuilderExtensionMethods": [ "modelBuilderExtensionMethods": [
{ {

24
modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs

@ -64,8 +64,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
return await query return await query
.OrderBy(sorting.IsNullOrWhiteSpace() ? (nameof(AuditLog.ExecutionTime) + " DESC") : sorting) .OrderBy(sorting.IsNullOrWhiteSpace() ? (nameof(AuditLog.ExecutionTime) + " DESC") : sorting)
.As<IMongoQueryable<AuditLog>>() .PageBy(skipCount, maxResultCount)
.PageBy<AuditLog, IMongoQueryable<AuditLog>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }
@ -104,8 +103,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
cancellationToken: cancellationToken cancellationToken: cancellationToken
); );
var count = await query.As<IMongoQueryable<AuditLog>>() var count = await query.LongCountAsync(GetCancellationToken(cancellationToken));
.LongCountAsync(GetCancellationToken(cancellationToken));
return count; return count;
} }
@ -128,7 +126,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
bool includeDetails = false, bool includeDetails = false,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return (await GetMongoQueryableAsync(cancellationToken)) return (await GetQueryableAsync(cancellationToken))
.WhereIf(startTime.HasValue, auditLog => auditLog.ExecutionTime >= startTime) .WhereIf(startTime.HasValue, auditLog => auditLog.ExecutionTime >= startTime)
.WhereIf(endTime.HasValue, auditLog => auditLog.ExecutionTime <= endTime) .WhereIf(endTime.HasValue, auditLog => auditLog.ExecutionTime <= endTime)
.WhereIf(hasException.HasValue && hasException.Value, auditLog => auditLog.Exceptions != null && auditLog.Exceptions != "") .WhereIf(hasException.HasValue && hasException.Value, auditLog => auditLog.Exceptions != null && auditLog.Exceptions != "")
@ -151,7 +149,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
DateTime endDate, DateTime endDate,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var result = await (await GetMongoQueryableAsync(cancellationToken)) var result = await (await GetQueryableAsync(cancellationToken))
.Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate) .Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate)
.OrderBy(t => t.ExecutionTime) .OrderBy(t => t.ExecutionTime)
.GroupBy(t => new { .GroupBy(t => new {
@ -169,7 +167,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
Guid entityChangeId, Guid entityChangeId,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var entityChange = (await (await GetMongoQueryableAsync(cancellationToken)) var entityChange = (await (await GetQueryableAsync(cancellationToken))
.Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)) .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId))
.OrderBy(x => x.Id) .OrderBy(x => x.Id)
.FirstAsync(GetCancellationToken(cancellationToken))).EntityChanges.FirstOrDefault(x => x.Id == entityChangeId); .FirstAsync(GetCancellationToken(cancellationToken))).EntityChanges.FirstOrDefault(x => x.Id == entityChangeId);
@ -199,8 +197,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
return await query return await query
.OrderBy(sorting.IsNullOrWhiteSpace() ? (nameof(EntityChange.ChangeTime) + " DESC") : sorting) .OrderBy(sorting.IsNullOrWhiteSpace() ? (nameof(EntityChange.ChangeTime) + " DESC") : sorting)
.As<IMongoQueryable<EntityChange>>() .PageBy(skipCount, maxResultCount)
.PageBy<EntityChange, IMongoQueryable<EntityChange>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }
@ -215,7 +212,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
{ {
var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, cancellationToken); var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, cancellationToken);
var count = await query.As<IMongoQueryable<EntityChange>>().LongCountAsync(GetCancellationToken(cancellationToken)); var count = await query.LongCountAsync(GetCancellationToken(cancellationToken));
return count; return count;
} }
@ -224,7 +221,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
Guid entityChangeId, Guid entityChangeId,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var auditLog = await (await GetMongoQueryableAsync(cancellationToken)) var auditLog = await (await GetQueryableAsync(cancellationToken))
.Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)) .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId))
.FirstAsync(GetCancellationToken(cancellationToken)); .FirstAsync(GetCancellationToken(cancellationToken));
@ -240,9 +237,8 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
string entityTypeFullName, string entityTypeFullName,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var auditLogs = await (await GetMongoQueryableAsync(cancellationToken)) var auditLogs = await (await GetQueryableAsync(cancellationToken))
.Where(x => x.EntityChanges.Any(y => y.EntityId == entityId && y.EntityTypeFullName == entityTypeFullName)) .Where(x => x.EntityChanges.Any(y => y.EntityId == entityId && y.EntityTypeFullName == entityTypeFullName))
.As<IMongoQueryable<AuditLog>>()
.OrderByDescending(x => x.ExecutionTime) .OrderByDescending(x => x.ExecutionTime)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
@ -263,7 +259,7 @@ public class MongoAuditLogRepository : MongoDbRepository<IAuditLoggingMongoDbCon
string entityTypeFullName = null, string entityTypeFullName = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return (await GetMongoQueryableAsync(cancellationToken)) return (await GetQueryableAsync(cancellationToken))
.SelectMany(x => x.EntityChanges) .SelectMany(x => x.EntityChanges)
.WhereIf(auditLogId.HasValue, e => e.Id == auditLogId) .WhereIf(auditLogId.HasValue, e => e.Id == auditLogId)
.WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime) .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime)

6
modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AuditLogEntityTypeFullNameConverter_Tests.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.AuditLogging.EntityFrameworkCore;
public class AuditLogEntityTypeFullNameConverter_Tests : AuditLogEntityTypeFullNameConverter_Tests<AbpAuditLoggingEntityFrameworkCoreTestModule>
{
}

8
modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.csproj

@ -14,10 +14,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="EphemeralMongo.Core" /> <PackageReference Include="MongoSandbox.Core" />
<PackageReference Include="EphemeralMongo6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> <PackageReference Include="MongoSandbox6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="EphemeralMongo6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> <PackageReference Include="MongoSandbox6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="EphemeralMongo6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> <PackageReference Include="MongoSandbox6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
</ItemGroup> </ItemGroup>
</Project> </Project>

9
modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogEntityTypeFullNameConverter_Tests.cs

@ -0,0 +1,9 @@
using Xunit;
namespace Volo.Abp.AuditLogging.MongoDB;
[Collection(MongoTestCollection.Name)]
public class AuditLogEntityTypeFullNameConverter_Tests : AuditLogEntityTypeFullNameConverter_Tests<AbpAuditLoggingMongoDbTestModule>
{
}

2
modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs

@ -1,5 +1,5 @@
using System; using System;
using EphemeralMongo; using MongoSandbox;
namespace Volo.Abp.AuditLogging.MongoDB; namespace Volo.Abp.AuditLogging.MongoDB;

46
modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter_Tests.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;
namespace Volo.Abp.AuditLogging;
public abstract class AuditLogEntityTypeFullNameConverter_Tests<TStartupModule> : AuditLoggingTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
private readonly AuditLogEntityTypeFullNameConverter _typeFullNameConverter;
protected AuditLogEntityTypeFullNameConverter_Tests()
{
_typeFullNameConverter = GetRequiredService<AuditLogEntityTypeFullNameConverter>();
}
[Fact]
public void AuditLogEntityTypeFullNameConverter_Test()
{
_typeFullNameConverter.Convert("MyType").ShouldBe("MyType");
_typeFullNameConverter.Convert(typeof(string).FullName!).ShouldBe("System.String");
_typeFullNameConverter.Convert(typeof(Guid).FullName!).ShouldBe("System.Guid");
_typeFullNameConverter.Convert(typeof(Guid?).FullName!).ShouldBe("System.Guid?");
_typeFullNameConverter.Convert(typeof(int).FullName!).ShouldBe("System.Int32");
_typeFullNameConverter.Convert(typeof(long?).FullName!).ShouldBe("System.Int64?");
_typeFullNameConverter.Convert(typeof(MyClass).FullName!).ShouldBe("Volo.Abp.AuditLogging.AuditLogEntityTypeFullNameConverter_Tests.MyClass");
_typeFullNameConverter.Convert(typeof(ICollection<string>).FullName!).ShouldBe($"System.Collections.Generic.ICollection<System.String>");
_typeFullNameConverter.Convert(typeof(Collection<int>).FullName!).ShouldBe($"System.Collections.ObjectModel.Collection<System.Int32>");
_typeFullNameConverter.Convert(typeof(List<Guid>).FullName!).ShouldBe($"System.Collections.Generic.List<System.Guid>");
_typeFullNameConverter.Convert(typeof(List<MyClass>).FullName!).ShouldBe($"System.Collections.Generic.List<Volo.Abp.AuditLogging.AuditLogEntityTypeFullNameConverter_Tests.MyClass>");
_typeFullNameConverter.Convert(typeof(ICollection<long?>).FullName!).ShouldBe($"System.Collections.Generic.ICollection<System.Int64?>");
_typeFullNameConverter.Convert(typeof(Collection<int?>).FullName!).ShouldBe($"System.Collections.ObjectModel.Collection<System.Int32?>");
_typeFullNameConverter.Convert(typeof(List<Guid?>).FullName!).ShouldBe($"System.Collections.Generic.List<System.Guid?>");
}
public class MyClass
{
}
}

9
modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo.Abp.BackgroundJobs.EntityFrameworkCore.abppkg.analyze.json

@ -67,7 +67,14 @@
{ {
"namespace": "Volo.Abp.BackgroundJobs.EntityFrameworkCore", "namespace": "Volo.Abp.BackgroundJobs.EntityFrameworkCore",
"connectionStringName": "AbpBackgroundJobs", "connectionStringName": "AbpBackgroundJobs",
"databaseTables": [], "databaseTables": [
{
"entityFullName": "Volo.Abp.BackgroundJobs.BackgroundJobRecord",
"contentType": "databaseTable",
"name": "BackgroundJobs",
"summary": null
}
],
"replacedDbContexts": [], "replacedDbContexts": [],
"modelBuilderExtensionMethods": [ "modelBuilderExtensionMethods": [
{ {

5
modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Domain.Repositories.MongoDB;
@ -29,10 +30,10 @@ public class MongoBackgroundJobRepository : MongoDbRepository<IBackgroundJobsMon
return await (await GetWaitingListQuery(maxResultCount)).ToListAsync(GetCancellationToken(cancellationToken)); return await (await GetWaitingListQuery(maxResultCount)).ToListAsync(GetCancellationToken(cancellationToken));
} }
protected virtual async Task<IMongoQueryable<BackgroundJobRecord>> GetWaitingListQuery(int maxResultCount, CancellationToken cancellationToken = default) protected virtual async Task<IQueryable<BackgroundJobRecord>> GetWaitingListQuery(int maxResultCount, CancellationToken cancellationToken = default)
{ {
var now = Clock.Now; var now = Clock.Now;
return (await GetMongoQueryableAsync(cancellationToken)) return (await GetQueryableAsync(cancellationToken))
.Where(t => !t.IsAbandoned && t.NextTryTime <= now) .Where(t => !t.IsAbandoned && t.NextTryTime <= now)
.OrderByDescending(t => t.Priority) .OrderByDescending(t => t.Priority)
.ThenBy(t => t.TryCount) .ThenBy(t => t.TryCount)

8
modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj

@ -14,10 +14,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="EphemeralMongo.Core" /> <PackageReference Include="MongoSandbox.Core" />
<PackageReference Include="EphemeralMongo6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> <PackageReference Include="MongoSandbox6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="EphemeralMongo6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> <PackageReference Include="MongoSandbox6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="EphemeralMongo6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> <PackageReference Include="MongoSandbox6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs

@ -1,5 +1,5 @@
using System; using System;
using EphemeralMongo; using MongoSandbox;
namespace Volo.Abp.BackgroundJobs.MongoDB; namespace Volo.Abp.BackgroundJobs.MongoDB;

4
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml

@ -57,6 +57,10 @@
<div class="@containerClass"> <div class="@containerClass">
<abp-row> <abp-row>
<abp-column class="col mx-auto" style="max-width: 440px"> <abp-column class="col mx-auto" style="max-width: 440px">
<div class="brand-container">
<img class="brand-logo" src="@(BrandingProvider.LogoUrl ?? Url.Content("~/themes/basic/logo.svg"))" alt="@BrandingProvider.AppName"/>
<h2 class="brand-text">@BrandingProvider.AppName</h2>
</div>
@if (MultiTenancyOptions.Value.IsEnabled && @if (MultiTenancyOptions.Value.IsEnabled &&
(TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true || (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true ||
TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName) == true)) TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName) == true))

24
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css

@ -1,4 +1,6 @@
@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
#main-navbar-tools a.dropdown-toggle { #main-navbar-tools a.dropdown-toggle {
text-decoration: none; text-decoration: none;
color: #fff; color: #fff;
@ -97,4 +99,24 @@ div.dataTables_wrapper div.dataTables_length label {
} }
.rtl table.dataTable thead th, table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td { .rtl table.dataTable thead th, table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td {
text-align: right; text-align: right;
}
.brand-container{
text-align: center;
margin-top: 8rem;
}
.brand-logo {
height: 50px;
}
.brand-text {
color: #292D33;
font-family: Lexend;
font-size: 30px;
font-style: normal;
font-weight: 500;
line-height: 34px;
margin-top: 5px;
margin-bottom: 15px;
} }

17
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/logo.svg

@ -0,0 +1,17 @@
<svg width="147" height="50" viewBox="0 0 147 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7065_14314)">
<path d="M25.8749 28.9393C28.1779 28.9393 30.0448 27.0724 30.0448 24.7695C30.0448 22.4665 28.1779 20.5996 25.8749 20.5996C23.572 20.5996 21.7051 22.4665 21.7051 24.7695C21.7051 27.0724 23.572 28.9393 25.8749 28.9393Z" fill="#B84297"/>
<path d="M74.2393 11.0308C72.0441 11.0115 69.8776 11.5309 67.9297 12.5435C65.9819 13.5561 64.3121 15.031 63.0668 16.8389C61.8215 18.6469 61.0386 20.7327 60.7868 22.9135C60.535 25.0944 60.822 27.3037 61.6225 29.3479C62.4231 31.392 63.7128 33.2087 65.3786 34.6385C67.0445 36.0684 69.0356 37.0678 71.1775 37.5492C73.3194 38.0307 75.5467 37.9794 77.6642 37.3999C79.7816 36.8204 81.7247 35.7304 83.3229 34.2255V36.6566H87.5314V25.0904C87.5421 24.8417 87.5485 24.5908 87.5464 24.3379C87.5047 20.8216 86.0893 17.4611 83.6027 14.9745C81.1161 12.4879 77.7556 11.0725 74.2393 11.0308ZM74.2972 33.7002C72.4534 33.7349 70.6413 33.2176 69.0937 32.2148C67.546 31.2119 66.3335 29.7693 65.6119 28.0722C64.8902 26.3751 64.6924 24.5011 65.0439 22.6907C65.3953 20.8804 66.2799 19.2164 67.584 17.9125C68.8882 16.6087 70.5524 15.7245 72.3628 15.3735C74.1732 15.0225 76.0473 15.2207 77.7442 15.9428C79.4411 16.6648 80.8835 17.8777 81.8859 19.4255C82.8884 20.9734 83.4053 22.7856 83.3701 24.6294C83.3701 24.7216 83.3701 24.8117 83.3572 24.9017H83.3251V25.3541C83.0966 27.6071 82.0527 29.699 80.3896 31.2361C78.7266 32.7732 76.5591 33.6495 74.2951 33.7002H74.2972Z" fill="#292D33"/>
<path d="M103.56 11.0306C100.084 10.9953 96.7302 12.3148 94.2102 14.7095V3.00391H90.0039V24.9015H90.0275C90.1154 27.5308 90.974 30.0762 92.4965 32.2215C94.0191 34.3669 96.1385 36.0175 98.5914 36.9684C101.044 37.9192 103.722 38.1283 106.293 37.5697C108.864 37.011 111.214 35.7093 113.051 33.8263C114.888 31.9432 116.131 29.5619 116.626 26.9781C117.121 24.3944 116.846 21.7222 115.835 19.2936C114.823 16.865 113.121 14.787 110.938 13.318C108.756 11.8489 106.19 11.0535 103.56 11.0306V11.0306ZM103.611 33.7C101.207 33.7458 98.8805 32.8537 97.1236 31.2128C95.3666 29.5719 94.3178 27.3112 94.1995 24.9101V24.9101V23.9689C94.2942 22.1709 94.9117 20.4396 95.9764 18.9876C97.041 17.5356 98.5065 16.426 100.193 15.795C101.879 15.164 103.713 15.0391 105.469 15.4355C107.226 15.832 108.828 16.7326 110.08 18.0268C111.331 19.3211 112.178 20.9526 112.516 22.7212C112.853 24.4897 112.667 26.3184 111.98 27.9828C111.294 29.6471 110.136 31.0747 108.649 32.0903C107.162 33.1059 105.411 33.6654 103.611 33.7V33.7Z" fill="#292D33"/>
<path d="M132.882 11.0312C131.099 11.0155 129.332 11.3549 127.682 12.0298C126.032 12.7046 124.534 13.7012 123.273 14.9615C122.013 16.2219 121.016 17.7206 120.341 19.3703C119.667 21.02 119.327 22.7875 119.343 24.5698C119.343 24.6813 119.343 24.7842 119.343 24.9021H119.319V46.8019H123.526V34.1744C125.112 35.6781 127.042 36.7713 129.148 37.359C131.253 37.9466 133.471 38.0108 135.607 37.5461C137.743 37.0813 139.733 36.1017 141.404 34.6924C143.075 33.2831 144.376 31.4868 145.195 29.4598C146.013 27.4328 146.324 25.2365 146.1 23.062C145.876 20.8875 145.124 18.8007 143.91 16.983C142.696 15.1654 141.055 13.672 139.132 12.6328C137.209 11.5937 135.061 11.0402 132.875 11.0205L132.882 11.0312ZM132.942 33.7006C130.543 33.7462 128.221 32.858 126.465 31.2235C124.71 29.589 123.658 27.3362 123.532 24.9407V24.9021C123.532 24.6878 123.517 24.4884 123.532 24.2783C123.569 22.4648 124.137 20.7021 125.168 19.2095C126.199 17.7169 127.645 16.5602 129.328 15.8835C131.011 15.2067 132.856 15.0397 134.633 15.4033C136.41 15.7669 138.041 16.6449 139.322 17.9282C140.604 19.2115 141.48 20.8434 141.842 22.6208C142.204 24.3983 142.034 26.2428 141.356 27.9248C140.677 29.6069 139.519 31.0523 138.025 32.0812C136.531 33.11 134.768 33.6769 132.954 33.7114L132.942 33.7006Z" fill="#292D33"/>
<path d="M25.8762 0.00294068C22.0878 -0.00061638 18.3485 0.859427 14.9424 2.51771V7.88814C17.9771 5.95077 21.472 4.85417 25.0695 4.71047C28.6671 4.56676 32.2383 5.38111 35.4179 7.07025C38.5975 8.75938 41.2714 11.2627 43.1662 14.3242C45.0611 17.3856 46.1088 20.8954 46.2023 24.4946V24.4946V39.6004C48.8844 35.8623 50.4836 31.4569 50.824 26.8687C51.1645 22.2805 50.2329 17.6873 48.1319 13.5943C46.0309 9.50126 42.8417 6.06695 38.9152 3.66907C34.9887 1.27119 30.477 0.00264244 25.8762 0.00294068V0.00294068Z" fill="#292D33"/>
<path d="M25.8756 45.3391C20.6536 45.3389 15.6321 43.3283 11.8531 39.7245C8.07398 36.1207 5.82741 31.2002 5.57948 25.9841H5.5559V10.4346C1.90165 15.5416 0.302109 21.8363 1.07501 28.0683C1.84791 34.3002 4.9367 40.0135 9.7278 44.0731C14.5189 48.1327 20.6617 50.2416 26.936 49.9808C33.2104 49.7201 39.157 47.1088 43.5948 42.6657V34.9692C41.8282 38.1154 39.2555 40.7343 36.1414 42.5569C33.0272 44.3794 29.4839 45.3397 25.8756 45.3391Z" fill="#292D33"/>
<path d="M25.875 31.4199C24.3641 31.4213 22.8965 30.9155 21.7073 29.9835V34.7644C23.8753 35.6561 26.2725 35.8274 28.5452 35.2531C30.818 34.6788 32.846 33.3892 34.3301 31.5746C35.8142 29.7599 36.6757 27.5164 36.7877 25.1748C36.8997 22.8333 36.2562 20.5178 34.952 18.5698C33.6479 16.6219 31.7522 15.1446 29.5446 14.356C27.337 13.5674 24.9344 13.5092 22.6912 14.1899C20.448 14.8707 18.483 16.2544 17.0861 18.1369C15.6891 20.0194 14.9342 22.3011 14.9326 24.6453V38.7027C16.2039 39.7036 17.6061 40.5259 19.1003 41.1467V24.6388C19.1003 23.2989 19.4977 21.9891 20.2421 20.875C20.9865 19.761 22.0445 18.8926 23.2824 18.3799C24.5203 17.8671 25.8825 17.733 27.1967 17.9944C28.5108 18.2558 29.7179 18.901 30.6654 19.8484C31.6129 20.7959 32.2581 22.003 32.5195 23.3172C32.7809 24.6313 32.6467 25.9935 32.134 27.2314C31.6212 28.4693 30.7529 29.5274 29.6388 30.2718C28.5247 31.0162 27.2149 31.4135 25.875 31.4135V31.4199Z" fill="#292D33"/>
<path d="M12.3286 36.1851V24.7689C12.3299 21.9102 13.2359 19.1252 14.9166 16.8127C16.5974 14.5003 18.9668 12.779 21.6856 11.8954C24.4044 11.0118 27.333 11.0112 30.0521 11.8937C32.7713 12.7762 35.1414 14.4965 36.8231 16.8082C38.5049 19.12 39.4119 21.9046 39.4144 24.7633C39.4169 27.6221 38.5147 30.4083 36.837 32.723C35.1593 35.0377 32.7922 36.7621 30.0746 37.6493C27.3571 38.5366 24.4284 38.5411 21.7081 37.6622V41.9929C24.7756 42.7356 27.9858 42.6456 31.0068 41.732C34.0278 40.8185 36.75 39.1146 38.8919 36.7966C41.0338 34.4786 42.5178 31.6305 43.1903 28.5469C43.8628 25.4633 43.6994 22.256 42.7172 19.2566C41.7349 16.2572 39.9693 13.5747 37.603 11.4862C35.2367 9.39778 32.3555 7.97928 29.2573 7.37737C26.1591 6.77546 22.9564 7.01198 19.9802 8.06249C17.004 9.113 14.3625 10.9394 12.3286 13.3528V7.56428V3.98828C10.8199 4.95977 9.4198 6.09035 8.15234 7.36061V25.3006C8.27063 29.2972 9.74319 33.1351 12.3286 36.1851V36.1851Z" fill="#292D33"/>
</g>
<defs>
<clipPath id="clip0_7065_14314">
<rect width="145.226" height="50" fill="white" transform="translate(0.886719)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

15
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.abppkg.analyze.json

@ -67,7 +67,20 @@
{ {
"namespace": "Volo.Abp.BlobStoring.Database.EntityFrameworkCore", "namespace": "Volo.Abp.BlobStoring.Database.EntityFrameworkCore",
"connectionStringName": "AbpBlobStoring", "connectionStringName": "AbpBlobStoring",
"databaseTables": [], "databaseTables": [
{
"entityFullName": "Volo.Abp.BlobStoring.Database.DatabaseBlobContainer",
"contentType": "databaseTable",
"name": "BlobContainers",
"summary": null
},
{
"entityFullName": "Volo.Abp.BlobStoring.Database.DatabaseBlob",
"contentType": "databaseTable",
"name": "Blobs",
"summary": null
}
],
"replacedDbContexts": [], "replacedDbContexts": [],
"modelBuilderExtensionMethods": [ "modelBuilderExtensionMethods": [
{ {

4
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs

@ -17,7 +17,7 @@ public class MongoDbDatabaseBlobRepository : MongoDbRepository<IBlobStoringMongo
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.FirstOrDefaultAsync( .FirstOrDefaultAsync(
x => x.ContainerId == containerId && x.Name == name, x => x.ContainerId == containerId && x.Name == name,
cancellationToken cancellationToken
@ -28,7 +28,7 @@ public class MongoDbDatabaseBlobRepository : MongoDbRepository<IBlobStoringMongo
{ {
cancellationToken = GetCancellationToken(cancellationToken); cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.AnyAsync( .AnyAsync(
x => x.ContainerId == containerId && x.Name == name, x => x.ContainerId == containerId && x.Name == name,
cancellationToken cancellationToken

2
modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs

@ -1,5 +1,5 @@
using System; using System;
using EphemeralMongo; using MongoSandbox;
namespace Volo.Abp.BlobStoring.Database.MongoDB; namespace Volo.Abp.BlobStoring.Database.MongoDB;

8
modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj

@ -7,10 +7,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="EphemeralMongo.Core" /> <PackageReference Include="MongoSandbox.Core" />
<PackageReference Include="EphemeralMongo6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> <PackageReference Include="MongoSandbox6.runtime.linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="EphemeralMongo6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> <PackageReference Include="MongoSandbox6.runtime.osx-x64" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="EphemeralMongo6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> <PackageReference Include="MongoSandbox6.runtime.win-x64" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.Database.MongoDB\Volo.Abp.BlobStoring.Database.MongoDB.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.Database.MongoDB\Volo.Abp.BlobStoring.Database.MongoDB.csproj" />
<ProjectReference Include="..\Volo.Abp.BlobStoring.Database.TestBase\Volo.Abp.BlobStoring.Database.TestBase.csproj" /> <ProjectReference Include="..\Volo.Abp.BlobStoring.Database.TestBase\Volo.Abp.BlobStoring.Database.TestBase.csproj" />
</ItemGroup> </ItemGroup>

2
modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.abppkg.analyze.json

@ -1,6 +1,6 @@
{ {
"name": "Volo.BloggingTestApp", "name": "Volo.BloggingTestApp",
"hash": "faf8c09c571a52e961d15c9dd9c7d7ac", "hash": "",
"contents": [ "contents": [
{ {
"namespace": "Volo.BloggingTestApp", "namespace": "Volo.BloggingTestApp",

84
modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo.Blogging.Admin.HttpApi.abppkg.analyze.json

@ -63,90 +63,6 @@
"contentType": "abpModule", "contentType": "abpModule",
"name": "BloggingAdminHttpApiModule", "name": "BloggingAdminHttpApiModule",
"summary": null "summary": null
},
{
"apis": [
{
"path": "api/blogging/blogs/admin",
"method": "GET",
"groupName": "BlogManagement",
"responseType": "Volo.Abp.Application.Dtos.ListResultDto`1[Volo.Blogging.Blogs.Dtos.BlogDto]",
"responseTypeAsJson": "{\r\n \"items\": [\r\n {\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ]\r\n}",
"parameterDescriptions": []
},
{
"path": "api/blogging/blogs/admin/{id}",
"method": "GET",
"groupName": "BlogManagement",
"responseType": "Volo.Blogging.Blogs.Dtos.BlogDto",
"responseTypeAsJson": "{\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
},
{
"path": "api/blogging/blogs/admin",
"method": "POST",
"groupName": "BlogManagement",
"responseType": "Volo.Blogging.Blogs.Dtos.BlogDto",
"responseTypeAsJson": "{\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "input",
"type": "CreateBlogDto"
}
]
},
{
"path": "api/blogging/blogs/admin/{id}",
"method": "PUT",
"groupName": "BlogManagement",
"responseType": "Volo.Blogging.Blogs.Dtos.BlogDto",
"responseTypeAsJson": "{\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
},
{
"name": "input",
"type": "UpdateBlogDto"
}
]
},
{
"path": "api/blogging/blogs/admin/{id}",
"method": "DELETE",
"groupName": "BlogManagement",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
},
{
"path": "api/blogging/blogs/admin/clear-cache/{id}",
"method": "GET",
"groupName": "BlogManagement",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
}
],
"contentType": "httpApi",
"name": "Defined Http Apis",
"summary": null
} }
] ]
} }

77
modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.abppkg.analyze.json

@ -1717,6 +1717,83 @@
"contentType": "repositoryInterface", "contentType": "repositoryInterface",
"name": "IBlogRepository", "name": "IBlogRepository",
"summary": null "summary": null
},
{
"eventHandlerType": "Distributed",
"namespace": "Volo.Blogging.Users",
"subscribedEvents": [
{
"underlyingType": {
"name": "UserEto",
"namespace": "Volo.Abp.Users",
"declaringAssemblyName": "Volo.Abp.Users.Abstractions",
"fullName": "Volo.Abp.Users.UserEto"
},
"name": "EntityUpdatedEto`1",
"namespace": "Volo.Abp.Domain.Entities.Events.Distributed",
"declaringAssemblyName": "Volo.Abp.Ddd.Domain.Shared",
"fullName": "Volo.Abp.Domain.Entities.Events.Distributed.EntityUpdatedEto`1"
}
],
"implementingInterfaces": [
{
"name": "IDistributedEventHandler<EntityUpdatedEto`1>",
"namespace": "Volo.Abp.EventBus.Distributed",
"declaringAssemblyName": "Volo.Abp.EventBus.Abstractions",
"fullName": "Volo.Abp.EventBus.Distributed.IDistributedEventHandler<EntityUpdatedEto`1>"
},
{
"name": "IEventHandler",
"namespace": "Volo.Abp.EventBus",
"declaringAssemblyName": "Volo.Abp.EventBus.Abstractions",
"fullName": "Volo.Abp.EventBus.IEventHandler"
},
{
"name": "ITransientDependency",
"namespace": "Volo.Abp.DependencyInjection",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.DependencyInjection.ITransientDependency"
}
],
"contentType": "eventHandler",
"name": "BlogUserSynchronizer",
"summary": null
},
{
"eventHandlerType": "Local",
"namespace": "Volo.Blogging.Posts",
"subscribedEvents": [
{
"underlyingType": null,
"name": "PostChangedEvent",
"namespace": "Volo.Blogging.Posts",
"declaringAssemblyName": "Volo.Blogging.Domain",
"fullName": "Volo.Blogging.Posts.PostChangedEvent"
}
],
"implementingInterfaces": [
{
"name": "ILocalEventHandler<PostChangedEvent>",
"namespace": "Volo.Abp.EventBus",
"declaringAssemblyName": "Volo.Abp.EventBus.Abstractions",
"fullName": "Volo.Abp.EventBus.ILocalEventHandler<PostChangedEvent>"
},
{
"name": "IEventHandler",
"namespace": "Volo.Abp.EventBus",
"declaringAssemblyName": "Volo.Abp.EventBus.Abstractions",
"fullName": "Volo.Abp.EventBus.IEventHandler"
},
{
"name": "ITransientDependency",
"namespace": "Volo.Abp.DependencyInjection",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.DependencyInjection.ITransientDependency"
}
],
"contentType": "eventHandler",
"name": "PostCacheInvalidator",
"summary": null
} }
] ]
} }

39
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo.Blogging.EntityFrameworkCore.abppkg.analyze.json

@ -67,7 +67,44 @@
{ {
"namespace": "Volo.Blogging.EntityFrameworkCore", "namespace": "Volo.Blogging.EntityFrameworkCore",
"connectionStringName": "Blogging", "connectionStringName": "Blogging",
"databaseTables": [], "databaseTables": [
{
"entityFullName": "Volo.Blogging.Users.BlogUser",
"contentType": "databaseTable",
"name": "Users",
"summary": null
},
{
"entityFullName": "Volo.Blogging.Blogs.Blog",
"contentType": "databaseTable",
"name": "Blogs",
"summary": null
},
{
"entityFullName": "Volo.Blogging.Posts.Post",
"contentType": "databaseTable",
"name": "Posts",
"summary": null
},
{
"entityFullName": "Volo.Blogging.Tagging.Tag",
"contentType": "databaseTable",
"name": "Tags",
"summary": null
},
{
"entityFullName": "Volo.Blogging.Posts.PostTag",
"contentType": "databaseTable",
"name": "PostTags",
"summary": null
},
{
"entityFullName": "Volo.Blogging.Comments.Comment",
"contentType": "databaseTable",
"name": "Comments",
"summary": null
}
],
"replacedDbContexts": [], "replacedDbContexts": [],
"modelBuilderExtensionMethods": [ "modelBuilderExtensionMethods": [
{ {

294
modules/blogging/src/Volo.Blogging.HttpApi/Volo.Blogging.HttpApi.abppkg.analyze.json

@ -63,300 +63,6 @@
"contentType": "abpModule", "contentType": "abpModule",
"name": "BloggingHttpApiModule", "name": "BloggingHttpApiModule",
"summary": null "summary": null
},
{
"apis": [
{
"path": "api/blogging/files/{name}",
"method": "GET",
"groupName": "BlogFiles",
"responseType": "Volo.Blogging.Files.RawFileDto",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "name",
"type": "String"
}
]
},
{
"path": "api/blogging/files/www/{name}",
"method": "GET",
"groupName": "BlogFiles",
"responseType": "Volo.Abp.Content.IRemoteStreamContent",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "name",
"type": "String"
}
]
},
{
"path": "api/blogging/files/images/upload",
"method": "POST",
"groupName": "BlogFiles",
"responseType": "Volo.Blogging.Files.FileUploadOutputDto",
"responseTypeAsJson": "{\r\n \"name\": \"string\",\r\n \"webUrl\": \"string\"\r\n}",
"parameterDescriptions": [
{
"name": "File",
"type": "IRemoteStreamContent"
},
{
"name": "Name",
"type": "String"
}
]
},
{
"path": "api/blogging/blogs",
"method": "GET",
"groupName": "Blogs",
"responseType": "Volo.Abp.Application.Dtos.ListResultDto`1[Volo.Blogging.Blogs.Dtos.BlogDto]",
"responseTypeAsJson": "{\r\n \"items\": [\r\n {\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ]\r\n}",
"parameterDescriptions": []
},
{
"path": "api/blogging/blogs/by-shortname/{shortName}",
"method": "GET",
"groupName": "Blogs",
"responseType": "Volo.Blogging.Blogs.Dtos.BlogDto",
"responseTypeAsJson": "{\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "shortName",
"type": "String"
}
]
},
{
"path": "api/blogging/blogs/{id}",
"method": "GET",
"groupName": "Blogs",
"responseType": "Volo.Blogging.Blogs.Dtos.BlogDto",
"responseTypeAsJson": "{\r\n \"name\": \"string\",\r\n \"shortName\": \"string\",\r\n \"description\": \"string\",\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
},
{
"path": "api/blogging/comments/hierarchical/{postId}",
"method": "GET",
"groupName": "Comments",
"responseType": "System.Collections.Generic.List`1[Volo.Blogging.Comments.Dtos.CommentWithRepliesDto]",
"responseTypeAsJson": "[\r\n {\r\n \"comment\": {\r\n \"repliedCommentId\": null,\r\n \"text\": \"string\",\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"replies\": [\r\n {\r\n \"repliedCommentId\": null,\r\n \"text\": \"string\",\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ]\r\n }\r\n]",
"parameterDescriptions": [
{
"name": "postId",
"type": "Guid"
}
]
},
{
"path": "api/blogging/comments",
"method": "POST",
"groupName": "Comments",
"responseType": "Volo.Blogging.Comments.Dtos.CommentWithDetailsDto",
"responseTypeAsJson": "{\r\n \"repliedCommentId\": null,\r\n \"text\": \"string\",\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "input",
"type": "CreateCommentDto"
}
]
},
{
"path": "api/blogging/comments/{id}",
"method": "PUT",
"groupName": "Comments",
"responseType": "Volo.Blogging.Comments.Dtos.CommentWithDetailsDto",
"responseTypeAsJson": "{\r\n \"repliedCommentId\": null,\r\n \"text\": \"string\",\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
},
{
"name": "input",
"type": "UpdateCommentDto"
}
]
},
{
"path": "api/blogging/comments/{id}",
"method": "DELETE",
"groupName": "Comments",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
},
{
"path": "api/blogging/posts/{blogId}/all",
"method": "GET",
"groupName": "Posts",
"responseType": "Volo.Abp.Application.Dtos.ListResultDto`1[Volo.Blogging.Posts.PostWithDetailsDto]",
"responseTypeAsJson": "{\r\n \"items\": [\r\n {\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ]\r\n}",
"parameterDescriptions": [
{
"name": "blogId",
"type": "Guid"
},
{
"name": "tagName",
"type": "String"
}
]
},
{
"path": "api/blogging/posts/{blogId}/all/by-time",
"method": "GET",
"groupName": "Posts",
"responseType": "Volo.Abp.Application.Dtos.ListResultDto`1[Volo.Blogging.Posts.PostWithDetailsDto]",
"responseTypeAsJson": "{\r\n \"items\": [\r\n {\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ]\r\n}",
"parameterDescriptions": [
{
"name": "blogId",
"type": "Guid"
}
]
},
{
"path": "api/blogging/posts/read",
"method": "GET",
"groupName": "Posts",
"responseType": "Volo.Blogging.Posts.PostWithDetailsDto",
"responseTypeAsJson": "{\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "Url",
"type": "String"
},
{
"name": "BlogId",
"type": "Guid"
}
]
},
{
"path": "api/blogging/posts/{id}",
"method": "GET",
"groupName": "Posts",
"responseType": "Volo.Blogging.Posts.PostWithDetailsDto",
"responseTypeAsJson": "{\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
},
{
"path": "api/blogging/posts",
"method": "POST",
"groupName": "Posts",
"responseType": "Volo.Blogging.Posts.PostWithDetailsDto",
"responseTypeAsJson": "{\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "input",
"type": "CreatePostDto"
}
]
},
{
"path": "api/blogging/posts/{id}",
"method": "PUT",
"groupName": "Posts",
"responseType": "Volo.Blogging.Posts.PostWithDetailsDto",
"responseTypeAsJson": "{\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n}",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
},
{
"name": "input",
"type": "UpdatePostDto"
}
]
},
{
"path": "api/blogging/posts/user/{userId}",
"method": "GET",
"groupName": "Posts",
"responseType": "System.Collections.Generic.List`1[Volo.Blogging.Posts.PostWithDetailsDto]",
"responseTypeAsJson": "[\r\n {\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n]",
"parameterDescriptions": [
{
"name": "userId",
"type": "Guid"
}
]
},
{
"path": "api/blogging/posts/{blogId}/latest/{count}",
"method": "GET",
"groupName": "Posts",
"responseType": "System.Collections.Generic.List`1[Volo.Blogging.Posts.PostWithDetailsDto]",
"responseTypeAsJson": "[\r\n {\r\n \"blogId\": \"00000000-0000-0000-0000-000000000000\",\r\n \"title\": \"string\",\r\n \"coverImage\": \"string\",\r\n \"url\": \"string\",\r\n \"content\": \"string\",\r\n \"description\": \"string\",\r\n \"readCount\": 0,\r\n \"commentCount\": 0,\r\n \"writer\": {\r\n \"tenantId\": null,\r\n \"name\": \"string\",\r\n \"surname\": \"string\",\r\n \"userName\": \"string\",\r\n \"email\": \"string\",\r\n \"emailConfirmed\": false,\r\n \"phoneNumber\": \"string\",\r\n \"phoneNumberConfirmed\": false,\r\n \"webSite\": \"string\",\r\n \"twitter\": \"string\",\r\n \"github\": \"string\",\r\n \"linkedin\": \"string\",\r\n \"company\": \"string\",\r\n \"jobTitle\": \"string\",\r\n \"biography\": \"string\",\r\n \"extraProperties\": {\r\n \"string\": \"object\"\r\n },\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n },\r\n \"tags\": [\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n ],\r\n \"concurrencyStamp\": \"string\",\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n]",
"parameterDescriptions": [
{
"name": "blogId",
"type": "Guid"
},
{
"name": "count",
"type": "Int32"
}
]
},
{
"path": "api/blogging/posts/{id}",
"method": "DELETE",
"groupName": "Posts",
"responseType": "System.Void",
"responseTypeAsJson": "",
"parameterDescriptions": [
{
"name": "id",
"type": "Guid"
}
]
},
{
"path": "api/blogging/tags/popular/{blogId}",
"method": "GET",
"groupName": "Tags",
"responseType": "System.Collections.Generic.List`1[Volo.Blogging.Tagging.Dtos.TagDto]",
"responseTypeAsJson": "[\r\n {\r\n \"name\": \"string\",\r\n \"description\": \"string\",\r\n \"usageCount\": 0,\r\n \"isDeleted\": false,\r\n \"deleterId\": null,\r\n \"deletionTime\": null,\r\n \"lastModificationTime\": null,\r\n \"lastModifierId\": null,\r\n \"creationTime\": \"0001-01-01T00:00:00\",\r\n \"creatorId\": null,\r\n \"id\": \"00000000-0000-0000-0000-000000000000\"\r\n }\r\n]",
"parameterDescriptions": [
{
"name": "blogId",
"type": "Guid"
},
{
"name": "ResultCount",
"type": "Int32"
},
{
"name": "MinimumPostCount",
"type": "Nullable<Int32>"
}
]
}
],
"contentType": "httpApi",
"name": "Defined Http Apis",
"summary": null
} }
] ]
} }

2
modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs

@ -16,7 +16,7 @@ namespace Volo.Blogging.Blogs
public virtual async Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default) public virtual async Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); return await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken));
} }
} }
} }

9
modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Domain.Repositories.MongoDB;
@ -18,7 +19,7 @@ namespace Volo.Blogging.Comments
public virtual async Task<List<Comment>> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default) public virtual async Task<List<Comment>> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.Where(a => a.PostId == postId) .Where(a => a.PostId == postId)
.OrderBy(a => a.CreationTime) .OrderBy(a => a.CreationTime)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
@ -26,19 +27,19 @@ namespace Volo.Blogging.Comments
public virtual async Task<int> GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default) public virtual async Task<int> GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.CountAsync(a => a.PostId == postId, GetCancellationToken(cancellationToken)); .CountAsync(a => a.PostId == postId, GetCancellationToken(cancellationToken));
} }
public virtual async Task<List<Comment>> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default) public virtual async Task<List<Comment>> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync(cancellationToken)) return await (await GetQueryableAsync(cancellationToken))
.Where(a => a.RepliedCommentId == id).ToListAsync(GetCancellationToken(cancellationToken)); .Where(a => a.RepliedCommentId == id).ToListAsync(GetCancellationToken(cancellationToken));
} }
public virtual async Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default) public virtual async Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default)
{ {
var recordsToDelete = (await GetMongoQueryableAsync(cancellationToken)).Where(pt => pt.PostId == id); var recordsToDelete = (await GetQueryableAsync(cancellationToken)).Where(pt => pt.PostId == id);
foreach (var record in recordsToDelete) foreach (var record in recordsToDelete)
{ {

13
modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; using MongoDB.Driver.Linq;
using Nito.AsyncEx; using Nito.AsyncEx;
@ -20,13 +21,13 @@ namespace Volo.Blogging.Posts
public virtual async Task<List<Post>> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default) public virtual async Task<List<Post>> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default)
{ {
return await (await GetMongoQueryableAsync(cancellationToken)).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); return await (await GetQueryableAsync(cancellationToken)).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken));
} }
public virtual async Task<bool> IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default) public virtual async Task<bool> IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default)
{ {
var query = (await GetMongoQueryableAsync(cancellationToken)).Where(p => blogId == p.BlogId && p.Url == url); var query = (await GetQueryableAsync(cancellationToken)).Where(p => blogId == p.BlogId && p.Url == url);
if (excludingPostId != null) if (excludingPostId != null)
{ {
@ -38,7 +39,7 @@ namespace Volo.Blogging.Posts
public virtual async Task<Post> GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default) public virtual async Task<Post> GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default)
{ {
var post = await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); var post = await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken));
if (post == null) if (post == null)
{ {
@ -50,7 +51,7 @@ namespace Volo.Blogging.Posts
public virtual async Task<List<Post>> GetOrderedList(Guid blogId, bool @descending = false, CancellationToken cancellationToken = default) public virtual async Task<List<Post>> GetOrderedList(Guid blogId, bool @descending = false, CancellationToken cancellationToken = default)
{ {
var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId); var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId);
if (!descending) if (!descending)
{ {
@ -62,7 +63,7 @@ namespace Volo.Blogging.Posts
public virtual async Task<List<Post>> GetListByUserIdAsync(Guid userId, CancellationToken cancellationToken = default) public virtual async Task<List<Post>> GetListByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
{ {
var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.CreatorId == userId) var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.CreatorId == userId)
.OrderByDescending(x => x.CreationTime); .OrderByDescending(x => x.CreationTime);
return await query.ToListAsync(GetCancellationToken(cancellationToken)); return await query.ToListAsync(GetCancellationToken(cancellationToken));
@ -70,7 +71,7 @@ namespace Volo.Blogging.Posts
public virtual async Task<List<Post>> GetLatestBlogPostsAsync(Guid blogId, int count, CancellationToken cancellationToken = default) public virtual async Task<List<Post>> GetLatestBlogPostsAsync(Guid blogId, int count, CancellationToken cancellationToken = default)
{ {
var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId) var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId)
.OrderByDescending(x => x.CreationTime) .OrderByDescending(x => x.CreationTime)
.Take(count); .Take(count);

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

Loading…
Cancel
Save