diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9fca40ba4f..fe03c27997 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.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 diff --git a/Directory.Packages.props b/Directory.Packages.props index 72405c55cf..09c9a476fd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -138,7 +138,7 @@ - + diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 1ea52ff5f5..d056e70b09 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -690,6 +690,33 @@ "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 : JPEG, JPG, PNG, SVG ", + "ModuleImageInfo2": "Max file size : 1 MB ", + "ModuleImageInfo3": "Image Proportion : 1:1 ", + "ModuleImageInfo4": " Download a sample cover image ", + "AreYouSureToDeleteImage": "Are you sure you want to delete this image?", + "CompactPercentage": "Compact Percentage", + "CompactPercentageDescription": "Percentage of cache entries to remove during compaction (1-100)", + "Settings": "Settings", + "MaxSizeOfInMemoryCache": "Max Size Of In Memory Cache", + "MaxSizeOfInMemoryCacheInfo": "Gets or sets the maximum size of the memory cache.", + "SizeOfTriggerAutoCompact": "Size Of Trigger Auto Compact", + "SizeOfTriggerAutoCompactInfo": "Gets or sets the size threshold that triggers auto-compaction. When the cache size exceeds this value, auto-compaction will be performed", + "AutoCompactPercentage": "Auto Compact Percentage", + "AutoCompactPercentageInfo": "Gets or sets the percentage of items to remove when auto-compaction is triggered (1-100)", + "ClearCaches": "This clears all NuGet content cache", + "CacheLatestVersions": "Cache Latest Versions", + "VersionCount": "Version Count", + "VersionCountInfo": "Gets or sets the number of versions to cache", + "IncludeLeptonX": "Include LeptonX", + "IncludeLeptonXInfo": "Gets or sets whether to include LeptonX in the cache", + "IncludePrerelease": "Include Prerelease", + "IncludePrereleaseInfo": "Gets or sets whether to include prerelease versions in the cache", + "CacheVersionCount": "Cache Version Count", + "CacheVersionCountInfo": "Gets or sets the number of versions to cache", + "CacheLatestVersionsInfo": "This caches the latest versions of the NuGet packages" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index c56e50af4c..91f0fd81a3 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -1886,6 +1886,9 @@ "FaqIyzicoPaymentIssuesExplanation5": "Credit/Debit Cards: Visa, MasterCard, American Express, Discover, Diners Club, JCB", "FaqIyzicoPaymentIssuesExplanation6": "Digital Wallets: PayPal, AliPay, WebMoney", "FaqIyzicoPaymentIssuesExplanation7": "Alternatively, you can send the license amount directly via bank wire transfer. For our bank account details, please visit: Bank Account Information (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 abp.io/contact." + "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 abp.io/contact.", + "BiographyContainsUrlValidationMessage": "Biography cannot contain URL.", + "CreatePostSEOTitleInfo": "SEO URL is a clean, readable, keyword-rich URL that helps both users and search engines understand what this post is about. Keep it short with 60 characters. SEO titles over 60 characters will be truncated. Use hyphens (-) to separate words (not underscores). Include target keywords near the start. Lowercase only. No stop words unless needed (e.g: \"and\", \"or\", \"the\").", + "SEOTitle": "SEO URL" } } diff --git a/common.props b/common.props index 81b7cb8ecb..54e6251ac6 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 9.2.0-rc.3 - 4.2.0-rc.3 + 9.3.0-preview + 4.3.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md b/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md index b78e3235a2..b0b44b9e45 100644 --- a/docs/en/Blog-Posts/2022-03-08 v5_2_Preview/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md b/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md index 24f55474ad..70cb53c75e 100644 --- a/docs/en/Blog-Posts/2024-02-16 v8_1_Preview/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md b/docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md index 23260950d4..aadaae1c6f 100644 --- a/docs/en/Blog-Posts/2024-05-21 v8_2_Release/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md b/docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md index 2d8a767568..fc3590b0d5 100644 --- a/docs/en/Blog-Posts/2024-07-31 v8_3_Preview/post.md +++ b/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 diff --git a/docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md index e69fbb86ea..3ac9de075a 100644 --- a/docs/en/Blog-Posts/2024-09-06 v8_3_Release_Stable/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md b/docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md index f06648e586..db52eed7d5 100644 --- a/docs/en/Blog-Posts/2024-10-23 v9_0_Preview/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md index 26850bc57f..9c96351d96 100644 --- a/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md +++ b/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? diff --git a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md index bb8bfdf4c7..bbcc69c727 100644 --- a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md index 96096807f1..2e6764e9b7 100644 --- a/docs/en/Blog-Posts/2025-03-07 v9_1_Release_Stable/POST.md +++ b/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 diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md new file mode 100644 index 0000000000..e59e75f44a --- /dev/null +++ b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/POST.md @@ -0,0 +1,195 @@ +# 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 +* Using Timezone Settings to Display Datetime +* 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(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(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. + +### Using Timezone Settings to Display Datetime + +A significant enhancement in ABP v9.2 is the ability to use timezone settings to display `DateTime` values according to the user's or application's configured timezone. Introduced in `v9.2.0-rc.2`, this feature addresses the common challenge of ensuring users see accurate time information, regardless of their geographical location. + +Previously, `DateTime` values were often shown in the server's timezone or in UTC, which could cause confusion for users in different timezones. With this new feature, ABP applications can now respect the configured timezone and automatically convert and display datetime values accordingly. + +**Before setting the timezone:** + +Consider a scenario where you have a list of books, and the `CreationTime` property is displayed without any timezone consideration. It may appear in the server's default timezone: + +![before.png](before.png) +*(Screenshot of a Books page showing the CreationTime in a default timezone.)* + +**Setting the timezone:** + +To set the timezone, start by configuring the `AbpClockOptions` in your module's `ConfigureServices` method: + +```csharp +Configure(options => +{ + options.Kind = DateTimeKind.Utc; +}); +``` + +> By setting the `Kind` property of `AbpClockOptions` to `DateTimeKind.Utc`, ABP will normalize all datetime values. Times stored in the database and returned to the frontend will be in UTC. Additionally, the `SupportsMultipleTimezone` property of the `IClock` service will be **true**, and you’ll be able to configure the timezone from the UI under the _Settings_ page. + +After setting the `Kind` property, you can run your application and configure the timezone from the UI under the _Settings_ page. For example, set the timezone to "Asia/Tokyo (+09:00)": + +![configure-timezone.png](configure-timezone.png) +*(Screenshot showing the configuration setting for Abp.Timing.TimeZone, set to "Asia/Tokyo (+09:00)".)* + +> ABP provides the `Abp.Timing.TimeZone` setting, which allows you to configure the desired timezone at the application, tenant, or user level and this setting can be configured in the UI under the _Settings_ page, inside of the _Time Zone_ tab. + +**After setting the timezone:** + +Once the timezone is configured, ABP automatically handles the conversion and display of `DateTime` values. The `CreationTime` on the _Books_ page will now be shown in the **"Asia/Tokyo"** timezone. + +![after.png](after.png) +*(Screenshot of the _Books_ page after setting the timezone, showing the `CreationTime` adjusted to the "Asia/Tokyo" timezone.)* + +This feature utilizes the `IClock` service, which provides methods like `ConvertToUserTime` and `ConvertToUtc` to facilitate timezone conversions. By configuring the `Abp.Timing.TimeZone` setting, developers can ensure a consistent and user-friendly experience across applications with a global user base. +> For a more detailed guide on implementing and using this feature, refer to the article: [Developing a Multi-Timezone Application Using the ABP Framework](https://abp.io/community/articles/developing-a-multitimezone-application-using-the-abp-framework-zk7fnrdq). It offers step-by-step instructions and examples for handling multi-timezone scenarios in ABP applications. + +### Identity Pro Module: Require Email Verification to Register + +[ABP Identity Pro module](https://abp.io/docs/9.2/modules/identity-pro) has been enhanced with a new feature that allows administrators to require email verification during the registration process. This security improvement ensures that users must verify their email addresses before their registration is considered complete. Enabling this feature is especially important for applications that want to prevent spam registrations. + +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! diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/after.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/after.png new file mode 100644 index 0000000000..f9f72302a5 Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/after.png differ diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/before.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/before.png new file mode 100644 index 0000000000..14340f91ee Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/before.png differ diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/configure-timezone.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/configure-timezone.png new file mode 100644 index 0000000000..d383eee6f6 Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/configure-timezone.png differ diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/cover-image.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/cover-image.png new file mode 100644 index 0000000000..dd40555c8e Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/cover-image.png differ diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/require-email-verification-for-register.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/require-email-verification-for-register.png new file mode 100644 index 0000000000..597eaa6118 Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/require-email-verification-for-register.png differ diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/select-account.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/select-account.png new file mode 100644 index 0000000000..39d8c1b421 Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/select-account.png differ diff --git a/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/studio-switch-to-preview.png b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/studio-switch-to-preview.png new file mode 100644 index 0000000000..32f6d01edb Binary files /dev/null and b/docs/en/Blog-Posts/2025-03-27 v9_2_Preview/studio-switch-to-preview.png differ diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md index 03caff8007..98685933ba 100644 --- a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md +++ b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md @@ -470,6 +470,12 @@ namespace TemplateReplace * I highly recommend you to [check it out](https://commercial.abp.io/modules/Volo.TextTemplateManagement). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## References * [Text Templating](https://docs.abp.io/en/abp/latest/Text-Templating) diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md index 6445d83e85..f946db23b9 100644 --- a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md +++ b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md @@ -4,6 +4,12 @@ Hi, in this step by step article, we will see how we can integrate the Telerik Blazor Components to our Blazor UI. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Creating the Solution > ABP Framework offers startup templates to get into business faster. @@ -532,6 +538,12 @@ namespace TelerikComponents.Blazor.Pages ![final-result](./final-result.jpg) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I've tried to explain how we can integrate [Telerik Blazor Component](https://www.telerik.com/blazor-ui) to our Blazor UI. ABP Framework designed as modular, so that it can work with any UI library/framework. \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md index 0ec86d40c4..c499f6e33c 100644 --- a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md +++ b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md @@ -6,6 +6,12 @@ This article shows how we can use this workflow library within our ABP-based application. We will start with a couple of examples and then we will integrate the **Elsa Dashboard** (you can see it in the above gif) into our application to be able to design our workflows visually. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Source Code You can find the source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo). @@ -453,4 +459,10 @@ namespace ElsaDemo.Web.Menus * Now we can click the "Workflow" menu item, display the Elsa Dashboard and designing workflows. -![elsa-demo-result](./elsa-demo-result.gif) \ No newline at end of file +![elsa-demo-result](./elsa-demo-result.gif) + +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md index ae688d6981..81c0b6ea34 100644 --- a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md +++ b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md @@ -10,6 +10,12 @@ You can see the ER Diagram of our application below. This diagram will be helpfu When we've examined the ER Diagram, we can see the one-to-many relationship between the **Author** and the **Book** tables. Also, the many-to-many relationship (**BookCategory** table) between the **Book** and the **Category** tables. (There can be more than one category on each book and vice-versa in our scenario). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Source Code You can find the source code of the application at https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo . @@ -1664,6 +1670,12 @@ namespace BookStore.Web.Pages.Books ![Edit Book Modal](./book-update-modal.png) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Conclusion In this article, I've tried to explain how to create a many-to-many relationship by using the ABP framework. (by following DDD principles) diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md index 4095c62092..2953c552d3 100644 --- a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md +++ b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md @@ -4,6 +4,12 @@ In this article we will see how we can integrate the Syncfusion MVC Components into our ABP application. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Source Code You can find the source code of the application at https://github.com/EngincanV/ABP-Syncfusion-Components-Demo. @@ -314,6 +320,12 @@ After injecting the Syncfusion style and script into our application, our config ![](./calendar-component.png) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Conclusion In this article, we've explained how to integrate the **Syncfusion Components** into our applications. After following this article, you can use the Syncfusion components in your application. diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md index a6ad7596d4..d4102d8256 100644 --- a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md +++ b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md @@ -8,6 +8,12 @@ In the issue, there are helpful comments about hiding the ABP related endpoints I thought it would be better to enable/disable showing endpoints on runtime by simply selecting a checkbox on the Setting Management page and in this article I wanted to show you how, so let's dive in. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Source Code You can find the source code of the application at https://github.com/EngincanV/ABP-Hide-Swagger-Endpoint-Demo. @@ -445,6 +451,10 @@ services.AddAbpSwaggerGen( > For more info, please see the [Swagger Integration](https://docs.abp.io/en/abp/latest/API/Swagger-Integration#hide-abp-endpoints-on-swagger-ui) docs. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv --- Thanks for reading. \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md b/docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md index be89ccb092..e43ba9a529 100644 --- a/docs/en/Community-Articles/2022-03-09-abpio-platform-52-rc-has-been-published/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md b/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md index b16d6bd9f1..cf8c3c122a 100644 --- a/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md +++ b/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md @@ -2,6 +2,12 @@ In this article, we'll create a basic application to demonstrate how "Concurrency Check/Control" can be implemented in an ABP project. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Creating the Solution For this article, we will create a simple BookStore application and add CRUD functionality to the pages. Hence we deal with the concurrency situation. @@ -546,3 +552,9 @@ Then we can run the application, navigate to the **/Books** endpoint and see the * After the first user updated the record, the second user tries to update the same record without getting the last state of the record. And therefore `AbpDbConcurrencyException` is thrown because **ConcurrencyStamp** values are different from each other. * The second user should close and re-open the model to get the last state of the record and then they can make changes to the current record. + +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md b/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md index 9663ac1f83..e0f1f38aa9 100644 --- a/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md +++ b/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md @@ -4,6 +4,12 @@ In this article, I'll show you one of the new features that came with .NET 7: ** > I've created a community article to highlight some interesting features (briefly) that are now available with the release of .NET 7. You can check it from [here](https://community.abp.io/posts/whats-new-with-.net-7-tlq2g43w). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What is gRPC? What are the pros and cons? [gRPC](https://grpc.io/) is a high-performance RPC framework and uses HTTP/2 and Protobuf (protocol buffers). @@ -189,6 +195,12 @@ In this article, I've briefly introduced the JSON Transcoding feature that was s > See the [gRPC JSON transcoding in ASP.NET Core gRPC apps](https://learn.microsoft.com/en-us/aspnet/core/grpc/json-transcoding?view=aspnetcore-7.0) documentation for more information. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## References * https://devblogs.microsoft.com/dotnet/announcing-grpc-json-transcoding-for-dotnet/ diff --git a/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md b/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md index fb964fa730..671592d63e 100644 --- a/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md +++ b/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md @@ -6,6 +6,12 @@ In this article, I will highlight a few interesting features that are now availa There are many new features coming with this release. We are going to examine new features within 4 sub-sections: ASP.NET Core, C#11, EF 7 and MAUI. Let's start with ASP.NET Core. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## ASP.NET Core We will see the following features in this section: @@ -290,6 +296,12 @@ In this article, I've highlighted some features that were shipped with .NET 7. Thanks for reading the article and I hope you find it helpful. See you in the next one! +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## References * https://devblogs.microsoft.com/dotnet/announcing-dotnet-7/ diff --git a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md index 9023417b20..1a1aa55f97 100644 --- a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md +++ b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md @@ -8,6 +8,12 @@ ABP Framework provides services to compress and resize images and implements the > Refer to the documentation for more info: [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Source Code You can find the source code of the application at [https://github.com/abpframework/abp-samples/tree/master/ImageManipulation](https://github.com/abpframework/abp-samples/tree/master/ImageManipulation). Don't hesitate to check the source code, if you are stuck on any point. @@ -281,6 +287,12 @@ The results are impressive for the example above: * The original image was 12 KB and now the compressed & resized image has been reduced to 8 KB. * The original image was 225x225 and now resized as 200x200. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I have shown you how to compress and/or resize images with ABP Framework's Image Manipulation System by just defining some attributes to the top of the controller actions. diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md index 38cfab67e9..3d62f38886 100644 --- a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md +++ b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md @@ -2,6 +2,12 @@ In this article, I will highlight ABP Commercial's [GDPR Module](https://commercial.abp.io/modules/Volo.Gdpr) and show you how to provide personal data to the GDPR Module that is collected by your application and make it aligned with personal data download results. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## GDPR Module [GDPR Module](https://docs.abp.io/en/commercial/latest/modules/gdpr) allows users to download and delete the data collected by the application. It's used for Personal Data Management obligating by the GDPR regulation and provides the following features: @@ -143,6 +149,12 @@ Then, when we clicked the download button, a zip file will be downloaded. We can {"City":"Istanbul","Postcode":"..."} ``` +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I've explained the GDPR Module's data collection system and given you a brief overview of the module. GDPR Module allows you to request to download personal data, delete/anonymize your own personal data and delete your account permanently. It's pre-installed in the Application and Application(single layer) Pro Startup templates, but you can easily configure it to your existing application. diff --git a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md index 9142a36be9..9586b51cf5 100644 --- a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md +++ b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md @@ -2,6 +2,12 @@ In this article, I'll show you the new built-in metrics of .NET 8, which are basically numerical measurements reported over time in your application and can be used to monitor the health of your application and generate reports according to those numerical values. We will see what the metrics are, why to use them, and how to use them in detail. So, let's dive in. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What are Metrics? Metrics are numerical measurements reported over time. These measurements are crucial for monitoring the health of an application and generating alerts when necessary. In the context of a web service, various metrics can be tracked, such as: @@ -185,6 +191,12 @@ If you want to build a dashboard, you can see [this documentation from Microsoft Also, you can check [this video](https://www.youtube.com/watch?v=A2pKhNQoQUU) if you want to see it in action. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I've shown you the new built-in metrics of .NET8, described what metrics are, which pre-built metrics are provided by ASP.NET Core, how to use & view these pre-built metrics and finally, I have shown you how to create custom metrics and view them in a dashboard. If you want to learn more about the **Metrics System of .NET**, you can check out the references that I have shared below. diff --git a/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md b/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md index 3ed34680c1..c247f1e3e9 100644 --- a/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md +++ b/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md @@ -4,6 +4,12 @@ A new .NET version was released on November 14, 2023 and ABP 8.0 RC.1 shipped ba Despite all the related dependency upgrades and changes made on the ABP Framework and ABP Commercial sides, we still need to make some changes. Let's see the required actions that need to be taken in the following sections. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Installing the .NET 8.0 SDK To get started with ASP.NET Core in .NET 8.0, you need to install the .NET 8 SDK. You can install it at [https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). @@ -99,3 +105,9 @@ After that, you need to check the migration guide documents, listed below: That's it! These were all the related steps that need to be taken to upgrade your application to .NET 8 and ABP 8.0. Now, you can enjoy the .NET 8 & ABP 8.0 and benefit from the performance improvements and new features. Happy Coding 🤗 + +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- \ No newline at end of file diff --git a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md b/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md index d100838b1a..aeefc03c0c 100644 --- a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md +++ b/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md @@ -2,6 +2,12 @@ In this post, I describe the new **"keyed service"** support for the dependency injection container, which came with .NET 8.0. Then, I'll show you an example of usage within the ABP Framework. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What Are Keyed Services? ASP.NET Core ships with a built-in dependency injection container, which is a pretty basic DI container that supports minimal features a dependency injection container is supposed to have. For that reason, most of the .NET users use third-party containers like [Autofac](https://autofac.org/), or Ninject. @@ -203,6 +209,12 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie Please refer to the [Dependency Injection document](https://abp.io/docs/latest/framework/fundamentals/dependency-injection#exposekeyedservice-attribute) for further info. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Summary In this post, I described the new **"keyed service"** support added to the built-in dependency injection container that was released in .NET 8, and wanted to announce it's being supported from v8.0.2 for ABP Framework. It's a really good addition to the built-in dependency injection container and can be pretty useful for certain use-cases. diff --git a/docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md b/docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md index 569e566af7..fbe2c2cbad 100644 --- a/docs/en/Community-Articles/2024-02-19-abpio-platform-81-rc-has-been-published/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md b/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md index e4f763f279..5eb4a5bd98 100644 --- a/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md +++ b/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md @@ -120,6 +120,12 @@ After these configurations, you should create a migration and apply it to your d However, this solution comes with some problems, for example, by using non-deterministic collations, it's not yet possible to use pattern-matching operators such as `LIKE` on columns. This is a huge problem because it makes it hard to use LINQ. For example, you can't use the `.EndsWith` or `.StartsWith` methods, because they are [translated to `LIKE` command on the SQL level](https://www.npgsql.org/efcore/mapping/translations.html). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In PostgreSQL, you can perform case-insensitive searches by using the `citext` data type or by utilizing collation settings. Nevertheless, if you have an ABP-based PostgreSQL application or a plain .NET application with PostgreSQL as the database option, to make a decision to pick one of these options, you can follow the following points: diff --git a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md index e6417b70ce..deb162bcba 100644 --- a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md +++ b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md @@ -4,6 +4,12 @@ In this article, first I will briefly explain what sentiment analysis is and the We will use ML.NET Framework, which is an open-source machine learning framework created by the dotnet team and also we will create a layered ABP Solution by using the application template and finally we will use CMS Kit's Comment Feature and extend its behavior by adding spam detection while creating or updating a comment, at that point we will make sentiment analysis. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Sentiment Analysis [Sentiment Analysis (or opinion mining)](https://en.wikipedia.org/wiki/Sentiment_analysis) refers to determining the emotion from the given input. The primary goal of sentiment analysis is to identify, extract, and categorize (positive, negative, or neutral) the sentiments expressed in textual data. @@ -333,6 +339,12 @@ Then, finally, we can run the application to see the final results: Once the model is trained and evaluated, we can save the trained model and use it directly for further use. In this way, you don’t have to retrain the model every time when you want to make predictions. It’s essential to save the trained model for future use and a must for the production-ready code. I created a separate article dedicated to that topic, and if you are interested, you can read it from [here](https://engincanv.github.io/machine-learning/sentiment-analysis/best-practises/2024/05/16/reusing-and-optimizing-machine-learning-models-in-dotnet.html). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I briefly explain what sentiment analysis is, created a sample ABP-based application, integrated the CMS Kit Module and finally, applied sentiment analysis to make spam checks whenever a new comment has been submitted or updated. You can get the source code of the demo from [https://github.com/EngincanV/SentimentAnalysisDemo](https://github.com/EngincanV/SentimentAnalysisDemo) diff --git a/docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md b/docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md index 6574012f24..c3eadb6419 100644 --- a/docs/en/Community-Articles/2024-05-24-abpio-platform-82-rc-has-been-published/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md b/docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md index 05c18fde03..4ddd247a63 100644 --- a/docs/en/Community-Articles/2024-08-01-abpio-platform-83-rc-has-been-published/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md b/docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md index 55f7b80294..de27f8cdbd 100644 --- a/docs/en/Community-Articles/2024-09-12-abpio-platform-83-final-has-been-released/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md b/docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md index 9e22660332..a7dfa896bd 100644 --- a/docs/en/Community-Articles/2024-10-23-abpio-platform-90-rc-has-been-published/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md b/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md index 3cd4bdc706..a9b869a822 100644 --- a/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md +++ b/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md @@ -6,6 +6,12 @@ It offers a flexible caching solution that combines the best aspects of local an In this article, we’ll explore **HybridCache** in .NET 9 and how it integrates with ABP Framework using `AbpHybridCache`. This new feature offers a robust solution for applications that need to scale while maintaining efficient caching strategies. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What is HybridCache? **HybridCache** is designed to merge different caching layers, commonly including an in-memory cache (for high-speed access) and a distributed cache (for scalability across multiple instances). This hybrid approach allows for: @@ -109,6 +115,12 @@ As you can see from the figure, it only set the cache item to the **LocalCache** **Note:** If you configure distributed caching options, `HybridCache` service uses the distributed cache and sets the **BackendCache**. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion The **HybridCache** library in .NET 9 provides a powerful tool for applications needing both high-speed caching and consistency in distributed environments. diff --git a/docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md b/docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md index 30ff978de6..931379eac0 100644 --- a/docs/en/Community-Articles/2024-11-21-abpio-platform-90-has-been-released-based-on-net-90/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md b/docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md index 2c3a98ac49..615457a7ff 100644 --- a/docs/en/Community-Articles/2025-01-21-abp-platform-91-rc-has-been-released/post.md +++ b/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 diff --git a/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md index f5602650ae..e8b8c9a1b3 100644 --- a/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md +++ b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md @@ -6,6 +6,12 @@ OpenIddict provides an event-driven model ([event models](https://documentation. In this article, we will explore OpenIddict event models, their key use cases, and how to implement them effectively. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Understanding OpenIddict Event Model OpenIddict events are primarily used within the OpenIddict server component. These events provide hooks into the OpenID Connect flow, allowing developers to modify behavior at different stages of authentication & authorization processes. @@ -101,6 +107,12 @@ That's it! After these steps, your `SignOutEventHandler.HandleAsync()` method sh Each event provides access to the relevant context, allowing you to access and modify the authentication flow's behavior. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion ABP Framework integrates OpenIddict as its authentication and authorization module. OpenIddict provides an event-driven model that allows developers to customize authentication and authorization processes within their ABP applications. It's pre-installed & pre-configured in the ABP's startup templates. diff --git a/docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md b/docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md index bfde4d5318..09d7f917bb 100644 --- a/docs/en/Community-Articles/2025-03-11-Developing-A-Multi-Timezone-Application-Using-The-ABP-Framework/post.md +++ b/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. diff --git a/docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md b/docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md index bc0b390b03..09998169e6 100644 --- a/docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md +++ b/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
- +
``` diff --git a/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md b/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md index b74aecdbe4..4213ad0e48 100644 --- a/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md +++ b/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md @@ -4,6 +4,12 @@ In this article, we'll explore the [`CurrentUser` service](https://abp.io/docs/latest/framework/infrastructure/current-user), its use cases, and how to change it when necessary. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Understanding the ICurrentUser Service The `ICurrentUser` interface is the primary service in ABP Framework for obtaining information about the logged-in user. It provides some key properties, such as `Id`, `UserName`, `TenantId`, `Roles` (roleNames), and more... @@ -108,6 +114,12 @@ Here's what the code does step by step: This pattern is particularly useful in background jobs, event handlers, or any scenario where you need to perform operations under a specific user's context, regardless of the actual authenticated user. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion The `CurrentUser` service in ABP Framework provides a simple way to access information about the authenticated user. While it works automatically in most scenarios, there are cases where you need to explicitly change the current user identity, particularly in background processing scenarios. diff --git a/docs/en/Community-Articles/2025-03-27-Announcing-the-ABP-Contributor-Program/post.md b/docs/en/Community-Articles/2025-03-27-Announcing-the-ABP-Contributor-Program/post.md new file mode 100644 index 0000000000..7767a6673b --- /dev/null +++ b/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 \ No newline at end of file diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/POST.md b/docs/en/Community-Articles/2025-04-20-IDX10204/POST.md new file mode 100644 index 0000000000..5de0650d11 --- /dev/null +++ b/docs/en/Community-Articles/2025-04-20-IDX10204/POST.md @@ -0,0 +1,170 @@ +# Common Errors in JWT Bearer Authentication + +When implementing JWT Bearer authentication in an ABP(tiered) application, you might occasionally encounter errors starting with `IDX`. These errors are related to JWT Bearer Token validation and this article will help you understand and resolve them. + +## Enable JWT Bearer authentication + +Your API project usually contains the following code, which enables JWT Bearer authentication and makes it as the default authentication scheme. + +We simply configure the JWT's `Authority` and `Audience` properties, and it will work fine. + +```csharp +context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://localhost:44301/"; //configuration["AuthServer:Authority"]; + options.Audience = "MyProjectName"; + }); +``` + +> `AddJwtBearer` and `AddAbpJwtBearer` will do the same thing, but `AddAbpJwtBearer` is recommended. + +## JWT authentication process + +Let's take a look at how the above code works. + +A JWT Token usually consists of three parts: `Header`, `Payload`, and `Signature`. + +- `Header`: Contains the type and signing algorithm of the token +- `Payload`: Contains the claims of the token, including `sub`, `aud`, `exp`, `iat`, `iss`, `jti`, `preferred_username`, `given_name`, `role`, `email`, etc. +- `Signature`: The cryptographic signature of the token used to verify its authenticity + +Here is an example of a JWT Token issued by `AuthServer(OpenIddict)`: + +The `Header` part: + +![JWT Header](header.png) + +The `Payload` part: + +![JWT Payload](payload.png) + +### TokenValidationParameters + +In the `JwtBearerOptions`, there is a `TokenValidationParameters` property, which is used to validate the JWT Token. + +The default implementation for JWT Token validation is `JsonWebTokenHandler`, which comes from the [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/) package. + +We didn't set the `TokenValidationParameters` property in the code above, so the default values below will be used: + +```csharp +//... +TokenValidationParameters.ValidateAudience = true +TokenValidationParameters.ValidAudience = "MyProjectName" +TokenValidationParameters.ValidAudiences = null + +TokenValidationParameters.ValidateIssuer = true +TokenValidationParameters.ValidIssuer = null +TokenValidationParameters.ValidIssuers = null +//... +``` + +### JWT Bearer Token Validation Process + +During JWT Bearer authentication, API website will get the token from the HTTP request and validate it. + +The `JsonWebTokenHandler` will get the `OpenID Connect` metadata from the `AuthServer`, it will be used in the validation process, the current metadata request address is: https://localhost:44301/.well-known/openid-configuration , it is a fixed address calculated from the `Authority` property. + +First, the token's Signature is verified using the public key obtained from `OpenID Connect` metadata(https://localhost:44301/.well-known/jwks). + +Then, the payload is validated. The payload is a JSON object containing essential information such as the `token type`, `expiration time`, `issuer`, and `audience` etc. + +Most of the validation problems we may encounter are payload validation failures, for example: + +#### Lifetime + +If the token in your request has expired, the validation will fail. You will see the exception information like `IDX10230` in the log. + +#### Audience + +The `ValidAudience` of `TokenValidationParameters` is `MyProjectName`, the `aud` in the payload of the token is also `MyProjectName`, if the token does not contain `aud` or the `aud` does not match, the validation will fail. You may see the exception information like `IDX10206`, `IDX10277` or `IDX10208`. + +> If the `ValidateAudience` of `TokenValidationParameters` is `false`, then the `aud` will not be validated. + +#### Issuer + +The default value of `TokenValidationParameters.ValidateIssuer` is `true`, it requires the token's payload to contain the `issuer` field, and it must match one of `TokenValidationParameters.ValidIssuer` or `TokenValidationParameters.ValidIssuers`. + +> The default value of `ValidIssuer` or `ValidIssuers` is `null`, it will use the `issuer` from the `OpenID Connect` metadata as the default value. + +1. If the token's payload does not contain the `issuer` field, you may see the error `IDX10211`. +2. If the API website cannot get the `OpenID Connect` metadata from AuthServer website, the validation will fail. You may see the error `IDX10204`, the full exception message is: `IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null or empty.` +3. If the `issuer` does not match, the validation will fail. You may see the error `IDX10205` in the log. + +> If the `ValidateIssuer` of `TokenValidationParameters` is `false`, then the `issuer` will not be validated. + +> Please note that `OpenIddict` will use the current HTTP request information as the value of `issuer`. If the AuthServer website is deployed behind a reverse proxy or similar deployment configurations, the `issuer` in the token may not be the value you expect. In this case, please specify it manually. + +```csharp +PreConfigure(serverBuilder => +{ + serverBuilder.SetIssuer("https://localhost:44301/"); +}); +``` + +## Troubleshooting + +To troubleshoot any `IDX` errors during JWT authentication, you can enable detailed logging by configuring the `identitymodel` logs as follows: + +```csharp +using System.Diagnostics.Tracing; +using Microsoft.IdentityModel.Logging; + +public class Program +{ + public async static Task Main(string[] args) + { + IdentityModelEventSource.ShowPII = true; + IdentityModelEventSource.Logger.LogLevel = EventLevel.Verbose; + var wilsonTextLogger = newTextWriterEventListener("Logs/identitymodel.txt"); + wilsonTextLogger.EnableEvents(IdentityModelEventSource.Logger, EventLevel.Verbose); + + //... + } +} +``` + +Additionally, you can enable `OpenIddict`'s `Verbose` logs for more detailed debugging information: + +```csharp +var loggerConfiguration = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + .MinimumLevel.Override("OpenIddict", LogEventLevel.Verbose) + .Enrich.FromLogContext() + .WriteTo.Async(c => c.File("Logs/logs.txt")) +``` + +## Summary + +For JWT authentication, you need to pay attention to the following key points: + +1. Ensure your API website can communicate with the AuthServer properly +2. Verify that the `aud` claim in your token matches the expected audience +3. Confirm that the `issuer` claim in your token is valid and matches the configuration + +You can customize the `JwtBearerOptions`'s `TokenValidationParameters` to modify the validation rules to meet your actual needs. + +For example, if your `issuer` needs to support multiple subdomains, you can use the [Owl.TokenWildcardIssuerValidator](https://github.com/maliming/Owl.TokenWildcardIssuerValidator) library to customize the validation. + +```csharp +services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://abp.io"; + options.Audience = "abp_io"; + + options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator; + options.TokenValidationParameters.ValidIssuers = new[] + { + "https://{0}.abp.io" + }; + }); +``` + +## References + +- [Configure JWT bearer authentication in ASP.NET Core]([https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwt-auth?view=aspnetcore-8.0](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/configure-jwt-bearer-authentication)) +- [OpenIddict](https://github.com/openiddict/openiddict-core) +- [IdentityModel](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) +- [Owl.TokenWildcardIssuerValidator](https://github.com/maliming/Owl.TokenWildcardIssuerValidator) diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/header.png b/docs/en/Community-Articles/2025-04-20-IDX10204/header.png new file mode 100644 index 0000000000..a71c9a66ff Binary files /dev/null and b/docs/en/Community-Articles/2025-04-20-IDX10204/header.png differ diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png b/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png new file mode 100644 index 0000000000..a83d6e1051 Binary files /dev/null and b/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png differ diff --git a/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/POST.md b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/POST.md new file mode 100644 index 0000000000..f79fa21ce8 --- /dev/null +++ b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/POST.md @@ -0,0 +1,262 @@ +# Using Microsoft AI Extensions Library and OpenAI to Summarize User Comments + +Either you are building an e-commerce application or a simple blog, **user comments** (about your products or blog posts) **can grow rapidly**, making it harder for users to get the gist of discussions at a glance. AI is a pretty good tool to solve the problem. By using AI, you can **summarize all the user comments** and show a single paragraph to your users, so they can easily understand the overall thought of users about the product or the blog post. + +In this tutorial, we’ll walk through a real-life implementation of using AI to summarize multiple user comments in an application. I will implement the solution based on ABP's **[CMS Kit](https://abp.io/docs/latest/modules/cms-kit)** library, as it already features a **[commenting system](https://abp.io/docs/latest/modules/cms-kit/comments)** and a [demo application](https://cms-kit-demo.abpdemo.com/) that displays user comments on **[gallery images](https://cms-kit-demo.abpdemo.com/image-gallery)** (it has not a comment summary feature yet, we will implement it in this tutorial). + +## A Screenshot + +Here, an example screenshot from the application with the comment summary feature: + +![comment-example](comment-example.png) + +## Cloning the Repository + +If you want to follow the development, you can clone the [CMS Kit Demo repository](https://github.com/abpframework/cms-kit-demo) to your computer and make it running by following the instructions on the [README file](https://github.com/abpframework/cms-kit-demo?tab=readme-ov-file#cms-kit-demo). + +I suggest to you to play a little with [the application](https://cms-kit-demo.abpdemo.com/) (create a new user for yourself, add some comments to the images in the gallery), so you understand how it works. + +## Preparing the Solution for AI + +I will use [Microsoft AI Extensions Library](https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions) to use the AI features. It is an abstraction library that can work with multiple AI models and tools. I will use an OpenAI model in the demo. + +The first step is to add the [Microsoft.Extensions.AI.OpenAI](http://nuget.org/packages/Microsoft.Extensions.AI.OpenAI) NuGet package to the project: + +````bash +dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease +```` + +>The Microsoft AI Extensions Library was in preview at the time when I wrote this article. If it has a stable release now, you can remove the `--prerelease` parameter for the preceding command. + +We will store the OpenAI key and model name in user secrets. So, locate the root path of the CMS Kit project (`src\CmsKitDemo` folder) and execute the following commands in order in a command-line terminal: + +````bash +dotnet user-secrets init +dotnet user-secrets set OpenAIKey +dotnet user-secrets set ModelName +```` + +For this example, you need to have an [OpenAI API Key](https://platform.openai.com/). That's all. Now, we are ready to use the AI. + +## Implementing the AI Summarization + +Let's start from the most important point of this article: Comment summarization. I will create a class named `AiCommentSummarizer` to implement the summarization work. Here, the full content of that class: + +````csharp +using System.Text; +using Microsoft.Extensions.AI; +using OpenAI; +using Volo.Abp.DependencyInjection; + +namespace CmsKitDemo.Utils; + +public class AiCommentSummarizer : ITransientDependency +{ + private readonly IConfiguration _configuration; + + public AiCommentSummarizer(IConfiguration configuration) + { + _configuration = configuration; + } + + public async Task SummarizeAsync(string[] commentTexts) + { + // Get the model and key from the configuration + var aiModel = _configuration["ModelName"]; + var apiKey = _configuration["OpenAIKey"]; + + if (aiModel.IsNullOrEmpty() || apiKey.IsNullOrEmpty()) + { + return ""; + } + + // Create the IChatClient + var client = new OpenAIClient(apiKey) + .GetChatClient(aiModel) + .AsIChatClient(); + + // Create a prompt (input for AI) + var promptBuilder = new StringBuilder(); + + promptBuilder.AppendLine( + @"There are comments from different users of our website about an image. +We want to summarize the comments into a single comment. +Return a single comment with a maximum of 512 characters. Comments are separated by a newline character and given below." + ); + promptBuilder.AppendLine(); + + foreach (var commentText in commentTexts) + { + promptBuilder.AppendLine("User comment:"); + promptBuilder.AppendLine(commentText); + promptBuilder.AppendLine(); + } + + // Submit the prompt and get the response + var response = await client.GetResponseAsync( + promptBuilder.ToString(), + new ChatOptions { MaxOutputTokens = 1024 } + ); + + return response.Text; + } +} +```` + +That class is pretty simple and already decorated with comments: + +* First, we are getting the API Key and an OpenAI model name from user secrets. I used `gpt-4.1` as the model name, but you can use another available model. +* Then we are obtaining an `IChatClient` reference for OpenAI. `IChatClient` interface is an abstraction that is provided by the [Microsoft AI Extensions Library](https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions) library, so we can implement rest of the code independently from OpenAI. +* Then we continue by building a proper prompt (input) for the AI operation. +* And finally we are using the AI to generate a response (the summary). + +At this point, all the AI-related work has already been done. The rest of this article explains how to integrate that summarization feature with the [CMS Kit Demo application](https://cms-kit-demo.abpdemo.com/). + +## Adding a CommentsSummary Property to the GalleryImage Entity + +The `GalleryImage` entity is used to represent an image on [the image gallery](https://cms-kit-demo.abpdemo.com/image-gallery). I add a `CommentsSummary` property to that entity: + +````csharp +public class GalleryImage : CreationAuditedAggregateRoot +{ + public string Description { get; set; } + + public Guid CoverImageMediaId { get; set; } + + public string CommentsSummary { get; set; } // The new property is here + + //... +} +```` + +Since the CMS Kit Demo application uses Entity Framework Core, I need to add a new database schema migration and update the database: + +````bash +dotnet ef migrations add Added_Summary_To_GalleryImage +dotnet ef database update +```` + +## Updating the Summary + +Great, we have a `GalleryImage.CommentsSummary` property now. But, how will it be updated when a users adds or removes a comment for an image? To implement that; + +* We will listen all the change events for user comments (when a user adds, removes or updates a comment). +* Whenever a comment is changed, we will find the related gallery image, retrieve all the user comments for this image, use the `AiCommentSummarizer` class to summarize all the comments. +* Finally, we wil set the `GalleryImage.CommentsSummary` property with the generated summary text. + +Here, the implementation: + +````csharp +using CmsKitDemo.Entities; +using CmsKitDemo.Utils; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.EventBus; +using Volo.CmsKit.Comments; + +namespace CmsKitDemo.EventHandlers; + +public class GalleryImageCommentListener : + ILocalEventHandler>, + ITransientDependency +{ + private readonly IRepository _galleryImageRepository; + private readonly IRepository _commentRepository; + private readonly AiCommentSummarizer _aiCommentSummarizer; + + public GalleryImageCommentListener( + IRepository galleryImageRepository, + IRepository commentRepository, + AiCommentSummarizer aiCommentSummarizer) + { + _galleryImageRepository = galleryImageRepository; + _commentRepository = commentRepository; + _aiCommentSummarizer = aiCommentSummarizer; + } + + public async Task HandleEventAsync(EntityChangedEventData eventData) + { + var comment = eventData.Entity; + + //Here, we only interest in comments related to image gallery items + if (comment.EntityType != CmsKitDemoConsts.ImageGalleryEntityType) + { + return; + } + + if (!Guid.TryParse(comment.EntityId, out var galleryImageId)) + { + return; + } + + // Get the related image from database + var galleryImage = await _galleryImageRepository.FindAsync(galleryImageId); + if (galleryImage == null) + { + return; + } + + // Get all the comments related to the image + var queryable = await _commentRepository.GetQueryableAsync(); + var allCommentTexts = await queryable + .Where(c => c.EntityType == CmsKitDemoConsts.ImageGalleryEntityType && + c.EntityId == comment.EntityId) + .Select(c => c.Text) + .ToArrayAsync(); + + // Update the summary of comments related to the image + if (allCommentTexts.Length <= 0) + { + galleryImage.CommentsSummary = ""; + } + else + { + galleryImage.CommentsSummary = + await _aiCommentSummarizer.SummarizeAsync(allCommentTexts); + } + + // Update the image in database + await _galleryImageRepository.UpdateAsync(galleryImage); + } +} +```` + +Let's explain that class: + +* `GalleryImageCommentListener` implements the `ILocalEventHandler>` interface. In this way, it can handle an event whenever a `Comment` [entity](https://abp.io/docs/latest/framework/architecture/domain-driven-design/entities) is changed (created, updated or deleted). We are using ABP's [local event bus](https://abp.io/docs/latest/framework/infrastructure/event-bus/local) and its [pre-defined events](https://abp.io/docs/latest/framework/infrastructure/event-bus/local#pre-built-events). +* `HandleEventAsync` is called by the ABP Framework whenever a new `Comment` is created, or an existing `Comment` is deleted or updated. +* ABP's `Comment` entity is reusable and it can be associated with any kind of objects (blog posts, images, etc). So, first we are checking if this comment is related to an image gallery item. +* Then we are getting the related `GalleryImage` entity from the database. +* And getting all comments (including the new one) from the database for this image. +* Finally, using the `AiCommentSummarizer` class to generate the summary and set the `CommentsSummary` property. + +## Show the Summary Card on the UI + +Everything is ready on the backend. Now, we can show the summary text on the user interface. To do, that, I added `CommentsSummary` property also to the `GalleryImageDto` class and used it on the `/Pages/Gallery/Detail.cshtml` view: + +````csharp +@if (!Model.Image.CommentsSummary.IsNullOrEmpty()) +{ +
+
+
Summary of the User Comments
+

