Browse Source

Merge branch 'dev' into auto-merge/rel-9-2/3628

pull/22610/head
maliming 2 years ago
committed by GitHub
parent
commit
82bfeca60d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/ISSUE_TEMPLATE/config.yml
  2. 2
      Directory.Packages.props
  3. 8
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  5. 4
      common.props
  6. 2
      docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md
  7. 2
      docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md
  8. 2
      docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md
  9. 2
      docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md
  10. 2
      docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md
  11. 2
      docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md
  12. 2
      docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md
  13. 2
      docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md
  14. 2
      docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md
  15. 151
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md
  16. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/cover-image.png
  17. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/require-email-verification-for-register.png
  18. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/select-account.png
  19. BIN
      docs/en/Blog-Posts/2025-03-27 v9_2_Preview/studio-switch-to-preview.png
  20. 2
      docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md
  21. 2
      docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md
  22. 2
      docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md
  23. 2
      docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md
  24. 2
      docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md
  25. 2
      docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md
  26. 2
      docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md
  27. 2
      docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md
  28. 2
      docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md
  29. 2
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md
  30. 38
      docs/en/Community-Articles/2025-03-27-Announcing-the-ABP-Contributor-Program/post.md
  31. 1
      docs/en/cli/index.md
  32. 2
      docs/en/docs-nav.json
  33. 30
      docs/en/studio/installation.md
  34. 63
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  35. 7
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs
  36. 6
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpRabbitMqEventBusOptions.cs
  37. 44
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  38. 17
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs
  39. 84
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs
  40. 66
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ConnectionPool.cs
  41. 6
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs
  42. 2
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelAccessor.cs
  43. 5
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelPool.cs
  44. 5
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IConnectionPool.cs
  45. 2
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqMessageConsumer.cs
  46. 15
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs
  47. 55
      framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs
  48. 27
      latest-versions.json
  49. 3
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml
  50. 8
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css
  51. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js

2
.github/ISSUE_TEMPLATE/config.yml

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

2
Directory.Packages.props

@ -142,7 +142,7 @@
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.13.0" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.13.0" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.13.0" />
<PackageVersion Include="RabbitMQ.Client" Version="6.8.1" />
<PackageVersion Include="RabbitMQ.Client" Version="7.1.2" />
<PackageVersion Include="Rebus" Version="8.6.0" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.2.0" />
<PackageVersion Include="Scriban" Version="5.10.0" />

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

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

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

@ -1886,6 +1886,9 @@
"FaqIyzicoPaymentIssuesExplanation5": "<strong>Credit/Debit Cards</strong>: Visa, MasterCard, American Express, Discover, Diners Club, JCB",
"FaqIyzicoPaymentIssuesExplanation6": "<strong>Digital Wallets</strong>: PayPal, AliPay, WebMoney",
"FaqIyzicoPaymentIssuesExplanation7": "Alternatively, you can send the license amount directly via bank wire transfer. For our bank account details, please visit: <a href='https://volosoft.com/bank-account'>Bank Account Information</a> (use USD currency).",
"FaqIyzicoPaymentIssuesExplanation8": "ABP website doesn't save or process your credit card. We use payment gateways for this and the entire transaction is handled by payment gateways. We have no authority to interfere with the payment process or fix the payment steps. If you have further questions or need additional support, feel free to contact us at <a href='https://abp.io/contact'>abp.io/contact</a>."
"FaqIyzicoPaymentIssuesExplanation8": "ABP website doesn't save or process your credit card. We use payment gateways for this and the entire transaction is handled by payment gateways. We have no authority to interfere with the payment process or fix the payment steps. If you have further questions or need additional support, feel free to contact us at <a href='https://abp.io/contact'>abp.io/contact</a>.",
"BiographyContainsUrlValidationMessage": "Biography cannot contain URL.",
"CreatePostSEOTitleInfo": "The SEO Title is what appears in search engine results as the clickable headline. By default, it matches the article’s title, but you can customize it here for better search visibility. Keep it clear, compelling, and include target keywords—ideally near the beginning. Aim for around 60 characters to avoid truncation in search results.",
"SEOTitle": "SEO Title"
}
}