@Model.Image.CommentsSummary

+
+
+} +```` + +That section renders the following card on the user interface: + +![summary-card](summary-card.png) + +## Conclusion + +In this article, I demonstrated how to use [Microsoft AI Extensions Library](https://learn.microsoft.com/en-us/dotnet/ai/ai-extensions) to work with OpenAI for summarization of multiple user comments. I reused the [ABP's CMS Kit Demo application](https://github.com/abpframework/cms-kit-demo) to show it in a more real world example. + +## Source Code + +* [Source code of the CMS Kit Demo application](https://github.com/abpframework/cms-kit-demo) +* [All the changes made for this article (as a pull request)](https://github.com/abpframework/cms-kit-demo/pull/18) \ No newline at end of file diff --git a/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/comment-example.png b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/comment-example.png new file mode 100644 index 0000000000..4ac33b60f9 Binary files /dev/null and b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/comment-example.png differ diff --git a/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png new file mode 100644 index 0000000000..35151efafb Binary files /dev/null and b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png differ diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md index a9caa0b6bb..02521e72cb 100644 --- a/docs/en/cli/index.md +++ b/docs/en/cli/index.md @@ -889,6 +889,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. diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index c160bacfde..fc3844317d 100644 --- a/docs/en/docs-nav.json +++ b/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" }, { diff --git a/docs/en/framework/api-development/static-csharp-clients.md b/docs/en/framework/api-development/static-csharp-clients.md index c8eaea4231..b3cf80fcfc 100644 --- a/docs/en/framework/api-development/static-csharp-clients.md +++ b/docs/en/framework/api-development/static-csharp-clients.md @@ -47,7 +47,7 @@ Implement this class in your service application. You can use [auto API controll First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: ```` -Install-Package Volo.Abp.Http.Client +dotnet add package Volo.Abp.Http.Client ```` Then add `AbpHttpClientModule` dependency to your module: diff --git a/docs/en/framework/api-development/swagger.md b/docs/en/framework/api-development/swagger.md index 1699e4d0d2..fb27bcf27b 100644 --- a/docs/en/framework/api-development/swagger.md +++ b/docs/en/framework/api-development/swagger.md @@ -29,7 +29,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Swashbuckle](https://www.nuget.org/packages/Volo.Abp.Swashbuckle) NuGet package to your `Web` or `HttpApi.Host` project: - `Install-Package Volo.Abp.Swashbuckle` + `dotnet add package Volo.Abp.Swashbuckle` 2. Add the `AbpSwashbuckleModule` to the dependency list of your module: diff --git a/docs/en/framework/fundamentals/fluent-validation.md b/docs/en/framework/fundamentals/fluent-validation.md index d3af69bae9..207cddcbd3 100644 --- a/docs/en/framework/fundamentals/fluent-validation.md +++ b/docs/en/framework/fundamentals/fluent-validation.md @@ -21,7 +21,7 @@ If you want to manually install; 1. Add the [Volo.Abp.FluentValidation](https://www.nuget.org/packages/Volo.Abp.FluentValidation) NuGet package to your project: ```` - Install-Package Volo.Abp.FluentValidation + dotnet add package Volo.Abp.FluentValidation ```` 2. Add the `AbpFluentValidationModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/background-jobs/hangfire.md b/docs/en/framework/infrastructure/background-jobs/hangfire.md index 293f03494c..f5991ee19b 100644 --- a/docs/en/framework/infrastructure/background-jobs/hangfire.md +++ b/docs/en/framework/infrastructure/background-jobs/hangfire.md @@ -25,7 +25,7 @@ If you want to manually install; 1. Add the [Volo.Abp.BackgroundJobs.HangFire](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.HangFire) NuGet package to your project: ```` - Install-Package Volo.Abp.BackgroundJobs.HangFire + dotnet add package Volo.Abp.BackgroundJobs.HangFire ```` 2. Add the `AbpBackgroundJobsHangfireModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/background-jobs/quartz.md b/docs/en/framework/infrastructure/background-jobs/quartz.md index 93cf5d53bf..1b18fd36ae 100644 --- a/docs/en/framework/infrastructure/background-jobs/quartz.md +++ b/docs/en/framework/infrastructure/background-jobs/quartz.md @@ -25,7 +25,7 @@ If you want to manually install; 1. Add the [Volo.Abp.BackgroundJobs.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.Quartz) NuGet package to your project: ```` - Install-Package Volo.Abp.BackgroundJobs.Quartz + dotnet add package Volo.Abp.BackgroundJobs.Quartz ```` 2. Add the `AbpBackgroundJobsQuartzModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/background-workers/hangfire.md b/docs/en/framework/infrastructure/background-workers/hangfire.md index 4612e15b06..7fec8be273 100644 --- a/docs/en/framework/infrastructure/background-workers/hangfire.md +++ b/docs/en/framework/infrastructure/background-workers/hangfire.md @@ -23,7 +23,7 @@ If you want to manually install; 1. Add the [Volo.Abp.BackgroundWorkers.Hangfire](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Hangfire) NuGet package to your project: ```` - Install-Package Volo.Abp.BackgroundWorkers.Hangfire + dotnet add package Volo.Abp.BackgroundWorkers.Hangfire ```` 2. Add the `AbpBackgroundWorkersHangfireModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/background-workers/quartz.md b/docs/en/framework/infrastructure/background-workers/quartz.md index 663fe22a7f..909371adb0 100644 --- a/docs/en/framework/infrastructure/background-workers/quartz.md +++ b/docs/en/framework/infrastructure/background-workers/quartz.md @@ -21,7 +21,7 @@ If you want to manually install; 1. Add the [Volo.Abp.BackgroundWorkers.Quartz](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Quartz) NuGet package to your project: ```` - Install-Package Volo.Abp.BackgroundWorkers.Quartz + dotnet add package Volo.Abp.BackgroundWorkers.Quartz ```` 2. Add the `AbpBackgroundWorkersQuartzModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/image-manipulation.md b/docs/en/framework/infrastructure/image-manipulation.md index 7ab8dc562e..a1ee6e1584 100644 --- a/docs/en/framework/infrastructure/image-manipulation.md +++ b/docs/en/framework/infrastructure/image-manipulation.md @@ -22,7 +22,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Imaging.Abstractions](https://www.nuget.org/packages/Volo.Abp.Imaging.Abstractions) NuGet package to your project: ``` -Install-Package Volo.Abp.Imaging.Abstractions +dotnet add package Volo.Abp.Imaging.Abstractions ``` 2. Add the `AbpImagingAbstractionsModule` to the dependency list of your module: @@ -240,7 +240,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Imaging.MagickNet](https://www.nuget.org/packages/Volo.Abp.Imaging.MagickNet) NuGet package to your project: ``` -Install-Package Volo.Abp.Imaging.MagickNet +dotnet add package Volo.Abp.Imaging.MagickNet ``` 2. Add `AbpImagingMagickNetModule` to your [module](../architecture/modularity/basics.md)'s dependency list: @@ -284,7 +284,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Imaging.ImageSharp](https://www.nuget.org/packages/Volo.Abp.Imaging.ImageSharp) NuGet package to your project: ``` -Install-Package Volo.Abp.Imaging.ImageSharp +dotnet add package Volo.Abp.Imaging.ImageSharp ``` 2. Add `AbpImagingImageSharpModule` to your [module](../architecture/modularity/basics.md)'s dependency list: @@ -350,7 +350,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Imaging.AspNetCore](https://www.nuget.org/packages/Volo.Abp.Imaging.AspNetCore) NuGet package to your project: ``` -Install-Package Volo.Abp.Imaging.AspNetCore +dotnet add package Volo.Abp.Imaging.AspNetCore ``` 2. Add `AbpImagingAspNetCoreModule` to your [module](../architecture/modularity/basics.md)'s dependency list: diff --git a/docs/en/framework/infrastructure/sms-sending.md b/docs/en/framework/infrastructure/sms-sending.md index e6bc9135e5..ef2a86445f 100644 --- a/docs/en/framework/infrastructure/sms-sending.md +++ b/docs/en/framework/infrastructure/sms-sending.md @@ -26,7 +26,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Sms](https://www.nuget.org/packages/Volo.Abp.Sms) NuGet package to your project: ``` -Install-Package Volo.Abp.Sms +dotnet add package Volo.Abp.Sms ``` 2. Add the `AbpSmsModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/string-encryption.md b/docs/en/framework/infrastructure/string-encryption.md index 5f47e246ef..bb398425f6 100644 --- a/docs/en/framework/infrastructure/string-encryption.md +++ b/docs/en/framework/infrastructure/string-encryption.md @@ -22,7 +22,7 @@ If you want to manually install; 1. Add the [Volo.Abp.Security](https://www.nuget.org/packages/Volo.Abp.Security) NuGet package to your project: - `Install-Package Volo.Abp.Security` + `dotnet add package Volo.Abp.Security` 2. Add the `AbpSecurityModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/text-templating/razor.md b/docs/en/framework/infrastructure/text-templating/razor.md index 1d17d4337e..77db5467aa 100644 --- a/docs/en/framework/infrastructure/text-templating/razor.md +++ b/docs/en/framework/infrastructure/text-templating/razor.md @@ -25,7 +25,7 @@ If you want to manually install; 1. Add the [Volo.Abp.TextTemplating.Razor](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Razor) NuGet package to your project: ```` -Install-Package Volo.Abp.TextTemplating.Razor +dotnet add package Volo.Abp.TextTemplating.Razor ```` 2. Add the `AbpTextTemplatingRazorModule` to the dependency list of your module: diff --git a/docs/en/framework/infrastructure/text-templating/scriban.md b/docs/en/framework/infrastructure/text-templating/scriban.md index bd54914411..99d160ce85 100644 --- a/docs/en/framework/infrastructure/text-templating/scriban.md +++ b/docs/en/framework/infrastructure/text-templating/scriban.md @@ -19,7 +19,7 @@ If you want to manually install; 1. Add the [Volo.Abp.TextTemplating.Scriban](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Scriban) NuGet package to your project: ```` -Install-Package Volo.Abp.TextTemplating.Scriban +dotnet add package Volo.Abp.TextTemplating.Scriban ```` 2. Add the `AbpTextTemplatingScribanModule` to the dependency list of your module: diff --git a/docs/en/framework/real-time/signalr.md b/docs/en/framework/real-time/signalr.md index d6cbbfbada..2d2710c77d 100644 --- a/docs/en/framework/real-time/signalr.md +++ b/docs/en/framework/real-time/signalr.md @@ -27,7 +27,7 @@ If you want to manually install; 1. Add the [Volo.Abp.AspNetCore.SignalR](https://www.nuget.org/packages/Volo.Abp.AspNetCore.SignalR) NuGet package to your project: ``` - Install-Package Volo.Abp.AspNetCore.SignalR + dotnet add package Volo.Abp.AspNetCore.SignalR ``` Or use the Visual Studio NuGet package management UI to install it. diff --git a/docs/en/get-started/empty-aspnet-core-application.md b/docs/en/get-started/empty-aspnet-core-application.md index b0457db71b..95861719ce 100644 --- a/docs/en/get-started/empty-aspnet-core-application.md +++ b/docs/en/get-started/empty-aspnet-core-application.md @@ -104,7 +104,7 @@ Replacing ASP.NET Core's DI system by Autofac and integrating to ABP is pretty e 1. Install [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) package ```` -Install-Package Volo.Abp.Autofac +dotnet add package Volo.Abp.Autofac ```` 2. Add the ``AbpAutofacModule`` Dependency diff --git a/docs/en/images/samples-cms-kit.png b/docs/en/images/samples-cms-kit.png new file mode 100644 index 0000000000..0c4e3e5877 Binary files /dev/null and b/docs/en/images/samples-cms-kit.png differ diff --git a/docs/en/images/samples-easycrm.png b/docs/en/images/samples-easycrm.png new file mode 100644 index 0000000000..72924ae3e7 Binary files /dev/null and b/docs/en/images/samples-easycrm.png differ diff --git a/docs/en/images/samples-eshoponabp.png b/docs/en/images/samples-eshoponabp.png new file mode 100644 index 0000000000..f8e22fff7d Binary files /dev/null and b/docs/en/images/samples-eshoponabp.png differ diff --git a/docs/en/images/samples-eventhub.png b/docs/en/images/samples-eventhub.png new file mode 100644 index 0000000000..548412d62c Binary files /dev/null and b/docs/en/images/samples-eventhub.png differ diff --git a/docs/en/modules/docs.md b/docs/en/modules/docs.md index 69de3362a6..b564980ba3 100644 --- a/docs/en/modules/docs.md +++ b/docs/en/modules/docs.md @@ -73,25 +73,25 @@ Or you can also manually install nuget package to each project: * Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project. ```bash - Install-Package Volo.Docs.Domain + dotnet add package Volo.Docs.Domain ``` * Install [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) nuget package to `Acme.MyProject.EntityFrameworkCore` project. ```bash - Install-Package Volo.Docs.EntityFrameworkCore + dotnet add package Volo.Docs.EntityFrameworkCore ``` * Install [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) nuget package to `Acme.MyProject.Application` project. ```bash - Install-Package Volo.Docs.Application + dotnet add package Volo.Docs.Application ``` * Install [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Web` project. ```bash - Install-Package Volo.Docs.Web + dotnet add package Volo.Docs.Web ``` ##### 3.2.1- Adding Module Dependencies diff --git a/docs/en/modules/virtual-file-explorer.md b/docs/en/modules/virtual-file-explorer.md index 21bccfd999..3157198c30 100644 --- a/docs/en/modules/virtual-file-explorer.md +++ b/docs/en/modules/virtual-file-explorer.md @@ -24,7 +24,7 @@ Or you can also manually install nuget package to `Acme.MyProject.Web` project: * Install [Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget package to `Acme.MyProject.Web` project. - `Install-Package Volo.Abp.VirtualFileExplorer.Web` + `dotnet add package Volo.Abp.VirtualFileExplorer.Web` ##### 2.1- Adding Module Dependencies diff --git a/docs/en/samples/index.md b/docs/en/samples/index.md index 6aea3d3fa5..39512d0134 100644 --- a/docs/en/samples/index.md +++ b/docs/en/samples/index.md @@ -9,6 +9,8 @@ A reference application built with ABP. It implements the Domain Driven Design w * [Live demo](https://www.openeventhub.com/) * [Source code](https://github.com/abpframework/eventhub) +![samples-event-hub](../images/samples-eventhub.png) + ## eShopOnAbp > ⚠️ **Important Notice** @@ -18,6 +20,8 @@ Reference microservice solution built with ABP and .NET. * [Source code](https://github.com/abpframework/eShopOnAbp) +![eshoponabp](../images/samples-eshoponabp.png) + ## CMS Kit Demo A minimal example website built with the [CMS Kit module](../modules/cms-kit/index.md). @@ -25,6 +29,8 @@ A minimal example website built with the [CMS Kit module](../modules/cms-kit/ind * [Live demo](https://cms-kit-demo.abpdemo.com/) * [Source code](https://github.com/abpframework/cms-kit-demo) +![samples-cms-kit](../images/samples-cms-kit.png) + ## Easy CRM A middle-size CRM application built with ABP. @@ -32,6 +38,8 @@ A middle-size CRM application built with ABP. * [Live demo](http://easycrm.abp.io/) * [Click here](easy-crm.md) to see the details and download the source code. +![samples-easy-crm](../images/samples-easycrm.png) + ## 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: diff --git a/docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md b/docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md index 3d33cbd7a1..0de00c8d35 100644 --- a/docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md +++ b/docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md @@ -283,6 +283,34 @@ See: - https://learn.microsoft.com/en-us/aspnet/web-api/overview/testing-and-debugging/troubleshooting-http-405-errors-after-publishing-web-api-applications#resolve-http-405-errors - https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/site-behavior-performance/http-error-405-website#resolution-for-cause-3 +## Publish the Application(s) as IIS sub-application + +If your MVC application is a sub-application, you need to set the `BaseUrl` property of `AbpThemingOptions` to the sub-application’s path. The `BaseUrl` is used to configure the `base` element in the `head` section of the layout page. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + Configure(options => + { + options.BaseUrl = "/myapp/"; + }); +} +``` + +```html + + + + ... + + + ... + + +``` + +For Blazor applications, you can to set the `base` tag in the `App.razor` file instead of configure `AbpThemingOptions`. + ## How to get stdout-log If your application is running on IIS and getting errors like `502.5, 500.3x`, you can enable stdout logs to see the error details. diff --git a/docs/en/studio/installation.md b/docs/en/studio/installation.md index 34dde8a5c9..c0e6aebd8f 100644 --- a/docs/en/studio/installation.md +++ b/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. diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs index a48901f6dd..e4a2f8a3cc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.JSInterop; +using Volo.Abp.AspNetCore.Components.Web.Security; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; using Volo.Abp.AspNetCore.Mvc.Client; @@ -23,6 +24,8 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } + protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; } + protected IJSRuntime JSRuntime { get; } protected IClock Clock { get; } @@ -34,6 +37,7 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor ICurrentTimezoneProvider currentTimezoneProvider, AuthenticationStateProvider authenticationStateProvider, AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy, + ApplicationConfigurationChangedService applicationConfigurationChangedService, IJSRuntime jsRuntime, IClock clock) { @@ -42,6 +46,7 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor CurrentTenantAccessor = currentTenantAccessor; CurrentTimezoneProvider = currentTimezoneProvider; ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; + ApplicationConfigurationChangedService = applicationConfigurationChangedService; JSRuntime = jsRuntime; Clock = clock; @@ -81,6 +86,8 @@ namespace Volo.Abp.AspNetCore.Components.MauiBlazor await JSRuntime.InvokeAsync("abp.clock.setBrowserTimeZoneToCookie"); } + + ApplicationConfigurationChangedService.NotifyChanged(); } public virtual Task GetAsync() diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs new file mode 100644 index 0000000000..ad2f6ba983 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor; + +[Dependency(ReplaceServices = true)] +public class MauiCurrentApplicationConfigurationCacheResetService : + ICurrentApplicationConfigurationCacheResetService, + ITransientDependency +{ + private readonly MauiBlazorCachedApplicationConfigurationClient _mauiBlazorCachedApplicationConfigurationClient; + + public MauiCurrentApplicationConfigurationCacheResetService(MauiBlazorCachedApplicationConfigurationClient mauiBlazorCachedApplicationConfigurationClient) + { + _mauiBlazorCachedApplicationConfigurationClient = mauiBlazorCachedApplicationConfigurationClient; + } + + public async Task ResetAsync() + { + await _mauiBlazorCachedApplicationConfigurationClient.InitializeAsync(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs index 62cae94413..af1538bbde 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs @@ -47,11 +47,13 @@ public class AbpInputTagHelper : AbpTagHelper } if (isCheckBox) { - output.Attributes.AddClass("custom-checkbox"); - output.Attributes.AddClass("custom-control"); + if (!TagHelper.UseSwitchCheckBox) + { + output.Attributes.AddClass("custom-checkbox"); + output.Attributes.AddClass("custom-control"); + } + else + { + output.Attributes.AddClass("form-switch"); + } + output.Attributes.AddClass("form-check"); } } @@ -266,7 +274,7 @@ public class AbpInputTagHelperService : AbpTagHelperService } protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) - { + { if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel) { return string.Empty; @@ -395,7 +403,7 @@ public class AbpInputTagHelperService : AbpTagHelperService } innerOutput.Content.AppendHtml($" "); } - + innerOutput.Content.AppendHtml(GetRequiredSymbol(context, output)); return innerOutput.Render(_encoder); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Theming/AbpThemingOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Theming/AbpThemingOptions.cs index 1197ad6e53..bf999be982 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Theming/AbpThemingOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Theming/AbpThemingOptions.cs @@ -6,6 +6,12 @@ public class AbpThemingOptions public string? DefaultThemeName { get; set; } + /// + /// If set, the base element will be added to the head element of the page. + /// eg: + /// + public string? BaseUrl { get; set; } + public AbpThemingOptions() { Themes = new ThemeDictionary(); diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index d042bb6026..b9142b5aed 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -115,7 +115,7 @@ public static class AbpApplicationBuilderExtensions return app.UseMiddleware(); } - [Obsolete("Replace with AbpClaimsTransformation")] + [Obsolete("Use the TransformAbpClaims extension method from IServiceCollection instead.")] public static IApplicationBuilder UseAbpClaimsMap(this IApplicationBuilder app) { return app.UseMiddleware(); diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpClaimsMapMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpClaimsMapMiddleware.cs index 2104edab0a..70fad0d19e 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpClaimsMapMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpClaimsMapMiddleware.cs @@ -11,7 +11,7 @@ using Volo.Abp.Security.Claims; namespace Volo.Abp.AspNetCore.Security.Claims; -[Obsolete("Replace with AbpClaimsTransformation")] +[Obsolete("Use the TransformAbpClaims extension method from IServiceCollection instead.")] public class AbpClaimsMapMiddleware : AbpMiddlewareBase, ITransientDependency { public async override Task InvokeAsync(HttpContext context, RequestDelegate next) diff --git a/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs b/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs index 3357d8f95c..e834b3d00d 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs @@ -38,7 +38,7 @@ public static class AbpRegistrationBuilderExtensions } registrationBuilder = registrationBuilder.EnablePropertyInjection(moduleContainer, implementationType); - registrationBuilder = registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType); + registrationBuilder = registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType, serviceDescriptor.ServiceKey); return registrationBuilder; } @@ -69,10 +69,11 @@ public static class AbpRegistrationBuilderExtensions this IRegistrationBuilder registrationBuilder, ServiceRegistrationActionList registrationActionList, Type serviceType, - Type implementationType) + Type implementationType, + object? serviceKey = null) where TActivatorData : ReflectionActivatorData { - var serviceRegistredArgs = new OnServiceRegistredContext(serviceType, implementationType); + var serviceRegistredArgs = new OnServiceRegistredContext(serviceType, implementationType, serviceKey); foreach (var registrationAction in registrationActionList) { diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs index ee747e4611..345a69703f 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs @@ -25,13 +25,8 @@ public class AbpBackgroundJobsHangfireModule : AbpModule if (!options.IsJobExecutionEnabled) { var hangfireOptions = context.ServiceProvider.GetRequiredService>().Value; - hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer; + context.ServiceProvider.GetRequiredService(); + hangfireOptions.BackgroundJobServerFactory = _ => null; } } - - private BackgroundJobServer? CreateOnlyEnqueueJobServer(IServiceProvider serviceProvider) - { - serviceProvider.GetRequiredService(); - return null; - } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs index 1dbe4a6dcc..bdc28c7ca0 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs +++ b/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 : IJobQueue 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 : IJobQueue //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 : IJobQueue 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); } } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs index 9425cd0604..c6418f52f7 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs +++ b/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 DeclareDelayedAsync(IChannel channel) { - var delayedArguments = new Dictionary(Arguments) + var delayedArguments = new Dictionary(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, diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpBackgroundWorkersHangfireModule.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpBackgroundWorkersHangfireModule.cs index 2577181143..433ac699d2 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpBackgroundWorkersHangfireModule.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/AbpBackgroundWorkersHangfireModule.cs @@ -1,5 +1,4 @@ -using System; -using Hangfire; +using Hangfire; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.Hangfire; @@ -23,17 +22,12 @@ public class AbpBackgroundWorkersHangfireModule : AbpModule if (!options.IsEnabled) { var hangfireOptions = context.ServiceProvider.GetRequiredService>().Value; - hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer; + context.ServiceProvider.GetRequiredService(); + hangfireOptions.BackgroundJobServerFactory = _ => null; } context.ServiceProvider .GetRequiredService() .Initialize(); } - - private BackgroundJobServer? CreateOnlyEnqueueJobServer(IServiceProvider serviceProvider) - { - serviceProvider.GetRequiredService(); - return null; - } } diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs index 1c04fdb755..d20c8d1214 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs @@ -86,7 +86,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency return null; } var result = ossClient.GetObject(containerName, blobName); - return await TryCopyToMemoryStreamAsync(result.Content, args.CancellationToken); + return result.ResponseStream; } protected virtual string GetContainerName(BlobProviderArgs args) diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs index 00a82a0f3e..7c520da471 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs @@ -103,7 +103,7 @@ public class AwsBlobProvider : BlobProviderBase, ITransientDependency Key = blobName }); - return await TryCopyToMemoryStreamAsync(response.ResponseStream, args.CancellationToken); + return response.ResponseStream; } } diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index 5d45e749ea..bea55c624d 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -66,8 +66,7 @@ public class AzureBlobProvider : BlobProviderBase, ITransientDependency } var blobClient = GetBlobClient(args, blobName); - var download = await blobClient.DownloadAsync(); - return await TryCopyToMemoryStreamAsync(download.Value.Content, args.CancellationToken); + return await blobClient.OpenReadAsync(cancellationToken: args.CancellationToken); } protected virtual BlobClient GetBlobClient(BlobProviderArgs args, string blobName) diff --git a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs index a221441749..2a410005db 100644 --- a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs @@ -70,13 +70,7 @@ public class FileSystemBlobProvider : BlobProviderBase, ITransientDependency return await Policy.Handle() .WaitAndRetryAsync(2, retryCount => TimeSpan.FromSeconds(retryCount)) - .ExecuteAsync(async () => - { - using (var fileStream = File.OpenRead(filePath)) - { - return await TryCopyToMemoryStreamAsync(fileStream, args.CancellationToken); - } - }); + .ExecuteAsync(() => Task.FromResult(File.OpenRead(filePath))); } protected virtual Task ExistsAsync(string filePath) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceRegistredContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceRegistredContext.cs index 86dad068b6..a1c50c0c64 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceRegistredContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceRegistredContext.cs @@ -9,4 +9,6 @@ public interface IOnServiceRegistredContext ITypeList Interceptors { get; } Type ImplementationType { get; } + + object? ServiceKey { get; } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceRegistredContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceRegistredContext.cs index a843b136f6..8491c94e91 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceRegistredContext.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceRegistredContext.cs @@ -13,10 +13,13 @@ public class OnServiceRegistredContext : IOnServiceRegistredContext public virtual Type ImplementationType { get; } - public OnServiceRegistredContext(Type serviceType, [NotNull] Type implementationType) + public virtual object? ServiceKey { get; } + + public OnServiceRegistredContext(Type serviceType, [NotNull] Type implementationType, object? serviceKey = null) { ServiceType = Check.NotNull(serviceType, nameof(serviceType)); ImplementationType = Check.NotNull(implementationType, nameof(implementationType)); + ServiceKey = serviceKey; Interceptors = new TypeList(); } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpRabbitMqEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpRabbitMqEventBusOptions.cs index 130f5b38e2..0b450ca26e 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpRabbitMqEventBusOptions.cs +++ b/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 QueueArguments { get; set; } = new Dictionary(); + public IDictionary QueueArguments { get; set; } = new Dictionary(); + + public IDictionary ExchangeArguments { get; set; } = new Dictionary(); - public IDictionary ExchangeArguments { get; set; } = new Dictionary(); - public string GetExchangeTypeOrDefault() { return string.IsNullOrEmpty(ExchangeType) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index b0f85afb56..3a647388b5 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/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 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? 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? 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? headersArguments) + protected virtual void SetEventMessageHeaders(IBasicProperties properties, Dictionary? headersArguments) { if (headersArguments == null) { return; } - properties.Headers ??= new Dictionary(); + properties.Headers ??= new Dictionary(); foreach (var header in headersArguments) { diff --git a/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs b/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs index 9a2f53b988..da6b75b4b8 100644 --- a/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs +++ b/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs @@ -5,9 +5,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Webp; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; @@ -32,7 +29,7 @@ public class ImageSharpImageCompressorContributor : IImageCompressorContributor, return new ImageCompressResult(stream, ImageProcessState.Unsupported); } - var image = await Image.LoadAsync(stream, cancellationToken); + using var image = await Image.LoadAsync(stream, cancellationToken); if (!CanCompress(image.Metadata.DecodedImageFormat!.DefaultMimeType)) { @@ -46,7 +43,7 @@ public class ImageSharpImageCompressorContributor : IImageCompressorContributor, return new ImageCompressResult(memoryStream, ImageProcessState.Done); } - memoryStream.Dispose(); + await memoryStream.DisposeAsync(); return new ImageCompressResult(stream, ImageProcessState.Canceled); } @@ -69,7 +66,7 @@ public class ImageSharpImageCompressorContributor : IImageCompressorContributor, } var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); - result.Result.Dispose(); + await result.Result.DisposeAsync(); return new ImageCompressResult(newBytes, result.State); } @@ -100,7 +97,7 @@ public class ImageSharpImageCompressorContributor : IImageCompressorContributor, } catch { - memoryStream.Dispose(); + await memoryStream.DisposeAsync(); throw; } } @@ -110,11 +107,11 @@ public class ImageSharpImageCompressorContributor : IImageCompressorContributor, switch (format.DefaultMimeType) { case MimeTypes.Image.Jpeg: - return Options.JpegEncoder ?? new JpegEncoder(); + return Options.JpegEncoder; case MimeTypes.Image.Png: - return Options.PngEncoder ?? new PngEncoder(); + return Options.PngEncoder; case MimeTypes.Image.Webp: - return Options.WebpEncoder ?? new WebpEncoder(); + return Options.WebpEncoder; default: throw new NotSupportedException($"No encoder available for the given format: {format.Name}"); } diff --git a/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs b/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs index 6e17ddc9a8..e52155eaeb 100644 --- a/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs +++ b/framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs @@ -23,7 +23,7 @@ public class ImageSharpImageResizerContributor : IImageResizerContributor, ITran return new ImageResizeResult(stream, ImageProcessState.Unsupported); } - var image = await Image.LoadAsync(stream, cancellationToken); + using var image = await Image.LoadAsync(stream, cancellationToken); if (!CanResize(image.Metadata.DecodedImageFormat!.DefaultMimeType)) { @@ -49,7 +49,7 @@ public class ImageSharpImageResizerContributor : IImageResizerContributor, ITran } catch { - memoryStream.Dispose(); + await memoryStream.DisposeAsync(); throw; } } @@ -76,7 +76,7 @@ public class ImageSharpImageResizerContributor : IImageResizerContributor, ITran var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); - result.Result.Dispose(); + await result.Result.DisposeAsync(); return new ImageResizeResult(newBytes, result.State); } diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs index 760acd0916..0ba2c7c833 100644 --- a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs @@ -25,21 +25,19 @@ public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITrans return new ImageResizeResult(bytes, ImageProcessState.Unsupported); } - using (var memoryStream = new MemoryStream(bytes)) - { - var result = await TryResizeAsync(memoryStream, resizeArgs, mimeType, cancellationToken); + using var memoryStream = new MemoryStream(bytes); + var result = await TryResizeAsync(memoryStream, resizeArgs, mimeType, cancellationToken); - if (result.State != ImageProcessState.Done) - { - return new ImageResizeResult(bytes, result.State); - } + if (result.State != ImageProcessState.Done) + { + return new ImageResizeResult(bytes, result.State); + } - var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); + var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); - result.Result.Dispose(); + result.Result.Dispose(); - return new ImageResizeResult(newBytes, result.State); - } + return new ImageResizeResult(newBytes, result.State); } public virtual async Task> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default) @@ -51,21 +49,14 @@ public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITrans var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream); - using (var original = SKBitmap.Decode(memoryBitmapStream)) - { - using (var resized = original.Resize(new SKImageInfo(resizeArgs.Width, resizeArgs.Height), Options.SKFilterQuality)) - { - using (var image = SKImage.FromBitmap(resized)) - { - using (var codec = SKCodec.Create(memorySkCodecStream)) - { - var memoryStream = new MemoryStream(); - image.Encode(codec.EncodedFormat, Options.Quality).SaveTo(memoryStream); - return new ImageResizeResult(memoryStream, ImageProcessState.Done); - } - } - } - } + using var original = SKBitmap.Decode(memoryBitmapStream); + using var resized = original.Resize(new SKImageInfo(resizeArgs.Width, resizeArgs.Height), Options.SKFilterQuality); + using var image = SKImage.FromBitmap(resized); + using var codec = SKCodec.Create(memorySkCodecStream); + var memoryStream = new MemoryStream(); + using var skData = image.Encode(codec.EncodedFormat, Options.Quality); + skData.SaveTo(memoryStream); + return new ImageResizeResult(memoryStream, ImageProcessState.Done); } protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream) diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs index da010b83c9..74b41dea23 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/AbpRabbitMqModule.cs +++ b/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() - .Dispose(); + .DisposeAsync(); - context.ServiceProvider + await context.ServiceProvider .GetRequiredService() - .Dispose(); + .DisposeAsync(); } } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs index b7ed4c80c8..03d6029b69 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs +++ b/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 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.Instance; } - public virtual IChannelAccessor Acquire(string? channelName = null, string? connectionName = null) + public virtual async Task 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 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; diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ConnectionPool.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ConnectionPool.cs index 5f4556b96a..888de64613 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ConnectionPool.cs +++ b/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> Connections { get; } + protected ConcurrentDictionary Connections { get; } + + protected SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1); private bool _isDisposed; public ConnectionPool(IOptions options) { Options = options.Value; - Connections = new ConcurrentDictionary>(); + Connections = new ConcurrentDictionary(); } - public virtual IConnection Get(string? connectionName = null) + public virtual async Task 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 GetConnectionAsync(string connectionName, ConnectionFactory connectionFactory) { - return Connections.GetOrAdd( - connectionName, () => new Lazy(() => - { - 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 } } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs index 5c239893c7..4a5c0f01d0 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs @@ -12,19 +12,19 @@ public class ExchangeDeclareConfiguration public bool AutoDelete { get; set; } - public IDictionary Arguments { get; } + public IDictionary Arguments { get; } public ExchangeDeclareConfiguration( string exchangeName, string type, bool durable = false, bool autoDelete = false, - IDictionary? arguments = null) + IDictionary? arguments = null) { ExchangeName = exchangeName; Type = type; Durable = durable; AutoDelete = autoDelete; - Arguments = arguments?? new Dictionary(); + Arguments = arguments?? new Dictionary(); } } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelAccessor.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelAccessor.cs index ad8829f8ee..d4eb954a67 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelAccessor.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelAccessor.cs @@ -10,7 +10,7 @@ public interface IChannelAccessor : IDisposable /// Never dispose the object. /// Instead, dispose the after usage. /// - IModel Channel { get; } + IChannel Channel { get; } /// /// Name of the channel. diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelPool.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelPool.cs index 2ba6259fec..5a9611ad86 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IChannelPool.cs +++ b/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 AcquireAsync(string? channelName = null, string? connectionName = null); } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IConnectionPool.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IConnectionPool.cs index dc97476c84..02fdf286d9 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IConnectionPool.cs +++ b/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 GetAsync(string? connectionName = null); } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqMessageConsumer.cs index a24ad69a4a..8501b8958f 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqMessageConsumer.cs @@ -11,5 +11,5 @@ public interface IRabbitMqMessageConsumer Task UnbindAsync(string routingKey); - void OnMessageReceived(Func callback); + void OnMessageReceived(Func callback); } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs index eb59ec3058..ebb2c3e1cc 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs +++ b/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 Arguments { get; } + public ushort? PrefetchCount { get; set; } + + public IDictionary Arguments { get; } public QueueDeclareConfiguration( [NotNull] string queueName, @@ -24,19 +25,19 @@ public class QueueDeclareConfiguration bool exclusive = false, bool autoDelete = false, ushort? prefetchCount = null, - IDictionary? arguments = null) + IDictionary? arguments = null) { QueueName = queueName; Durable = durable; Exclusive = exclusive; AutoDelete = autoDelete; - Arguments = arguments?? new Dictionary(); + Arguments = arguments?? new Dictionary(); PrefetchCount = prefetchCount; } - public virtual QueueDeclareOk Declare(IModel channel) + public virtual async Task DeclareAsync(IChannel channel) { - return channel.QueueDeclare( + return await channel.QueueDeclareAsync( queue: QueueName, durable: Durable, exclusive: Exclusive, diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 6f361f7670..74dac68a61 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/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> Callbacks { get; } + protected ConcurrentBag> Callbacks { get; } - protected IModel? Channel { get; private set; } + protected IChannel? Channel { get; private set; } protected ConcurrentQueue 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.Instance; QueueBindCommands = new ConcurrentQueue(); - Callbacks = new ConcurrentBag>(); + Callbacks = new ConcurrentBag>(); 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 callback) + public virtual void OnMessageReceived(Func 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 { } diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.js index 0e13317cbf..11cbe56803 100644 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.js +++ b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.js @@ -4,7 +4,9 @@ var abp = abp || {}; /* Application paths *****************************************/ //Current application root path (including virtual directory if exists). - abp.appPath = abp.appPath || '/'; + var baseElement = document.querySelector('base'); + var baseHref = baseElement ? baseElement.getAttribute('href') : null; + abp.appPath = baseHref || abp.appPath || '/'; /* UTILS ***************************************************/ diff --git a/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs index b2af6d85da..46c9ea2e8c 100644 --- a/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs +++ b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs @@ -31,9 +31,9 @@ public class AutoFac_OnActivated_Tests : Autofac_Interception_Test var server = ServiceProvider.GetRequiredService(); server.Name.ShouldBe("MyServer12"); } -} -class MyServer -{ - public string Name { get; set; } = "MyServer"; + class MyServer + { + public string Name { get; set; } = "MyServer"; + } } diff --git a/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnRegistred_Tests.cs b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnRegistred_Tests.cs new file mode 100644 index 0000000000..34b739cb7f --- /dev/null +++ b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnRegistred_Tests.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Autofac.Interception; +using Xunit; + +namespace Volo.Abp.Autofac; + +public class AutoFac_OnRegistred_Tests : Autofac_Interception_Test +{ + protected override Task AfterAddApplicationAsync(IServiceCollection services) + { + services.Add(ServiceDescriptor.KeyedTransient("key")); + services.OnRegistered(onServiceRegistredContext => + { + if (onServiceRegistredContext.ImplementationType == typeof(MyServer)) + { + onServiceRegistredContext.ServiceKey.ShouldBe("key"); + } + }); + + return base.AfterAddApplicationAsync(services); + } + + class MyServer + { + public string Name { get; set; } = "MyServer"; + } +} diff --git a/latest-versions.json b/latest-versions.json index 0ced5df485..340ed62653 100644 --- a/latest-versions.json +++ b/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": "", diff --git a/modules/basic-theme/Volo.Abp.BasicTheme.sln b/modules/basic-theme/Volo.Abp.BasicTheme.sln index ac0ab4ed3b..66f19dad9a 100644 --- a/modules/basic-theme/Volo.Abp.BasicTheme.sln +++ b/modules/basic-theme/Volo.Abp.BasicTheme.sln @@ -1,5 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.35906.104 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED6D078F-B0A2-48E8-A09D-3B7CDF6CE3D1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0BC55E3B-4964-48E3-A390-2ADD37980149}" @@ -65,6 +68,9 @@ Global {D02053D9-10EF-4717-A792-A53F83347816}.Release|Any CPU.ActiveCfg = Release|Any CPU {D02053D9-10EF-4717-A792-A53F83347816}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection GlobalSection(NestedProjects) = preSolution {C8068E7F-4A04-4755-8976-C2A4C0ADC708} = {ED6D078F-B0A2-48E8-A09D-3B7CDF6CE3D1} {655C0CF7-7BFA-45E4-A157-E868A97FB45B} = {ED6D078F-B0A2-48E8-A09D-3B7CDF6CE3D1} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml index 5f7298ca25..6c4aa7f3b8 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml @@ -9,7 +9,7 @@ @Model.CurrentLanguage.DisplayName - - \ No newline at end of file + + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpBadgeTagHelper)) \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Blockquotes.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Blockquotes.cshtml index 34ab545163..63bfbd519c 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Blockquotes.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Blockquotes.cshtml @@ -30,3 +30,6 @@ +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpBlockquoteTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Borders.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Borders.cshtml index 7473c84bb8..4f7a7a93b6 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Borders.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Borders.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Border @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.BordersModel @{ ViewData["Title"] = "Borders"; @@ -196,3 +197,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpBorderTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Breadcrumbs.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Breadcrumbs.cshtml index 4c4164a823..f7e056ed3b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Breadcrumbs.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Breadcrumbs.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.BreadcrumbsModel @{ ViewData["Title"] = "Breadcrumbs"; @@ -77,3 +78,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpBreadcrumbTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ButtonGroups.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ButtonGroups.cshtml index 94e0d7ee70..36f3f41be3 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ButtonGroups.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ButtonGroups.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.ButtonGroupsModel @{ ViewData["Title"] = "ButtonGroups"; @@ -268,4 +269,8 @@ - \ No newline at end of file + + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpButtonGroupTagHelper)) \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Buttons.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Buttons.cshtml index 08d1e0093a..ad6324614c 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Buttons.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Buttons.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.ButtonsModel @{ ViewData["Title"] = "Buttons"; @@ -258,4 +259,8 @@ - \ No newline at end of file + + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpButtonTagHelper)) \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml index 2880f8b179..bfdf80e784 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml @@ -1,5 +1,7 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Card +@using Volo.Abp.DependencyInjection @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.CardsModel @{ ViewData["Title"] = "Cards"; @@ -41,7 +43,7 @@

-<div class="card" style="width: 18rem;">
+<div class="card mb-3" style="width: 18rem;">
   <img class="card-img-top" src=".../100px180/" alt="Card image cap">
   <div class="card-body">
     <h5 class="card-title">Card title</h5>
@@ -88,7 +90,7 @@
             
             
                 

-<div class="card" style="width: 18rem;">
+<div class="card mb-3" style="width: 18rem;">
   <div class="card-body">
     <h5 class="card-title">Card title</h5>
     <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
@@ -132,7 +134,7 @@
             
             
                 

-<div class="card" style="width: 18rem;">
+<div class="card mb-3" style="width: 18rem;">
   <ul class="list-group list-group-flush">
     <li class="list-group-item">Cras justo odio</li>
     <li class="list-group-item">Dapibus ac facilisis in</li>
@@ -174,7 +176,7 @@
             
             
                 

-<div class="card" style="width: 18rem;">
+<div class="card mb-3" style="width: 18rem;">
   <div class="card-header">
     Featured
   </div>
@@ -237,7 +239,7 @@
             
             
                 

-<div class="card" style="width: 18rem;">
+<div class="card mb-3" style="width: 18rem;">
   <img class="card-img-top" src=".../100px180/?text=Image cap" alt="Card image cap">
   <div class="card-body">
     <h5 class="card-title">Card title</h5>