4
common.props

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

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

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

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

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

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

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

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

@ -168,7 +168,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [How to use .NET Aspire with ABP framework](https://abp.io/community/articles/how-to-use-.net-aspire-with-abp-framework-h29km4kk) by [Berkan Şaşmaz](https://twitter.com/berkansasmazz)
* [Exciting New Feature in ABP.IO CMS Kit: Marked Item System](https://abp.io/community/articles/exciting-new-feature-in-abp.io-cms-kit-marked-item-system.-2hvpq0me) by [Suhaib Mousa](https://abp.io/community/members/suhaibmousa032@gmail.com)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion

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

@ -70,7 +70,7 @@ As always, exciting articles have been contributed by the ABP community. I will
* [Introducing the Google Cloud Storage BLOB Provider](https://abp.io/community/articles/introducing-the-google-cloud-storage-blob-provider-yrt6azc0) by [Engincan Veske](https://twitter.com/EngincanVeske)
* [Switching Between Organization Units](https://abp.io/community/articles/switching-between-organization-units-i5tokpzt) by [Liming Ma](https://github.com/maliming)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## About the Next Version

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

@ -214,7 +214,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [ABP-Powered Web App with Inertia.js, React, and Vite](https://abp.io/community/articles/abppowered-web-app-with-inertia.js-react-and-vite-j7cccvad) by [Anto Subash](https://antosubash.com/)
* [Multi-Tenancy Support in Angular Apps with ABP.IO](https://abp.io/community/articles/multitenancy-support-in-angular-apps-with-abp.io-lw9l36c5) by [HeadChannel Team](https://headchannel.co.uk/)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion

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

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

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

@ -138,7 +138,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [The new Unit Test structure in ABP application](https://abp.io/community/articles/the-new-unit-test-structure-in-abp-application-4vvvp2oy) by [Liming Ma](https://github.com/maliming)
* [How to Use OpenAI API with ABP Framework](https://abp.io/community/articles/how-to-use-openai-api-with-abp-framework-rsfvihla) by [Berkan Şaşmaz](https://github.com/berkansasmaz)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion

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

@ -67,7 +67,7 @@ As always, exciting articles have been contributed by the ABP community. I will
* [Containerization: Blazor WASM + JWT Web API => Docker](https://abp.io/community/articles/containerization-blazor-wasm-jwt-web-api-docker-i3eirlsf) by [Bart Van Hoey](https://abp.io/community/members/bartvanhoey)
* [Configuring Post-Logout Redirect URI in ABP Based Blazor Applications with OpenIddict](https://abp.io/community/articles/configuring-postlogout-redirect-uri-in-abp-based-blazor-applications-with-openiddict-1t84suxg) by [Engincan Veske](https://github.com/EngincanV)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
### ABP Community Talks 2025.2: Real World Problems and Solutions with AI

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

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

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

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

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

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

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

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

@ -165,7 +165,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [How to use .NET Aspire with ABP framework](https://abp.io/community/articles/how-to-use-.net-aspire-with-abp-framework-h29km4kk) by [Berkan Şaşmaz](https://twitter.com/berkansasmazz)
* [Exciting New Feature in ABP.IO CMS Kit: Marked Item System](https://abp.io/community/articles/exciting-new-feature-in-abp.io-cms-kit-marked-item-system.-2hvpq0me) by [Suhaib Mousa](https://abp.io/community/members/suhaibmousa032@gmail.com)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion

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

@ -70,7 +70,7 @@ As always, exciting articles have been contributed by the ABP community. I will
* [Introducing the Google Cloud Storage BLOB Provider](https://abp.io/community/articles/introducing-the-google-cloud-storage-blob-provider-yrt6azc0) by [Engincan Veske](https://twitter.com/EngincanVeske)
* [Switching Between Organization Units](https://abp.io/community/articles/switching-between-organization-units-i5tokpzt) by [Liming Ma](https://github.com/maliming)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## About the Next Version

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

@ -210,7 +210,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [ABP-Powered Web App with Inertia.js, React, and Vite](https://abp.io/community/articles/abppowered-web-app-with-inertia.js-react-and-vite-j7cccvad) by [Anto Subash](https://antosubash.com/)
* [Multi-Tenancy Support in Angular Apps with ABP.IO](https://abp.io/community/articles/multitenancy-support-in-angular-apps-with-abp.io-lw9l36c5) by [HeadChannel Team](https://headchannel.co.uk/)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion

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

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

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

@ -136,7 +136,7 @@ There are exciting articles contributed by the ABP community as always. I will h
* [The new Unit Test structure in ABP application](https://abp.io/community/articles/the-new-unit-test-structure-in-abp-application-4vvvp2oy) by [Liming Ma](https://github.com/maliming)
* [How to Use OpenAI API with ABP Framework](https://abp.io/community/articles/how-to-use-openai-api-with-abp-framework-rsfvihla) by [Berkan Şaşmaz](https://github.com/berkansasmaz)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community.
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community.
## Conclusion

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

@ -6,6 +6,8 @@ All these scenarios require us to handle timezone conversions correctly in our a
In this article, we'll show you step by step how to handle multi-timezone in the ABP framework.
> The content mentioned in this article will be available after the ABP 9.2 version
## Timezone Settings
The ABP framework provides a setting called `Abp.Timing.TimeZone` for setting and getting the timezone of users, tenants, or applications. The default value is empty, which means the application will use the server's time zone. Check out the [Timing documentation](https://abp.io/docs/latest/framework/infrastructure/timing) for more information.

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

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

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

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

1
docs/en/cli/index.md

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

2
docs/en/docs-nav.json

@ -614,7 +614,7 @@
"path": "framework/infrastructure/blob-storing/google.md"
},
{
"text": "Bunny Provider",
"text": "Bunny.Net Provider",
"path": "framework/infrastructure/blob-storing/bunny.md"
},
{

30
docs/en/studio/installation.md

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

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

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

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

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

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

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

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

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

17
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
@ -19,7 +20,6 @@ public class AbpRabbitMqModule : AbpModule
{
foreach (var connectionFactory in options.Connections.Values)
{
connectionFactory.DispatchConsumersAsync = true;
connectionFactory.AutomaticRecoveryEnabled = false;
}
});
@ -27,12 +27,17 @@ public class AbpRabbitMqModule : AbpModule
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
context.ServiceProvider
AsyncHelper.RunSync(() => OnApplicationShutdownAsync(context));
}
public async override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
await context.ServiceProvider
.GetRequiredService<IChannelPool>()
.Dispose();
.DisposeAsync();
context.ServiceProvider
await context.ServiceProvider
.GetRequiredService<IConnectionPool>()
.Dispose();
.DisposeAsync();
}
}

84
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs

@ -3,10 +3,12 @@ using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using RabbitMQ.Client;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.RabbitMQ;
@ -16,6 +18,8 @@ public class ChannelPool : IChannelPool, ISingletonDependency
protected ConcurrentDictionary<string, ChannelPoolItem> Channels { get; }
protected SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
protected bool IsDisposed { get; private set; }
protected TimeSpan TotalDisposeWaitDuration { get; set; } = TimeSpan.FromSeconds(10);
@ -29,28 +33,54 @@ public class ChannelPool : IChannelPool, ISingletonDependency
Logger = NullLogger<ChannelPool>.Instance;
}
public virtual IChannelAccessor Acquire(string? channelName = null, string? connectionName = null)
public virtual async Task<IChannelAccessor> AcquireAsync(string? channelName = null, string? connectionName = null)
{
CheckDisposed();
channelName = channelName ?? "";
var poolItem = Channels.GetOrAdd(
channelName,
_ => new ChannelPoolItem(CreateChannel(channelName, connectionName))
);
ChannelPoolItem poolItem;
if (Channels.TryGetValue(channelName, out var existingChannelPoolItem))
{
poolItem = existingChannelPoolItem;
}
else
{
using (await Semaphore.LockAsync())
{
if (Channels.TryGetValue(channelName, out var existingChannelPoolItem2))
{
poolItem = existingChannelPoolItem2;
}
else
{
poolItem = new ChannelPoolItem(await CreateChannelAsync(channelName, connectionName));
Channels.TryAdd(channelName, poolItem);
}
}
}
poolItem.Acquire();
if (poolItem.Channel.IsClosed)
{
poolItem.Dispose();
await poolItem.DisposeAsync();
Channels.TryRemove(channelName, out _);
poolItem = Channels.GetOrAdd(
channelName,
_ => new ChannelPoolItem(CreateChannel(channelName, connectionName))
);
using (await Semaphore.LockAsync())
{
if (Channels.TryGetValue(channelName, out var existingChannelPoolItem3))
{
poolItem = existingChannelPoolItem3;
}
else
{
poolItem = new ChannelPoolItem(await CreateChannelAsync(channelName, connectionName));
Channels.TryAdd(channelName, poolItem);
}
}
poolItem.Acquire();
}
@ -61,14 +91,14 @@ public class ChannelPool : IChannelPool, ISingletonDependency
);
}
protected virtual IModel CreateChannel(string channelName, string? connectionName)
protected virtual async Task<IChannel> CreateChannelAsync(string channelName, string? connectionName)
{
return ConnectionPool
.Get(connectionName)
.CreateModel();
return await (await ConnectionPool
.GetAsync(connectionName))
.CreateChannelAsync();
}
protected void CheckDisposed()
protected virtual void CheckDisposed()
{
if (IsDisposed)
{
@ -76,7 +106,7 @@ public class ChannelPool : IChannelPool, ISingletonDependency
}
}
public void Dispose()
public async ValueTask DisposeAsync()
{
if (IsDisposed)
{
@ -104,10 +134,12 @@ public class ChannelPool : IChannelPool, ISingletonDependency
try
{
poolItem.WaitIfInUse(remainingWaitDuration);
poolItem.Dispose();
await poolItem.DisposeAsync();
}
catch
{ }
{
// ignored
}
poolItemDisposeStopwatch.Stop();
@ -128,9 +160,9 @@ public class ChannelPool : IChannelPool, ISingletonDependency
Channels.Clear();
}
protected class ChannelPoolItem : IDisposable
protected class ChannelPoolItem : IAsyncDisposable
{
public IModel Channel { get; }
public IChannel Channel { get; }
public bool IsInUse {
get => _isInUse;
@ -138,7 +170,7 @@ public class ChannelPool : IChannelPool, ISingletonDependency
}
private volatile bool _isInUse;
public ChannelPoolItem(IModel channel)
public ChannelPoolItem(IChannel channel)
{
Channel = channel;
}
@ -178,21 +210,21 @@ public class ChannelPool : IChannelPool, ISingletonDependency
}
}
public void Dispose()
public async ValueTask DisposeAsync()
{
Channel.Dispose();
await Channel.DisposeAsync();
}
}
protected class ChannelAccessor : IChannelAccessor
{
public IModel Channel { get; }
public IChannel Channel { get; }
public string Name { get; }
private readonly Action _disposeAction;
public ChannelAccessor(IModel channel, string name, Action disposeAction)
public ChannelAccessor(IChannel channel, string name, Action disposeAction)
{
_disposeAction = disposeAction;
Name = name;

66
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ConnectionPool.cs

@ -1,9 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.RabbitMQ;
@ -11,55 +13,51 @@ public class ConnectionPool : IConnectionPool, ISingletonDependency
{
protected AbpRabbitMqOptions Options { get; }
protected ConcurrentDictionary<string, Lazy<IConnection>> Connections { get; }
protected ConcurrentDictionary<string, IConnection> Connections { get; }
protected SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
private bool _isDisposed;
public ConnectionPool(IOptions<AbpRabbitMqOptions> options)
{
Options = options.Value;
Connections = new ConcurrentDictionary<string, Lazy<IConnection>>();
Connections = new ConcurrentDictionary<string, IConnection>();
}
public virtual IConnection Get(string? connectionName = null)
public virtual async Task<IConnection> GetAsync(string? connectionName = null)
{
connectionName ??= RabbitMqConnections.DefaultConnectionName;
var connectionFactory = Options.Connections.GetOrDefault(connectionName);
try
using (await Semaphore.LockAsync())
{
var connection = GetConnection(connectionName, connectionFactory);
if (connection.IsOpen)
connectionName ??= RabbitMqConnections.DefaultConnectionName;
if (Connections.TryGetValue(connectionName, out var existingConnection) && existingConnection.IsOpen)
{
return connection;
return existingConnection;
}
connection.Dispose();
Connections.TryRemove(connectionName, out _);
return GetConnection(connectionName, connectionFactory);
}
catch (Exception)
{
Connections.TryRemove(connectionName, out _);
throw;
if(existingConnection != null)
{
await existingConnection.DisposeAsync();
}
var connectionFactory = Options.Connections.GetOrDefault(connectionName);
var connection = await GetConnectionAsync(connectionName, connectionFactory);
Connections[connectionName] = connection;
return connection;
}
}
protected virtual IConnection GetConnection(string connectionName, ConnectionFactory connectionFactory)
protected virtual async Task<IConnection> GetConnectionAsync(string connectionName, ConnectionFactory connectionFactory)
{
return Connections.GetOrAdd(
connectionName, () => new Lazy<IConnection>(() =>
{
var hostnames = connectionFactory.HostName.TrimEnd(';').Split(';');
// Handle Rabbit MQ Cluster.
return hostnames.Length == 1
? connectionFactory.CreateConnection()
: connectionFactory.CreateConnection(hostnames);
})
).Value;
var hostnames = connectionFactory.HostName.TrimEnd(';').Split(';');
// Handle Rabbit MQ Cluster.
return hostnames.Length == 1
? await connectionFactory.CreateConnectionAsync()
: await connectionFactory.CreateConnectionAsync(hostnames);
}
public void Dispose()
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
@ -72,11 +70,11 @@ public class ConnectionPool : IConnectionPool, ISingletonDependency
{
try
{
connection.Value.Dispose();
await connection.DisposeAsync();
}
catch
{
// ignored
}
}

6
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs

@ -12,19 +12,19 @@ public class ExchangeDeclareConfiguration
public bool AutoDelete { get; set; }
public IDictionary<string, object> Arguments { get; }
public IDictionary<string, object?> Arguments { get; }
public ExchangeDeclareConfiguration(
string exchangeName,
string type,
bool durable = false,
bool autoDelete = false,
IDictionary<string, object>? arguments = null)
IDictionary<string, object?>? arguments = null)
{
ExchangeName = exchangeName;
Type = type;
Durable = durable;
AutoDelete = autoDelete;
Arguments = arguments?? new Dictionary<string, object>();
Arguments = arguments?? new Dictionary<string, object?>();
}
}

2
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelAccessor.cs

@ -10,7 +10,7 @@ public interface IChannelAccessor : IDisposable
/// Never dispose the <see cref="Channel"/> object.
/// Instead, dispose the <see cref="IChannelAccessor"/> after usage.
/// </summary>
IModel Channel { get; }
IChannel Channel { get; }
/// <summary>
/// Name of the channel.

5
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelPool.cs

@ -1,8 +1,9 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.RabbitMQ;
public interface IChannelPool : IDisposable
public interface IChannelPool : IAsyncDisposable
{
IChannelAccessor Acquire(string? channelName = null, string? connectionName = null);
Task<IChannelAccessor> AcquireAsync(string? channelName = null, string? connectionName = null);
}

5
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IConnectionPool.cs

@ -1,9 +1,10 @@
using System;
using System.Threading.Tasks;
using RabbitMQ.Client;
namespace Volo.Abp.RabbitMQ;
public interface IConnectionPool : IDisposable
public interface IConnectionPool : IAsyncDisposable
{
IConnection Get(string? connectionName = null);
Task<IConnection> GetAsync(string? connectionName = null);
}

2
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqMessageConsumer.cs

@ -11,5 +11,5 @@ public interface IRabbitMqMessageConsumer
Task UnbindAsync(string routingKey);
void OnMessageReceived(Func<IModel, BasicDeliverEventArgs, Task> callback);
void OnMessageReceived(Func<IChannel, BasicDeliverEventArgs, Task> callback);
}

15
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using RabbitMQ.Client;
@ -13,10 +14,10 @@ public class QueueDeclareConfiguration
public bool Exclusive { get; set; }
public bool AutoDelete { get; set; }
public ushort? PrefetchCount { get; set; }
public IDictionary<string, object> Arguments { get; }
public ushort? PrefetchCount { get; set; }
public IDictionary<string, object?> Arguments { get; }
public QueueDeclareConfiguration(
[NotNull] string queueName,
@ -24,19 +25,19 @@ public class QueueDeclareConfiguration
bool exclusive = false,
bool autoDelete = false,
ushort? prefetchCount = null,
IDictionary<string, object>? arguments = null)
IDictionary<string, object?>? arguments = null)
{
QueueName = queueName;
Durable = durable;
Exclusive = exclusive;
AutoDelete = autoDelete;
Arguments = arguments?? new Dictionary<string, object>();
Arguments = arguments?? new Dictionary<string, object?>();
PrefetchCount = prefetchCount;
}
public virtual QueueDeclareOk Declare(IModel channel)
public virtual async Task<QueueDeclareOk> DeclareAsync(IChannel channel)
{
return channel.QueueDeclare(
return await channel.QueueDeclareAsync(
queue: QueueName,
durable: Durable,
exclusive: Exclusive,

55
framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs

@ -5,6 +5,7 @@ using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
@ -28,13 +29,13 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
protected string? ConnectionName { get; private set; }
protected ConcurrentBag<Func<IModel, BasicDeliverEventArgs, Task>> Callbacks { get; }
protected ConcurrentBag<Func<IChannel, BasicDeliverEventArgs, Task>> Callbacks { get; }
protected IModel? Channel { get; private set; }
protected IChannel? Channel { get; private set; }
protected ConcurrentQueue<QueueBindCommand> QueueBindCommands { get; }
protected object ChannelSendSyncLock { get; } = new object();
protected SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
public RabbitMqMessageConsumer(
IConnectionPool connectionPool,
@ -47,7 +48,7 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
Logger = NullLogger<RabbitMqMessageConsumer>.Instance;
QueueBindCommands = new ConcurrentQueue<QueueBindCommand>();
Callbacks = new ConcurrentBag<Func<IModel, BasicDeliverEventArgs, Task>>();
Callbacks = new ConcurrentBag<Func<IChannel, BasicDeliverEventArgs, Task>>();
Timer.Period = 5000; //5 sec.
Timer.Elapsed = Timer_Elapsed;
@ -88,21 +89,21 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
return;
}
lock (ChannelSendSyncLock)
using (await Semaphore.LockAsync())
{
if (QueueBindCommands.TryPeek(out var command))
{
switch (command.Type)
{
case QueueBindType.Bind:
Channel.QueueBind(
await Channel.QueueBindAsync(
queue: Queue.QueueName,
exchange: Exchange.ExchangeName,
routingKey: command.RoutingKey
);
break;
case QueueBindType.Unbind:
Channel.QueueUnbind(
await Channel.QueueUnbindAsync(
queue: Queue.QueueName,
exchange: Exchange.ExchangeName,
routingKey: command.RoutingKey
@ -124,7 +125,7 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
}
}
public virtual void OnMessageReceived(Func<IModel, BasicDeliverEventArgs, Task> callback)
public virtual void OnMessageReceived(Func<IChannel, BasicDeliverEventArgs, Task> callback)
{
Callbacks.Add(callback);
}
@ -144,11 +145,11 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
try
{
Channel = ConnectionPool
.Get(ConnectionName)
.CreateModel();
Channel = await (await ConnectionPool
.GetAsync(ConnectionName))
.CreateChannelAsync();
Channel.ExchangeDeclare(
await Channel.ExchangeDeclareAsync(
exchange: Exchange.ExchangeName,
type: Exchange.Type,
durable: Exchange.Durable,
@ -156,7 +157,7 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
arguments: Exchange.Arguments
);
Channel.QueueDeclare(
await Channel.QueueDeclareAsync(
queue: Queue.QueueName,
durable: Queue.Durable,
exclusive: Queue.Exclusive,
@ -166,13 +167,13 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
if (Queue.PrefetchCount.HasValue)
{
Channel.BasicQos(0, Queue.PrefetchCount.Value, false);
await Channel.BasicQosAsync(0, Queue.PrefetchCount.Value, false);
}
var consumer = new AsyncEventingBasicConsumer(Channel);
consumer.Received += HandleIncomingMessageAsync;
Channel.BasicConsume(
consumer.ReceivedAsync += HandleIncomingMessageAsync;
await Channel.BasicConsumeAsync(
queue: Queue.QueueName,
autoAck: false,
consumer: consumer
@ -194,17 +195,23 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen
await callback(Channel!, basicDeliverEventArgs);
}
Channel?.BasicAck(basicDeliverEventArgs.DeliveryTag, multiple: false);
if (Channel != null)
{
await Channel.BasicAckAsync(basicDeliverEventArgs.DeliveryTag, multiple: false);
}
}
catch (Exception ex)
{
try
{
Channel?.BasicNack(
basicDeliverEventArgs.DeliveryTag,
multiple: false,
requeue: true
);
if (Channel != null)
{
await Channel.BasicNackAsync(
basicDeliverEventArgs.DeliveryTag,
multiple: false,
requeue: true
);
}
}
// ReSharper disable once EmptyGeneralCatchClause
catch { }

27
latest-versions.json

@ -1,5 +1,14 @@
[
{
"version": "9.1.1",
"releaseDate": "",
"type": "stable",
"message": "",
"leptonx": {
"version": "4.1.1"
}
},
{
"version": "9.1.0",
"releaseDate": "",
"type": "stable",
@ -8,6 +17,24 @@
"version": "4.1.0"
}
},
{
"version": "9.0.8",
"releaseDate": "",
"type": "stable",
"message": "",
"leptonx": {
"version": "4.0.9"
}
},
{
"version": "9.0.7",
"releaseDate": "",
"type": "stable",
"message": "",
"leptonx": {
"version": "4.0.8"
}
},
{
"version": "9.0.6",
"releaseDate": "",

3
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml

@ -21,6 +21,7 @@
}
<abp-script src="/Pages/FeatureManagement/feature-management-modal.js" />
<abp-style src="/Pages/FeatureManagement/feature-management-modal.css" />
<form method="post" asp-page="/FeatureManagement/FeatureManagementModal" data-script-class="abp.modals.FeatureManagement" id="FeatureManagementForm">
<abp-modal id="featureManagmentModal" size="Large">
@ -39,7 +40,7 @@
<abp-tab title="@featureGroup.DisplayName" name="v-pills-tab-@featureGroup.GetNormalizedGroupName()">
<h4>@featureGroup.DisplayName</h4>
<hr class="mt-2 mb-3"/>
<div class="custom-scroll-content">
<div class="custom-scroll-content" style="height: 400px;">
<div class="pl-1 pt-1">
@for (var j = 0; j < featureGroup.Features.Count; j++)
{

8
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css

@ -0,0 +1,8 @@
.custom-scroll-content {
max-height: 400px;
}
.custom-scroll-container > .col-md-4 {
max-height: 500px;
}

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js

@ -88,7 +88,7 @@ var abp = abp || {};
$('.custom-scroll-content').mCustomScrollbar({
theme: 'minimal-dark',
});
$('.custom-scroll-container > .col-4').mCustomScrollbar({
$('.custom-scroll-container > .col-md-4').mCustomScrollbar({
theme: 'minimal-dark',
});
});

Loading…
Cancel
Save