@@ -290,7 +292,7 @@
             
             
                 

-<div class="card">
+<div class="card mb-3">
   <div class="card-header">
     Featured
   </div>
@@ -337,7 +339,7 @@
             
             
                 

-<div class="card">
+<div class="card mb-3">
   <div class="card-header">
     Quote
   </div>
@@ -586,4 +588,8 @@
             
         
     
-
\ No newline at end of file
+
+
+
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpCardTagHelper)) \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml.cs index 4a5d541668..01323da4d0 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml.cs +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Cards.cshtml.cs @@ -1,4 +1,10 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Card; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components; @@ -6,6 +12,6 @@ public class CardsModel : PageModel { public void OnGet() { - + } } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Carousel.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Carousel.cshtml index d1b6cbe0b1..9d44f19453 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Carousel.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Carousel.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.CarouselsModel @{ ViewData["Title"] = "Carousels"; @@ -266,3 +267,6 @@ +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpCarouselTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Collapse.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Collapse.cshtml index 728afc0a1e..b8b01ec60e 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Collapse.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Collapse.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.CollapseModel @{ ViewData["Title"] = "Collapse"; @@ -214,3 +215,28 @@ + +
+ + + + + +

abp-accordion

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpAccordionTagHelper)) + +
+ + + + +

abp-accordion-item

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpAccordionItemTagHelper)) + +
diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DatePicker.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DatePicker.cshtml index b65eaf15be..800fc4bf36 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DatePicker.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DatePicker.cshtml @@ -1,4 +1,4 @@ -@page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.DatePickerModel

Date Picker & Date Range Picker

@@ -775,5 +775,7 @@ public class DatePickerModel : PageModel +
+@await Component.InvokeAsync("TagHelperProperties", typeof(AbpDatePickerTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Dropdowns.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Dropdowns.cshtml index 3f5717033d..c2d84593cf 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Dropdowns.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Dropdowns.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Dropdown @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.DropdownsModel @{ ViewData["Title"] = "Dropdowns"; @@ -735,4 +736,8 @@ - \ No newline at end of file + + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpDropdownTagHelper)) \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml index cbec1ee01c..ab44e878c7 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form @model DynamicFormsModel @{ @@ -428,4 +429,8 @@ public class DynamicFormsModel : PageModel - \ No newline at end of file + + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpDynamicFormTagHelper)) \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml index e637aebfad..3ed17afd37 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml @@ -1,6 +1,7 @@ @page -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components -@model FormElementsModel +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form +@model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.FormElementsModel @{ ViewData["Title"] = "Form Elements"; @@ -65,7 +66,7 @@ <input type="password" data-val="true" data-val-required="The Password field is required." id="MyModel_Password" name="MyModel.Password" class="form-control "> <span class="text-danger field-validation-valid" data-valmsg-for="MyModel.Password" data-valmsg-replace="true"></span> </div> -<div class="form-check"> +<div class="mb-2 form-check"> <input type="checkbox" data-val="true" data-val-required="The CheckMeOut field is required." id="MyModel_CheckMeOut" name="MyModel.CheckMeOut" value="true" class="form-check-input "><input name="MyModel.CheckMeOut" type="hidden" value="false"> <label class="form-check-label" for="MyModel_CheckMeOut">Check Me Out</label> </div> @@ -495,4 +496,30 @@ - \ No newline at end of file + + +
+ + + + +

abp-input

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpInputTagHelper)) + +
+ + + + +

abp-select

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpSelectTagHelper)) + +
+ + diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Grids.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Grids.cshtml index 0e33d39c59..37a9ad4428 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Grids.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Grids.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.GridsModel @{ ViewData["Title"] = "Grids"; @@ -830,4 +831,38 @@
+ + + + +

abp-container

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpContainerTagHelper)) + +
+ + + + +

abp-row

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpRowTagHelper)) + +
+ + + + +

abp-column

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpColumnTagHelper)) + +
+ < back \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ListGroup.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ListGroup.cshtml index ce66b8d5aa..71668d3496 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ListGroup.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ListGroup.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.ListGroup @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.ListGroupsModel @{ ViewData["Title"] = "List Groups"; @@ -347,3 +348,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpListGroupTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Modals.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Modals.cshtml index 9e51ef25a3..fa208cb0a8 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Modals.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Modals.cshtml @@ -73,3 +73,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpModalTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Navs.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Navs.cshtml index ca9828d348..28d46eb59e 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Navs.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Navs.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Nav @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.NavsModel @{ ViewData["Title"] = "Navs"; @@ -201,3 +202,31 @@ + +
+ + + + +

abp-nav-bar

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpNavBarTagHelper)) + +
+ + + + +

abp-nav-item

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpNavItemTagHelper)) + +
+ + + + diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml index 00666c091a..77fe1530a3 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Paginator.cshtml @@ -1,4 +1,6 @@ @page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.PaginatorModel @{ ViewData["Title"] = "Paginator"; @@ -89,3 +91,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpPaginationTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml index ddc87ba66d..10fb759853 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.PopoversModel @{ ViewData["Title"] = "Popovers"; @@ -69,3 +70,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpPopoverTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ProgressBars.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ProgressBars.cshtml index 0390090e9a..3566c194db 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ProgressBars.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/ProgressBars.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.ProgressBar @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.ProgressBarsModel @{ ViewData["Title"] = "Progress Bars"; @@ -77,3 +78,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpProgressBarTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tables.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tables.cshtml index e39fe8cae3..f73a6d2ca5 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tables.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tables.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Table @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.TablesModel @{ ViewData["Title"] = "Tables"; @@ -449,3 +450,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpTableTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tabs.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tabs.cshtml index e9631ed179..a0efeb0c0a 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tabs.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tabs.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.TabsModel @{ ViewData["Title"] = "Tabs"; @@ -293,4 +294,39 @@ - \ No newline at end of file + + +
+ + + + +

abp-tab

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpTabTagHelper)) + +
+ + + + +

abp-tab-link

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpTabLinkTagHelper)) + +
+ + + + +

abp-tab-dropdown

+
+
+ + @await Component.InvokeAsync("TagHelperProperties", typeof(AbpTabDropdownTagHelper)) + +
diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tooltips.cshtml b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tooltips.cshtml index f19161d515..7bbf1cd2da 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tooltips.cshtml +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tooltips.cshtml @@ -1,5 +1,6 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tooltip @model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.TooltipsModel @{ ViewData["Title"] = "Tooltips"; @@ -84,3 +85,7 @@ + +
+ +@await Component.InvokeAsync("TagHelperProperties", typeof(AbpTooltipTagHelper)) diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenuContributor.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenuContributor.cs index e36f329306..625fb60a80 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenuContributor.cs +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenuContributor.cs @@ -25,6 +25,7 @@ public class BootstrapDemoMenuContributor : IMenuContributor { new ApplicationMenuItem(BootstrapDemoMenus.Components.Alerts, "Alerts", url: "/Components/Alerts"), new ApplicationMenuItem(BootstrapDemoMenus.Components.Badges, "Badges", url: "/Components/Badges"), + new ApplicationMenuItem(BootstrapDemoMenus.Components.Blockquotes, "Blockquotes", url: "/Components/Blockquotes"), new ApplicationMenuItem(BootstrapDemoMenus.Components.Borders, "Borders", url: "/Components/Borders"), new ApplicationMenuItem(BootstrapDemoMenus.Components.Breadcrumbs, "Breadcrumbs", url: "/Components/Breadcrumbs"), new ApplicationMenuItem(BootstrapDemoMenus.Components.Buttons, "Buttons", url: "/Components/Buttons"), diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenus.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenus.cs index 2f381221ed..7a758523af 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenus.cs +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Menus/BootstrapDemoMenus.cs @@ -11,6 +11,7 @@ public class BootstrapDemoMenus public const string Alerts = Root + ".Alerts"; public const string Badges = Root + ".Badges"; public const string Borders = Root + ".Borders"; + public const string Blockquotes = Root + ".Blockquotes"; public const string Breadcrumbs = Root + ".Breadcrumbs"; public const string ButtonGroups = Root + ".ButtonGroups"; public const string Buttons = Root + ".Buttons"; diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs index ad3a39c746..ac9601cda3 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs @@ -1,31 +1,42 @@ using System; -using System.IO; -using Microsoft.AspNetCore.Hosting; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo; public class Program { - public static int Main(string[] args) + public async static Task Main(string[] args) { Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() //TODO: Should be configurable! - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Debug() .Enrich.FromLogContext() - .WriteTo.File("Logs/logs.txt") + .WriteTo.Async(c => c.Console()) .CreateLogger(); try { Log.Information("Starting web host."); - CreateHostBuilder(args).Build().Run(); + var builder = WebApplication.CreateBuilder(args); + builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .UseSerilog(); + await builder.AddApplicationAsync(); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); return 0; } catch (Exception ex) { + if (ex is HostAbortedException) + { + throw; + } + Log.Fatal(ex, "Host terminated unexpectedly!"); return 1; } @@ -34,14 +45,4 @@ public class Program Log.CloseAndFlush(); } } - - - internal static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseAutofac() - .UseSerilog(); } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json index 07a310a660..7ad9d24119 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51339", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Volo.AbpIo.Commercial.Web": { + "Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo": { "commandName": "Project", "launchBrowser": true, - "applicationUrl": "http://localhost:5000", + "applicationUrl": "https://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} \ No newline at end of file +} diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs deleted file mode 100644 index 838cf43402..0000000000 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddApplication(); - } - - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - app.InitializeApplication(); - } -} diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj index f8202b97ec..9aa8142fda 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj @@ -10,7 +10,7 @@ - + @@ -23,5 +23,5 @@ Always - + diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/AbpAspNetCoreMvcUiThemeBasicDemoModule.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/AbpAspNetCoreMvcUiThemeBasicDemoModule.cs index 4ebeb18498..c887825b2b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/AbpAspNetCoreMvcUiThemeBasicDemoModule.cs +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/AbpAspNetCoreMvcUiThemeBasicDemoModule.cs @@ -55,8 +55,8 @@ public class AbpAspNetCoreMvcUiThemeBasicDemoModule : AbpModule app.UseDeveloperExceptionPage(); } - app.MapAbpStaticAssets(); app.UseRouting(); + app.MapAbpStaticAssets(); app.UseConfiguredEndpoints(); } } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Program.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Program.cs index a27f797715..d2ca3053b8 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Program.cs +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Program.cs @@ -1,30 +1,42 @@ using System; -using Microsoft.AspNetCore.Hosting; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo; public class Program { - public static int Main(string[] args) + public async static Task Main(string[] args) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.FromLogContext() - .WriteTo.Async(c => c.File("Logs/logs.txt")) .WriteTo.Async(c => c.Console()) .CreateLogger(); try { Log.Information("Starting web host."); - CreateHostBuilder(args).Build().Run(); + var builder = WebApplication.CreateBuilder(args); + builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .UseSerilog(); + await builder.AddApplicationAsync(); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); return 0; } catch (Exception ex) { + if (ex is HostAbortedException) + { + throw; + } + Log.Fatal(ex, "Host terminated unexpectedly!"); return 1; } @@ -33,14 +45,4 @@ public class Program Log.CloseAndFlush(); } } - - - internal static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseAutofac() - .UseSerilog(); } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Properties/launchSettings.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Properties/launchSettings.json index 9bee4298ff..4ae3999794 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Properties/launchSettings.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Properties/launchSettings.json @@ -1,24 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61659", - "sslPort": 0 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo": { "commandName": "Project", "launchBrowser": true, - "applicationUrl": "http://localhost:5000", + "applicationUrl": "https://localhost:5001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Startup.cs b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Startup.cs deleted file mode 100644 index a11ca855da..0000000000 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Startup.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddApplication(); - } - - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - app.InitializeApplication(); - } -} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor index 77f5018e10..a55e1cb026 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor @@ -17,12 +17,14 @@ - @foreach (var group in Groups) - { - - @group.DisplayName - - } +
+ @foreach (var group in Groups) + { + + @group.DisplayName + + } +
@for (var i = 0; i < Groups.Count; i++) @@ -35,12 +37,12 @@ {
@{ - var disabled = IsDisabled(feature.Provider.Name); + var disabled = IsDisabled(feature); if (feature.ValueType is FreeTextStringValueType) { - @feature.DisplayName + @GetShownName(feature) @@ -56,8 +58,8 @@ var items = ((SelectionStringValueType)feature.ValueType).ItemSource.Items; var selectedValue = SelectionStringValues[feature.Name]; - @feature.DisplayName - @foreach (var item in items) { @@ -75,8 +77,8 @@ if (feature.ValueType is ToggleStringValueType) { - - @feature.DisplayName + + @GetShownName(feature) @if (feature.Description != null) { diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs index 0d0728db71..b0b7b6f70f 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs @@ -150,9 +150,16 @@ public partial class FeatureManagementModal return $"margin-left: {feature.Depth * 20}px"; } - protected virtual bool IsDisabled(string providerName) + protected virtual bool IsDisabled(FeatureDto feature) { - return providerName != ProviderName && providerName != DefaultValueFeatureValueProvider.ProviderName; + return feature.Provider.Name != ProviderName && feature.Provider.Name != DefaultValueFeatureValueProvider.ProviderName; + } + + public string GetShownName(FeatureDto featureDto) + { + return !IsDisabled(featureDto) + ? featureDto.DisplayName + : $"{featureDto.DisplayName} ({featureDto.Provider.Name})"; } protected virtual async Task OnFeatureValueChangedAsync(string value, FeatureDto feature) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs index 9805f03752..c105779546 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs @@ -152,7 +152,7 @@ public class FeatureManager : IFeatureManager, ISingletonDependency await using (await providers[0].HandleContextAsync(providerName, providerKey)) { var fallbackValue = await GetOrNullInternalAsync(name, providers[1].Name, null); - if (fallbackValue.Value == value) + if (string.Equals(fallbackValue.Value, value, StringComparison.OrdinalIgnoreCase)) { //Clear the value if it's same as it's fallback value value = null; diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml index 9c3ceca88f..f9823e0e58 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml @@ -21,6 +21,7 @@ } +
@@ -39,49 +40,45 @@

@featureGroup.DisplayName


-
+
@for (var j = 0; j < featureGroup.Features.Count; j++) { var feature = featureGroup.Features[j]; + var disabled = Model.IsDisabled(feature);
+ - @if (feature.ValueType is ToggleStringValueType) { - - @if (feature.Description != null) + type="checkbox" + class="d-inline" + abp-id-name="@Model.FeatureGroups[i].Features[j].BoolValue" + label="@Model.GetShownName(feature)" + disabled="disabled" + group-data-feature-name="@feature.Name" + group-data-parent-name="@(feature.ParentName ?? "")" + group-style="margin-inline-start: @(feature.Depth * 20)px"/> + if (feature.Description != null) {
@feature.Description
} - } @if (feature.ValueType is FreeTextStringValueType) { - var type = "text"; - if(feature.ValueType.Validator is NumericValueValidator) - { - type = "number"; - } - + var type = feature.ValueType.Validator is NumericValueValidator ? "number" : "text"; - @if (feature.Description != null) + label="@Model.GetShownName(feature)" + abp-id-name="@Model.FeatureGroups[i].Features[j].Value" + type="@type" + disabled="disabled" + group-data-feature-name="@feature.Name" + group-data-parent-name="@(feature.ParentName ?? "")" + group-style="margin-inline-start: @(feature.Depth * 25)px"/> + if (feature.Description != null) {
@feature.Description
} @@ -90,19 +87,12 @@ @if (feature.ValueType is SelectionStringValueType selectType) {
- - - @foreach (var item in selectType.ItemSource.Items) { - if (item.Value == feature.Value) - { - - } - else - { - - } + var selected = item.Value == feature.Value ? "selected=\"selected\"" : ""; + @CreateHtmlLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) } @if (feature.Description != null) @@ -111,8 +101,8 @@ }
} - +
}
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs index f08d42cfd2..39bb3ad4f0 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs @@ -25,9 +25,9 @@ public class FeatureManagementModal : AbpPageModel [HiddenInput] [BindProperty(SupportsGet = true)] public string ProviderKey { get; set; } - + [HiddenInput] - [BindProperty(SupportsGet = true)] + [BindProperty(SupportsGet = true)] public string ProviderKeyDisplayName { get; set; } [BindProperty] @@ -69,7 +69,7 @@ public class FeatureManagementModal : AbpPageModel { var features = new UpdateFeaturesDto { - Features = FeatureGroups.SelectMany(g => g.Features).Select(f => new UpdateFeatureDto + Features = FeatureGroups.SelectMany(g => g.Features).Where(x => !x.IsDisabled).Select(f => new UpdateFeatureDto { Name = f.Name, Value = f.Type == nameof(ToggleStringValueType) ? f.BoolValue.ToString() : f.Value @@ -85,6 +85,18 @@ public class FeatureManagementModal : AbpPageModel return NoContent(); } + public bool IsDisabled(FeatureDto featureDto) + { + return featureDto.Provider.Name != ProviderName && featureDto.Provider.Name != DefaultValueFeatureValueProvider.ProviderName; + } + + public string GetShownName(FeatureDto featureDto) + { + return !IsDisabled(featureDto) + ? featureDto.DisplayName + : $"{featureDto.DisplayName} ({featureDto.Provider.Name})"; + } + public class FeatureGroupViewModel { public List Features { get; set; } @@ -92,6 +104,8 @@ public class FeatureManagementModal : AbpPageModel public class FeatureViewModel { + public bool IsDisabled { get; set; } + public string Name { get; set; } public string Value { get; set; } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css new file mode 100644 index 0000000000..921a40b160 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css @@ -0,0 +1,11 @@ +#FeaturesTabsContent { + padding-top: 0 !important; +} + +.custom-scroll-content { + max-height: 400px; +} + +.custom-scroll-container > .col-md-4 { + max-height: 500px; +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js index fd9c654ae6..5595db7a47 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js +++ b/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', }); }); diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs index a56935424e..582d9ade42 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -58,7 +59,7 @@ public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppSe { //TODO: Should also include roles of the related OUs. - var roles = await UserRepository.GetRolesAsync(id); + var roles = (await UserRepository.GetRolesAsync(id)).OrderBy(x => x.Name).ToList(); return new ListResultDto( ObjectMapper.Map, List>(roles) @@ -68,9 +69,8 @@ public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppSe [Authorize(IdentityPermissions.Users.Default)] public virtual async Task> GetAssignableRolesAsync() { - var list = await RoleRepository.GetListAsync(); - return new ListResultDto( - ObjectMapper.Map, List>(list)); + var list = (await RoleRepository.GetListAsync()).OrderBy(x => x.Name).ToList(); + return new ListResultDto(ObjectMapper.Map, List>(list)); } [Authorize(IdentityPermissions.Users.Create)] diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor index e3ab222ccf..6fb9445860 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor @@ -11,21 +11,22 @@ - + - @foreach (var group in SettingComponentCreationContext.Groups) - { - - @group.DisplayName - - } +
+ @foreach (var group in SettingComponentCreationContext.Groups) + { + + @group.DisplayName + + } +
@foreach (var group in SettingComponentCreationContext.Groups) { - @{ SettingItemRenders.Add(builder => { diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManager.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManager.cs index ddf451154d..b3fd120f38 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManager.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManager.cs @@ -136,7 +136,7 @@ public class SettingManager : ISettingManager, ISingletonDependency if (providers.Count > 1 && !forceToSet && setting.IsInherited && value != null) { var fallbackValue = await GetOrNullInternalAsync(name, providers[1].Name, null); - if (fallbackValue == value) + if (string.Equals(fallbackValue, value, StringComparison.OrdinalIgnoreCase)) { //Clear the value if it's same as it's fallback value value = null; diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml index 7fa674f4be..d31670a036 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml @@ -35,7 +35,7 @@
-
+
diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index 59f452a0b8..3b5c7d36b1 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -46,6 +46,7 @@ import { ENTITY_PROP_TYPE_CLASSES, EXTENSIONS_IDENTIFIER, PROP_DATA_STREAM, + ROW_RECORD, } from '../../tokens/extensions.token'; import { GridActionsComponent } from '../grid-actions/grid-actions.component'; @@ -187,6 +188,10 @@ export class ExtensibleTableComponent implements OnChanges, AfterViewIn provide: PROP_DATA_STREAM, useValue: value, }, + { + provide: ROW_RECORD, + useValue: record, + }, ], parent: this.#injector, }); @@ -222,8 +227,8 @@ export class ExtensibleTableComponent implements OnChanges, AfterViewIn ngAfterViewInit(): void { this.list?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => { - this.data = []; - this.cdr.markForCheck(); - }); + this.data = []; + this.cdr.markForCheck(); + }); } } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/tokens/extensions.token.ts b/npm/ng-packs/packages/components/extensible/src/lib/tokens/extensions.token.ts index 0a2df45a00..bafaf29908 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/tokens/extensions.token.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/tokens/extensions.token.ts @@ -18,6 +18,9 @@ export const PROP_DATA_STREAM = new InjectionToken>('PROP_DATA_S type EntityPropTypeClassMap = { [key in ePropType]: string; }; + +export const ROW_RECORD = new InjectionToken('ROW_RECORD'); + export type EntityPropTypeClass = Partial; export const ENTITY_PROP_TYPE_CLASSES = new InjectionToken( 'ENTITY_PROP_TYPE_CLASSES', diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index a1175b0b79..f117c4d5f1 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -4,7 +4,9 @@ var abp = abp || {}; /* Application paths *****************************************/ //Current application root path (including virtual directory if exists). - abp.appPath = abp.appPath || '/'; + var baseElement = document.querySelector('base'); + var baseHref = baseElement ? baseElement.getAttribute('href') : null; + abp.appPath = baseHref || abp.appPath || '/'; abp.pageLoadTime = new Date();